import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { CRM_FRONTEND_APP_ID } from '../src/route-surface-policy';
import {
  CRM_CURRENCY_CODE_FIELD,
  CRM_FIELD_DISPLAY_VALUE_SUFFIX,
  CRM_LOOKUP_FALLBACK_NAME_SUFFIXES,
  CRM_LOOKUP_NAME_SUFFIX,
  CRM_LOOKUP_TYPE_SUFFIX,
  CRM_MULTIPICKLIST_DELIMITER,
  CRM_URL_VALUE_SUFFIX,
} from '../src/ui-field-surface-policy';
import { InlineEditField } from '../src/platform/features/record/components/InlineEditField';
import { buildRuntimePicklistCriteria } from '../src/platform/features/record/runtimePicklist';
import {
  clearFieldSurfacePolicyForTest,
  registerFieldSurfacePolicy,
} from '../src/platform/features/objects/fieldSurfacePolicy';
import type { SObject, SObjectField } from '../src/platform/types';

const dataAPIMock = vi.hoisted(() => ({
  updateRecord: vi.fn(),
  query: vi.fn(),
}));

const fieldInputState = vi.hoisted(() => ({
  useToggleInput: false,
}));

vi.mock('../src/platform/infrastructure/api/data', () => ({
  dataAPI: {
    updateRecord: (...args: unknown[]) => dataAPIMock.updateRecord(...args),
    query: (...args: unknown[]) => dataAPIMock.query(...args),
  },
}));

vi.mock('../src/platform/registries/FieldUiRegistry', () => ({
  // No getErrorMessage here: FieldUiRegistry never exported one, so the stub
  // that used to sit at the top of this factory could not be reached by
  // anything. Inline-edit error text now comes from inlineEditErrorMessage,
  // covered in inline-edit-error-message.test.ts.
  isSystemField: (fieldName: string) => (
    ['CreatedDate', 'CreatedById', 'LastModifiedDate', 'LastModifiedById', 'SystemModstamp'].includes(fieldName)
  ),
  canWriteField: (field: SObjectField | null | undefined, mode: 'create' | 'edit') => (
    !!field && (mode === 'create' ? !!field.createable : !!field.updateable)
  ),
  UIRegistry: {
    hasFieldRendererOverride: () => false,
    getFieldRenderer: () =>
      ({ value }: { value: unknown }) => React.createElement('span', null, String(value ?? '')),
    getFieldRendererForField: () =>
      ({ value }: { value: unknown }) => React.createElement('span', null, String(value ?? '')),
    getFieldInput: () =>
      ({
        field,
        value,
        onChange,
        disabled,
        onKeyDown,
        inputId,
        ariaInvalid,
        ariaDescribedBy,
      }: {
        field: SObjectField;
        value: unknown;
        onChange: (value: unknown) => void;
        disabled?: boolean;
        onKeyDown?: (event: React.KeyboardEvent) => void;
        inputId?: string;
        ariaInvalid?: boolean;
        ariaDescribedBy?: string;
      }) => {
        const selections = String(value ?? '').split(';').map((item) => item.trim()).filter(Boolean);
        const nextOption = fieldInputState.useToggleInput && field.type === 'multipicklist'
          ? (field.picklistValues || []).find((option) => !selections.includes(option.value))
          : (field.picklistValues || []).find((option) => option.value !== value);
        const nextValue = fieldInputState.useToggleInput && field.type === 'multipicklist' && nextOption
          ? [...selections, nextOption.value].join(';')
          : nextOption?.value ?? value;

        // Mirrors the real CRM picklist input (ui-field-components.tsx): the
        // keyboard handler lives on a focusable wrapper that `disabled` does
        // not reach, so Enter still reaches the host while the control itself
        // is disabled.
        return React.createElement(
          'div',
          {
            onKeyDown,
            tabIndex: 0,
            'data-testid': 'mock-input-wrapper',
          },
          React.createElement(
            'button',
            {
              type: 'button',
              disabled,
              id: inputId,
              'aria-invalid': ariaInvalid || undefined,
              'aria-describedby': ariaDescribedBy,
              'data-testid': 'mock-input',
              onClick: () => onChange(nextValue),
            },
            `${String(value ?? '')}|${(field.picklistValues || []).map((option) => option.value).join(',')}`,
          ),
        );
      },
  },
}));

function registerCrmFieldSurfacePolicyForTest(): void {
  clearFieldSurfacePolicyForTest();
  registerFieldSurfacePolicy(CRM_FRONTEND_APP_ID, {
    displayValueSuffix: CRM_FIELD_DISPLAY_VALUE_SUFFIX,
    lookupNameSuffix: CRM_LOOKUP_NAME_SUFFIX,
    lookupTypeSuffix: CRM_LOOKUP_TYPE_SUFFIX,
    lookupFallbackNameSuffixes: CRM_LOOKUP_FALLBACK_NAME_SUFFIXES,
    urlValueSuffix: CRM_URL_VALUE_SUFFIX,
    currencyCodeField: CRM_CURRENCY_CODE_FIELD,
    multipicklistDelimiter: CRM_MULTIPICKLIST_DELIMITER,
  });
}

function picklistField(overrides: Partial<SObjectField> & { name: string; label?: string }): SObjectField {
  return {
    name: overrides.name,
    label: overrides.label ?? overrides.name,
    type: 'picklist',
    custom: false,
    readable: true,
    createable: true,
    updateable: true,
    nillable: true,
    picklistValues: [],
    ...overrides,
  };
}

function multipicklistField(overrides: Partial<SObjectField> & { name: string; label?: string }): SObjectField {
  return {
    ...picklistField(overrides),
    type: 'multipicklist',
  };
}

// Mirrors the production CampaignMember.Status describe payload: the static
// picklistValues are the schema baseline the server rejects on save, while the
// selectable values live on CampaignMemberStatus scoped by CampaignId.
function runtimeScopedStatusField(): SObjectField {
  return picklistField({
    name: 'Status',
    label: 'Status',
    custom: true,
    controllerName: 'CampaignId',
    picklistValues: [
      { value: 'Sent', label: 'Sent', active: true, defaultValue: false },
      { value: 'Responded', label: 'Responded', active: true, defaultValue: false },
    ],
    filteredLookupInfo: {
      runtimePicklist: {
        sourceObject: 'CampaignMemberStatus',
        valueField: 'Label',
        labelField: 'Label',
        filterMappings: [{ sourceField: 'CampaignId', recordField: 'CampaignId' }],
        sortField: 'SortOrder',
        sortOrder: 'ASC',
      },
    },
  });
}

describe('InlineEditField app-pack behavior coverage', () => {
  beforeEach(() => {
    dataAPIMock.updateRecord.mockReset();
    dataAPIMock.updateRecord.mockResolvedValue(undefined);
    dataAPIMock.query.mockReset();
    dataAPIMock.query.mockResolvedValue([]);
    fieldInputState.useToggleInput = false;
    registerCrmFieldSurfacePolicyForTest();
  });

  it('applies a layout validation relationship to the actual edit control', () => {
    const website = picklistField({ name: 'Website', label: 'Website' });
    render(
      <InlineEditField
        objectApiName="Account"
        recordId="rec-validation"
        field={website}
        value=""
        record={{ Id: 'rec-validation', Website: '' }}
        objectFields={[website]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
        inputId="inline-edit-field-Website-control"
        ariaInvalid={true}
        ariaDescribedBy="inline-edit-field-Website-error"
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    expect(screen.getByTestId('mock-input')).toHaveAttribute('id', 'inline-edit-field-Website-control');
    expect(screen.getByTestId('mock-input')).toHaveAttribute('aria-invalid', 'true');
    expect(screen.getByTestId('mock-input')).toHaveAttribute('aria-describedby', 'inline-edit-field-Website-error');
  });

  it('drops stale hidden multipicklist selections before save', async () => {
    const country = picklistField({
      name: 'CountryCode',
      label: 'Country',
      picklistValues: [
        { value: 'CA', label: 'Canada', active: true, defaultValue: false },
        { value: 'CN', label: 'China', active: true, defaultValue: false },
      ],
    });
    const tags = multipicklistField({
      name: 'Tags__c',
      label: 'Tags',
      custom: true,
      controllerName: 'CountryCode',
      picklistValues: [
        { value: 'CA', label: 'California', active: true, defaultValue: false, validFor: 'gA==' },
        { value: 'BJ', label: 'Beijing', active: true, defaultValue: false, validFor: 'QA==' },
      ],
    });
    const record: SObject = { Id: 'rec-1', CountryCode: 'CN', Tags__c: 'BJ;CA' };

    render(
      <InlineEditField
        objectApiName="Account"
        recordId="rec-1"
        field={tags}
        value="BJ;CA"
        record={record}
        objectFields={[country, tags]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent).toContain('BJ|BJ');
    });

    fireEvent.click(screen.getByTitle('Save (Enter)'));

    await waitFor(() => {
      expect(dataAPIMock.updateRecord).toHaveBeenCalledWith('Account', 'rec-1', { Tags__c: 'BJ' });
    });
  });

  it('clears direct and downstream dependents when a controller field changes inline', async () => {
    const country = picklistField({
      name: 'CountryCode',
      label: 'Country',
      picklistValues: [
        { value: 'CN', label: 'China', active: true, defaultValue: false },
        { value: 'US', label: 'United States', active: true, defaultValue: false },
      ],
    });
    const province = picklistField({
      name: 'Province__c',
      label: 'Province',
      custom: true,
      controllerName: 'CountryCode',
      picklistValues: [
        { value: 'BJ', label: 'Beijing', active: true, defaultValue: false, validFor: 'gA==' },
        { value: 'CA', label: 'California', active: true, defaultValue: false, validFor: 'QA==' },
      ],
    });
    const district = picklistField({
      name: 'District__c',
      label: 'District',
      custom: true,
      controllerName: 'Province__c',
      picklistValues: [
        { value: 'Haidian', label: 'Haidian', active: true, defaultValue: false, validFor: 'gA==' },
        { value: 'SoMa', label: 'SoMa', active: true, defaultValue: false, validFor: 'QA==' },
      ],
    });
    const record: SObject = {
      Id: 'rec-controller',
      CountryCode: 'CN',
      Province__c: 'BJ',
      District__c: 'Haidian',
    };

    render(
      <InlineEditField
        objectApiName="Account"
        recordId="rec-controller"
        field={country}
        value="CN"
        record={record}
        objectFields={[country, province, district]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));
    fireEvent.click(screen.getByTestId('mock-input'));
    fireEvent.click(screen.getByTitle('Save (Enter)'));

    await waitFor(() => {
      expect(dataAPIMock.updateRecord).toHaveBeenCalledWith('Account', 'rec-controller', {
        CountryCode: 'US',
        Province__c: '',
        District__c: '',
      });
    });
  });

  it('blocks controller inline edit when an invalidated dependent field is not updateable', async () => {
    const country = picklistField({
      name: 'CountryCode',
      label: 'Country',
      picklistValues: [
        { value: 'CN', label: 'China', active: true, defaultValue: false },
        { value: 'US', label: 'United States', active: true, defaultValue: false },
      ],
    });
    const province = picklistField({
      name: 'Province__c',
      label: 'Province',
      custom: true,
      updateable: false,
      controllerName: 'CountryCode',
      picklistValues: [
        { value: 'BJ', label: 'Beijing', active: true, defaultValue: false, validFor: 'gA==' },
        { value: 'CA', label: 'California', active: true, defaultValue: false, validFor: 'QA==' },
      ],
    });

    render(
      <InlineEditField
        objectApiName="Account"
        recordId="rec-blocked"
        field={country}
        value="CN"
        record={{ Id: 'rec-blocked', CountryCode: 'CN', Province__c: 'BJ' }}
        objectFields={[country, province]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));
    fireEvent.click(screen.getByTestId('mock-input'));
    fireEvent.click(screen.getByTitle('Save (Enter)'));

    await waitFor(() => {
      expect(dataAPIMock.updateRecord).not.toHaveBeenCalled();
    });
  });

  it('blocks controller inline edit when an invalidated dependent picklist is required', async () => {
    const country = picklistField({
      name: 'CountryCode',
      label: 'Country',
      picklistValues: [
        { value: 'CN', label: 'China', active: true, defaultValue: false },
        { value: 'US', label: 'United States', active: true, defaultValue: false },
      ],
    });
    const province = picklistField({
      name: 'Province__c',
      label: 'Province',
      custom: true,
      nillable: false,
      controllerName: 'CountryCode',
      picklistValues: [
        { value: 'BJ', label: 'Beijing', active: true, defaultValue: false, validFor: 'gA==' },
        { value: 'CA', label: 'California', active: true, defaultValue: false, validFor: 'QA==' },
      ],
    });

    render(
      <InlineEditField
        objectApiName="Account"
        recordId="rec-required"
        field={country}
        value="CN"
        record={{ Id: 'rec-required', CountryCode: 'CN', Province__c: 'BJ' }}
        objectFields={[country, province]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));
    fireEvent.click(screen.getByTestId('mock-input'));
    fireEvent.click(screen.getByTitle('Save (Enter)'));

    await waitFor(() => {
      expect(dataAPIMock.updateRecord).not.toHaveBeenCalled();
    });
  });

  it('drops stale required dependent multipicklist selections after a real toggle-style replacement pick', async () => {
    fieldInputState.useToggleInput = true;
    const country = picklistField({
      name: 'CountryCode',
      label: 'Country',
      picklistValues: [
        { value: 'CN', label: 'China', active: true, defaultValue: false },
        { value: 'US', label: 'United States', active: true, defaultValue: false },
      ],
    });
    const tags = multipicklistField({
      name: 'Tags__c',
      label: 'Tags',
      custom: true,
      nillable: false,
      controllerName: 'CountryCode',
      picklistValues: [
        { value: 'CA', label: 'California', active: true, defaultValue: false, validFor: 'QA==' },
        { value: 'BJ', label: 'Beijing', active: true, defaultValue: false, validFor: 'gA==' },
      ],
    });

    render(
      <InlineEditField
        objectApiName="Account"
        recordId="rec-replacement"
        field={tags}
        value="CA"
        record={{ Id: 'rec-replacement', CountryCode: 'CN', Tags__c: 'CA' }}
        objectFields={[country, tags]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));
    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent).toBe('CA|BJ');
    });

    fireEvent.click(screen.getByTestId('mock-input'));
    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent).toBe('BJ|BJ');
    });
    fireEvent.click(screen.getByTitle('Save (Enter)'));

    await waitFor(() => {
      expect(dataAPIMock.updateRecord).toHaveBeenCalledWith('Account', 'rec-replacement', { Tags__c: 'BJ' });
    });
  });

  it('preserves free-text multipicklist values when no metadata options exist', async () => {
    const tags = multipicklistField({
      name: 'Tags__c',
      label: 'Tags',
      custom: true,
      picklistValues: [],
    });

    render(
      <InlineEditField
        objectApiName="Account"
        recordId="rec-free-text"
        field={tags}
        value="Alpha;Beta"
        record={{ Id: 'rec-free-text', Tags__c: 'Alpha;Beta' }}
        objectFields={[tags]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent).toBe('Alpha;Beta|');
    });
  });

  it('resolves runtime-scoped picklist options from the controlling field instead of the static baseline', async () => {
    dataAPIMock.query.mockResolvedValue([
      { Label: 'Registered - Virtual' },
      { Label: 'Attended - Virtual' },
    ]);

    render(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-1"
        field={runtimeScopedStatusField()}
        value="Registered - Virtual"
        record={{ Id: 'cm-1', CampaignId: 'camp-1', Status: 'Registered - Virtual' }}
        objectFields={[runtimeScopedStatusField()]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent)
        .toBe('Registered - Virtual|Registered - Virtual,Attended - Virtual');
    });

    expect(dataAPIMock.query).toHaveBeenCalledWith(expect.objectContaining({
      objectApiName: 'CampaignMemberStatus',
      criteria: [{ field: 'CampaignId', op: '=', val: 'camp-1' }],
      sortField: 'SortOrder',
      sortDirection: 'ASC',
    }));
  });

  it('keeps the stored value selectable when the resolved runtime scope no longer contains it', async () => {
    dataAPIMock.query.mockResolvedValue([{ Label: 'Attended - Virtual' }]);

    render(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-2"
        field={runtimeScopedStatusField()}
        value="Retired Status"
        record={{ Id: 'cm-2', CampaignId: 'camp-1', Status: 'Retired Status' }}
        objectFields={[runtimeScopedStatusField()]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent)
        .toBe('Retired Status|Attended - Virtual,Retired Status');
    });
  });

  it('fails closed instead of offering the static baseline when the runtime scope is unresolved', async () => {
    render(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-3"
        field={runtimeScopedStatusField()}
        value=""
        record={{ Id: 'cm-3', CampaignId: '', Status: '' }}
        objectFields={[runtimeScopedStatusField()]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    await waitFor(() => {
      expect(screen.getByTestId('unresolved-runtime-picklist-field')).toBeTruthy();
    });

    expect(screen.queryByTestId('mock-input')).toBeNull();
    expect(dataAPIMock.query).not.toHaveBeenCalled();
  });

  it('does not offer a previous scope options after the controlling field changes', async () => {
    dataAPIMock.query.mockResolvedValueOnce([{ Label: 'Campaign One Status' }]);

    const { rerender } = render(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-5"
        field={runtimeScopedStatusField()}
        value=""
        record={{ Id: 'cm-5', CampaignId: 'camp-1', Status: '' }}
        objectFields={[runtimeScopedStatusField()]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent).toBe('|Campaign One Status');
    });

    // The controlling field moves to another campaign while the editor is open.
    // The already-resolved options belong to the previous scope and must not be
    // offered; the editor re-resolves against the new scope instead.
    let resolveSecond: (rows: Record<string, unknown>[]) => void = () => {};
    dataAPIMock.query.mockReturnValueOnce(new Promise((resolve) => {
      resolveSecond = resolve;
    }));

    rerender(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-5"
        field={runtimeScopedStatusField()}
        value=""
        record={{ Id: 'cm-5', CampaignId: 'camp-2', Status: '' }}
        objectFields={[runtimeScopedStatusField()]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    expect(screen.getByTestId('mock-input').textContent).toBe('|');
    expect(screen.getByTestId('mock-input')).toHaveProperty('disabled', true);
    expect(screen.getByTitle('Save (Enter)')).toHaveProperty('disabled', true);

    resolveSecond([{ Label: 'Campaign Two Status' }]);

    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent).toBe('|Campaign Two Status');
    });
  });

  it('blocks save while a runtime-scoped picklist is unresolved', async () => {
    render(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-6"
        field={runtimeScopedStatusField()}
        value=""
        record={{ Id: 'cm-6', CampaignId: '', Status: '' }}
        objectFields={[runtimeScopedStatusField()]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    await waitFor(() => {
      expect(screen.getByTestId('unresolved-runtime-picklist-field')).toBeTruthy();
    });

    expect(screen.getByTitle('Save (Enter)')).toHaveProperty('disabled', true);

    fireEvent.click(screen.getByTitle('Save (Enter)'));
    expect(dataAPIMock.updateRecord).not.toHaveBeenCalled();
  });

  it('refuses an Enter-key save while the runtime scope is reloading', async () => {
    dataAPIMock.query.mockResolvedValueOnce([
      { Label: 'Campaign One Status' },
      { Label: 'Campaign One Other' },
    ]);

    const { rerender } = render(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-7"
        field={runtimeScopedStatusField()}
        value=""
        record={{ Id: 'cm-7', CampaignId: 'camp-1', Status: '' }}
        objectFields={[runtimeScopedStatusField()]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent)
        .toBe('|Campaign One Status,Campaign One Other');
    });

    // Pick a value that is valid only for the current scope.
    fireEvent.click(screen.getByTestId('mock-input'));
    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent).toContain('Campaign One Status|');
    });

    // The controlling field moves to another campaign before the pick is saved.
    dataAPIMock.query.mockReturnValueOnce(new Promise(() => {}));
    rerender(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-7"
        field={runtimeScopedStatusField()}
        value=""
        record={{ Id: 'cm-7', CampaignId: 'camp-2', Status: '' }}
        objectFields={[runtimeScopedStatusField()]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    // Disabling the Save button and the child control is not enough: the
    // picklist wrapper stays focusable with its own onKeyDown, so Enter still
    // reaches the component while editValue holds the previous scope's pick.
    // The save operation itself must refuse.
    expect(screen.getByTestId('mock-input')).toHaveProperty('disabled', true);
    fireEvent.keyDown(screen.getByTestId('mock-input-wrapper'), { key: 'Enter' });

    await waitFor(() => {
      expect(dataAPIMock.updateRecord).not.toHaveBeenCalled();
    });
    expect(dataAPIMock.updateRecord).not.toHaveBeenCalled();
  });

  it('discards a previous scope pick once the replacement scope resolves', async () => {
    dataAPIMock.query.mockResolvedValueOnce([
      { Label: 'Campaign One Status' },
      { Label: 'Campaign One Other' },
    ]);

    const { rerender } = render(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-10"
        field={runtimeScopedStatusField()}
        value=""
        record={{ Id: 'cm-10', CampaignId: 'camp-1', Status: '' }}
        objectFields={[runtimeScopedStatusField()]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));
    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent)
        .toBe('|Campaign One Status,Campaign One Other');
    });

    fireEvent.click(screen.getByTestId('mock-input'));
    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent).toContain('Campaign One Status|');
    });

    // Controlling field moves to another campaign; the replacement scope
    // resolves successfully, so the loading guard no longer applies.
    dataAPIMock.query.mockResolvedValueOnce([{ Label: 'Campaign Two Status' }]);
    rerender(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-10"
        field={runtimeScopedStatusField()}
        value=""
        record={{ Id: 'cm-10', CampaignId: 'camp-2', Status: '' }}
        objectFields={[runtimeScopedStatusField()]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent).toBe('|Campaign Two Status');
    });

    // The old scope's pick must not survive into the new scope's draft: the
    // value segment is empty again, and only the new scope's option is offered.
    expect(screen.getByTestId('mock-input').textContent).not.toContain('Campaign One Status');

    // Enter reaches the component through the focusable wrapper even now that
    // the replacement scope has resolved and the loading guard no longer holds.
    fireEvent.keyDown(screen.getByTestId('mock-input-wrapper'), { key: 'Enter' });

    await waitFor(() => {
      expect(dataAPIMock.updateRecord).not.toHaveBeenCalled();
    });
    expect(dataAPIMock.updateRecord).not.toHaveBeenCalled();
  });

  it('retains every stored selection of a runtime-scoped multipicklist individually', async () => {
    // The dialect supports runtime-scoped multipicklists. A stored "A;B" must be
    // retained as two options, not one serialized entry: consumers reconcile
    // selections individually, so a single "A;B" option would leave both stored
    // selections unknown and clear the field.
    const runtimeTags = multipicklistField({
      name: 'Tags__c',
      label: 'Tags',
      custom: true,
      controllerName: 'CampaignId',
      picklistValues: [],
      filteredLookupInfo: {
        runtimePicklist: {
          sourceObject: 'CampaignTag',
          valueField: 'Label',
          labelField: 'Label',
          filterMappings: [{ sourceField: 'CampaignId', recordField: 'CampaignId' }],
          sortField: 'SortOrder',
          sortOrder: 'ASC',
        },
      },
    });

    // The resolved scope offers neither stored selection any more.
    dataAPIMock.query.mockResolvedValue([{ Label: 'Still Offered' }]);

    render(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-12"
        field={runtimeTags}
        value="Retired A;Retired B"
        record={{ Id: 'cm-12', CampaignId: 'camp-1', Tags__c: 'Retired A;Retired B' }}
        objectFields={[runtimeTags]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent)
        .toBe('Retired A;Retired B|Still Offered,Retired A,Retired B');
    });

    // Reconciliation must leave the stored selections alone rather than
    // filtering them out and saving an empty value.
    fireEvent.click(screen.getByTitle('Save (Enter)'));

    await waitFor(() => {
      expect(dataAPIMock.updateRecord).not.toHaveBeenCalled();
    });
    expect(dataAPIMock.updateRecord).not.toHaveBeenCalled();
  });

  it('refuses a multipicklist write that carries retained stale selections', async () => {
    // Retained stored values are readable data, not writable ones. Adding a
    // genuinely valid selection on top of them still produces a payload the
    // server rejects, so the write must be refused rather than attempted.
    const runtimeTags = multipicklistField({
      name: 'Tags__c',
      label: 'Tags',
      custom: true,
      controllerName: 'CampaignId',
      picklistValues: [],
      filteredLookupInfo: {
        runtimePicklist: {
          sourceObject: 'CampaignTag',
          valueField: 'Label',
          labelField: 'Label',
          filterMappings: [{ sourceField: 'CampaignId', recordField: 'CampaignId' }],
          sortField: 'SortOrder',
          sortOrder: 'ASC',
        },
      },
    });

    dataAPIMock.query.mockResolvedValue([{ Label: 'Still Offered' }]);
    fieldInputState.useToggleInput = true;

    render(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-13"
        field={runtimeTags}
        value="Retired A"
        record={{ Id: 'cm-13', CampaignId: 'camp-1', Tags__c: 'Retired A' }}
        objectFields={[runtimeTags]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent)
        .toBe('Retired A|Still Offered,Retired A');
    });

    // Add the one genuinely valid option, keeping the stale stored selection.
    fireEvent.click(screen.getByTestId('mock-input'));
    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent)
        .toContain('Retired A;Still Offered|');
    });

    fireEvent.click(screen.getByTitle('Save (Enter)'));

    await waitFor(() => {
      expect(dataAPIMock.updateRecord).not.toHaveBeenCalled();
    });
    expect(dataAPIMock.updateRecord).not.toHaveBeenCalled();
  });

  it('fails closed on an empty scope even when the record holds a stored value', async () => {
    // Zero rows means nothing is selectable. Retaining the stored value keeps it
    // from being dropped out of a non-empty list; it must not make an empty
    // result look populated.
    dataAPIMock.query.mockResolvedValue([]);

    render(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-11"
        field={runtimeScopedStatusField()}
        value="Registered - Virtual"
        record={{ Id: 'cm-11', CampaignId: 'camp-1', Status: 'Registered - Virtual' }}
        objectFields={[runtimeScopedStatusField()]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    await waitFor(() => {
      expect(screen.getByTestId('unresolved-runtime-picklist-field')).toBeTruthy();
    });

    expect(screen.queryByTestId('mock-input')).toBeNull();
    expect(screen.getByTitle('Save (Enter)')).toHaveProperty('disabled', true);
  });

  it('fails closed when a runtime picklist descriptor is malformed', async () => {
    const malformed = picklistField({
      name: 'Status',
      label: 'Status',
      custom: true,
      picklistValues: [
        { value: 'Sent', label: 'Sent', active: true, defaultValue: false },
        { value: 'Responded', label: 'Responded', active: true, defaultValue: false },
      ],
      filteredLookupInfo: {
        // Declares a runtime picklist but omits the fields needed to resolve it.
        runtimePicklist: {
          sourceObject: 'CampaignMemberStatus',
          valueField: '',
          labelField: '',
          filterMappings: [],
        },
      },
    });

    render(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-8"
        field={malformed}
        value=""
        record={{ Id: 'cm-8', CampaignId: 'camp-1', Status: '' }}
        objectFields={[malformed]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    await waitFor(() => {
      expect(screen.getByTestId('unresolved-runtime-picklist-field')).toBeTruthy();
    });

    // Must not degrade to the static Sent/Responded baseline.
    expect(screen.queryByTestId('mock-input')).toBeNull();
    expect(dataAPIMock.query).not.toHaveBeenCalled();
  });

  it('re-resolves runtime options on reopen instead of reusing the closed editor cache', async () => {
    dataAPIMock.query.mockResolvedValueOnce([{ Label: 'First Resolution' }]);

    render(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-9"
        field={runtimeScopedStatusField()}
        value=""
        record={{ Id: 'cm-9', CampaignId: 'camp-1', Status: '' }}
        objectFields={[runtimeScopedStatusField()]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));
    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent).toBe('|First Resolution');
    });

    fireEvent.click(screen.getByTitle('Cancel (Escape)'));

    // Reopening must query again rather than render the previous answer.
    let resolveSecond: (rows: Record<string, unknown>[]) => void = () => {};
    dataAPIMock.query.mockReturnValueOnce(new Promise((resolve) => {
      resolveSecond = resolve;
    }));

    fireEvent.click(screen.getByTitle('Click to edit'));

    expect(screen.getByTestId('mock-input').textContent).toBe('|');
    expect(screen.getByTestId('mock-input')).toHaveProperty('disabled', true);

    resolveSecond([{ Label: 'Second Resolution' }]);
    await waitFor(() => {
      expect(screen.getByTestId('mock-input').textContent).toBe('|Second Resolution');
    });
    expect(dataAPIMock.query).toHaveBeenCalledTimes(2);
  });

  it('fails closed when the runtime-scoped option query fails', async () => {
    dataAPIMock.query.mockRejectedValue(new Error('network down'));

    render(
      <InlineEditField
        objectApiName="CampaignMember"
        recordId="cm-4"
        field={runtimeScopedStatusField()}
        value="Registered - Virtual"
        record={{ Id: 'cm-4', CampaignId: 'camp-1', Status: 'Registered - Virtual' }}
        objectFields={[runtimeScopedStatusField()]}
        policyScope={CRM_FRONTEND_APP_ID}
        isEditable={true}
      />,
    );

    fireEvent.click(screen.getByTitle('Click to edit'));

    await waitFor(() => {
      expect(screen.getByTestId('unresolved-runtime-picklist-field')).toBeTruthy();
    });

    expect(screen.queryByTestId('mock-input')).toBeNull();
  });
});

describe('runtime-scoped picklist scope resolution', () => {
  it('refuses to build unscoped criteria when no filter mappings are declared', () => {
    // Empty mappings must not degrade to an unfiltered query returning every
    // row of the source object — that is the absence of a scope, not a wider one.
    expect(buildRuntimePicklistCriteria(
      {
        sourceObject: 'CampaignMemberStatus',
        valueField: 'Label',
        labelField: 'Label',
        filterMappings: [],
      },
      { CampaignId: 'camp-1' },
    )).toBeNull();
  });

  it('refuses to build criteria when a controlling value is blank', () => {
    expect(buildRuntimePicklistCriteria(
      {
        sourceObject: 'CampaignMemberStatus',
        valueField: 'Label',
        labelField: 'Label',
        filterMappings: [{ sourceField: 'CampaignId', recordField: 'CampaignId' }],
      },
      { CampaignId: '   ' },
    )).toBeNull();
  });
});
