import type { OnlyOfficeBridge } from '../../src/onlyoffice/bridge';

import { createPinia, setActivePinia } from 'pinia';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import {
  appendRepeatRow,
  deleteRepeatRow as requestDeleteRepeatRow,
  fetchFillData,
  previewSourceSync as requestSourceSyncPreview,
  restoreRepeatRow as requestRestoreRepeatRow,
  synchronizeSource as requestSourceSync,
} from '../../src/api/record-fill';
import { useRecordFiller } from '../../src/composables/useRecordFiller';

vi.mock('../../src/api/record-fill', () => ({
  appendRepeatRow: vi.fn(),
  deleteRepeatRow: vi.fn(),
  fetchFillData: vi.fn(),
  previewSourceSync: vi.fn(),
  restoreRepeatRow: vi.fn(),
  synchronizeSource: vi.fn(),
}));

const context = {
  apiBaseUrl: 'http://localhost:30000',
  bizDataId: 'record-1',
  bizModule: 'eln.record',
  bizScene: 'eln.record.fill',
  canFill: true,
  documentId: 'file-1',
  pluginVersion: '0.1.0',
  scope: 'onlyoffice:eln-record-fill',
  ticket: 'opaque-ticket',
};

function fixture(overrides: Partial<OnlyOfficeBridge> = {}) {
  let ready = () => undefined;
  let changed = (_control: { InternalId?: unknown; Tag?: unknown }) => undefined;
  let focused = (_control: { InternalId?: unknown; Tag?: unknown }) => undefined;
  const bridge: OnlyOfficeBridge = {
    applyFields: vi.fn().mockResolvedValue([]),
    appendRepeatRow: vi.fn().mockResolvedValue([]),
    bindLifecycle: vi.fn((callbacks) => {
      changed = callbacks.onControlChanged;
      focused = callbacks.onControlFocused;
      ready = callbacks.onReady;
      return vi.fn();
    }),
    getRuntimeOptions: vi.fn(() => context),
    inspectFields: vi.fn().mockResolvedValue([{ alias: '名称', currentValue: '', internalId: '1', options: [], tag: 'eln.field.name', type: 'text' }]),
    removeRepeatRow: vi.fn().mockResolvedValue([]),
    selectControl: vi.fn().mockResolvedValue(undefined),
    setFillOnlyMode: vi.fn().mockResolvedValue(undefined),
    ...overrides,
  };
  const filler = useRecordFiller(bridge);
  return {
    bridge,
    changed: (control: { InternalId?: unknown; Tag?: unknown }) => changed(control),
    filler,
    focused: (control: { InternalId?: unknown; Tag?: unknown }) => focused(control),
    ready: () => ready(),
  };
}

describe('useRecordFiller', () => {
  beforeEach(() => {
    setActivePinia(createPinia());
    vi.mocked(fetchFillData).mockReset().mockResolvedValue({ fields: {}, repeatGroups: [] });
    vi.mocked(appendRepeatRow).mockReset();
    vi.mocked(requestDeleteRepeatRow).mockReset();
    vi.mocked(requestRestoreRepeatRow).mockReset();
    vi.mocked(requestSourceSyncPreview).mockReset();
    vi.mocked(requestSourceSync).mockReset();
  });

  it('enables fill-only mode before inspecting a writable record document', async () => {
    let fillOnlyMode = false;
    const setFillOnlyMode = vi.fn(async (enabled: boolean) => {
      fillOnlyMode = enabled;
    });
    const inspectFields = vi.fn(async () => {
      expect(fillOnlyMode).toBe(true);
      return [];
    });
    const { filler, ready } = fixture({
      getRuntimeOptions: () => ({
        ...context,
        bizModule: 'eln.record', bizScene: 'eln.record.fill', scope: 'onlyoffice:eln-record-fill',
      }),
      inspectFields,
      setFillOnlyMode,
    });

    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    expect(setFillOnlyMode).toHaveBeenCalledWith(true);
    expect(inspectFields).toHaveBeenCalledOnce();
  });

  it('restores a persisted pending source synchronization and awaits document save', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.source': { readOnly: true, type: 'text', value: 'GYDW', valueSource: 'CROSS_TEMPLATE' },
      },
      pendingSourceSync: {
        changedCount: 1, changedFieldCodes: ['source'], differences: [],
        operationCode: 'source-sync-pending', recalculatedCount: 0,
        recordId: 'record-1', recordVersion: 5, revisionId: 'revision-1',
      },
      repeatGroups: [],
    });
    const applyFields = vi.fn().mockImplementation(async (changes) =>
      changes.map((change: { tag: string }) => ({ ok: true, tag: change.tag })));
    const { filler, ready } = fixture({
      applyFields,
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '来源', currentValue: '', internalId: '1', options: [], tag: 'eln.field.source', type: 'text' },
      ]),
    });

    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    expect(applyFields).toHaveBeenCalledWith([
      { tag: 'eln.field.source', type: 'text', value: 'GYDW' },
    ]);
    expect(filler.store.message).toBe('来源同步已恢复，等待文档保存确认');
  });

  it('keeps a pending source synchronization when document recovery is partial', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.source': { readOnly: true, type: 'text', value: 'GYDW', valueSource: 'CROSS_TEMPLATE' },
      },
      pendingSourceSync: {
        changedCount: 1, changedFieldCodes: ['source'], differences: [],
        operationCode: 'source-sync-pending', recalculatedCount: 0,
        recordId: 'record-1', recordVersion: 5, revisionId: 'revision-1',
      },
      repeatGroups: [],
    });
    const { filler, ready } = fixture({
      applyFields: vi.fn().mockResolvedValue([{ ok: false, reason: '写入失败', tag: 'eln.field.source' }]),
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '来源', currentValue: '', internalId: '1', options: [], tag: 'eln.field.source', type: 'text' },
      ]),
    });

    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('error'));

    expect(filler.store.message).toBe('数据库当前态恢复到文档失败');
  });

  it('synchronizes only changed source fields without discarding other drafts', async () => {
    vi.mocked(fetchFillData)
      .mockResolvedValueOnce({
        fields: {
          'eln.field.formula_result': { formulaStatus: 'SUCCESS', readOnly: true, type: 'text', value: '', valueSource: 'FORMULA' },
          'eln.field.operator_note': { type: 'text', value: '' },
          'eln.field.source': { readOnly: true, type: 'text', value: '', valueSource: 'CROSS_TEMPLATE' },
        },
        repeatGroups: [],
      })
      .mockResolvedValueOnce({
        fields: {
          'eln.field.formula_result': { formulaStatus: 'SUCCESS', readOnly: true, type: 'text', value: '42', valueSource: 'FORMULA' },
          'eln.field.operator_note': { type: 'text', value: '' },
          'eln.field.source': { readOnly: true, type: 'text', value: 'GYDW', valueSource: 'CROSS_TEMPLATE' },
        },
        repeatGroups: [],
      });
    vi.mocked(requestSourceSyncPreview).mockResolvedValue({
      changedCount: 1,
      differences: [{
        bindingId: 'binding-source', changed: true, currentValue: 'GYDW', dataType: 'TEXT',
        fieldCode: 'source', fieldName: '来源', previousValue: null,
        sourceProperty: null, sourceRecordId: 'source-record-1',
        sourceRevisionId: 'source-revision-2', sourceType: 'CROSS_TEMPLATE',
      }],
      previewToken: 'preview-token', recordId: 'record-1', recordVersion: 4, revisionId: 'revision-1',
    });
    vi.mocked(requestSourceSync).mockResolvedValue({
      changedCount: 1, changedFieldCodes: ['source', 'formula_result'],
      differences: [], operationCode: 'source-sync-1', recalculatedCount: 0,
      recordId: 'record-1', recordVersion: 5, revisionId: 'revision-1',
    });
    const applyFields = vi.fn().mockImplementation(async (changes) =>
      changes.map((change: { tag: string }) => ({ ok: true, tag: change.tag })));
    const { filler, ready } = fixture({
      applyFields,
      getRuntimeOptions: () => ({
        ...context,
        bizModule: 'eln.record', bizScene: 'eln.record.fill', scope: 'onlyoffice:eln-record-fill',
      }),
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '来源', currentValue: '', internalId: '1', options: [], tag: 'eln.field.source', type: 'text' },
        { alias: '来源引用', currentValue: '', internalId: '1-ref', options: [], tag: 'eln.ref.source.instance_1', type: 'text' },
        { alias: '公式结果', currentValue: '', internalId: '3', options: [], tag: 'eln.field.formula_result', type: 'text' },
        { alias: '人工备注', currentValue: '', internalId: '2', options: [], tag: 'eln.field.operator_note', type: 'text' },
      ]),
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));
    filler.store.setDraft('eln.field.operator_note', '人工草稿');

    await filler.previewSourceSync();
    expect(filler.store.message).toBe('来源快照差异已加载');
    await filler.synchronizeSource();

    expect(requestSourceSync).toHaveBeenCalledWith(expect.objectContaining({ ticket: 'opaque-ticket' }), {
      expectedRecordVersion: 4,
      operationCode: expect.stringMatching(/^source-sync-/u),
      previewToken: 'preview-token',
    });
    expect(applyFields).toHaveBeenCalledTimes(1);
    expect(applyFields).toHaveBeenCalledWith([
      { tag: 'eln.field.source', type: 'text', value: 'GYDW' },
      { tag: 'eln.ref.source.instance_1', type: 'text', value: 'GYDW' },
      { tag: 'eln.field.formula_result', type: 'text', value: '42' },
    ]);
    expect(filler.store.formulaResultsStale).toBe(false);
    expect(filler.store.message).toBe('来源快照已写入文档，等待保存确认');
    expect(filler.store.fields.find((field) => field.tag === 'eln.field.source')).toMatchObject({
      documentValue: 'GYDW', draftValue: 'GYDW',
    });
    expect(filler.store.fields.find((field) => field.tag === 'eln.field.operator_note')).toMatchObject({
      documentValue: '', draftValue: '人工草稿',
    });
    expect(filler.store.fields.find((field) => field.tag === 'eln.field.source')).toMatchObject({
      readOnly: true, valueSource: 'CROSS_TEMPLATE',
    });
  });

  it('retries document refresh with the same operation after source values were synchronized', async () => {
    const before = {
      fields: {
        'eln.field.source': { readOnly: true, type: 'text' as const, value: '', valueSource: 'CROSS_TEMPLATE' },
      },
      repeatGroups: [],
    };
    const after = {
      fields: {
        'eln.field.source': { readOnly: true, type: 'text' as const, value: 'GYDW', valueSource: 'CROSS_TEMPLATE' },
      },
      repeatGroups: [],
    };
    vi.mocked(fetchFillData)
      .mockResolvedValueOnce(before)
      .mockResolvedValueOnce(after)
      .mockResolvedValueOnce(after);
    vi.mocked(requestSourceSyncPreview).mockResolvedValue({
      changedCount: 1,
      differences: [{
        bindingId: 'binding-source', changed: true, currentValue: 'GYDW', dataType: 'TEXT',
        fieldCode: 'source', fieldName: '来源', previousValue: null,
        sourceProperty: null, sourceRecordId: 'source-record-1',
        sourceRevisionId: 'source-revision-2', sourceType: 'CROSS_TEMPLATE',
      }],
      previewToken: 'preview-token', recordId: 'record-1', recordVersion: 4, revisionId: 'revision-1',
    });
    vi.mocked(requestSourceSync).mockResolvedValue({
      changedCount: 1, changedFieldCodes: ['source'], differences: [],
      operationCode: 'source-sync-1', recalculatedCount: 0,
      recordId: 'record-1', recordVersion: 5, revisionId: 'revision-1',
    });
    const applyFields = vi.fn()
      .mockRejectedValueOnce(new Error('文档写入失败'))
      .mockImplementationOnce(async (changes) => changes.map((change: { tag: string }) => ({ ok: true, tag: change.tag })));
    const { filler, ready } = fixture({
      applyFields,
      getRuntimeOptions: () => ({
        ...context,
        bizModule: 'eln.record', bizScene: 'eln.record.fill', scope: 'onlyoffice:eln-record-fill',
      }),
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '来源', currentValue: '', internalId: '1', options: [], tag: 'eln.field.source', type: 'text' },
      ]),
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));
    await filler.previewSourceSync();

    await filler.synchronizeSource();
    expect(filler.sourceSyncPreview.value).not.toBeNull();
    await filler.synchronizeSource();

    expect(requestSourceSync).toHaveBeenCalledTimes(2);
    expect(vi.mocked(requestSourceSync).mock.calls[1]?.[1].operationCode)
      .toBe(vi.mocked(requestSourceSync).mock.calls[0]?.[1].operationCode);
    expect(applyFields).toHaveBeenCalledTimes(2);
    expect(filler.sourceSyncPreview.value).toBeNull();
    expect(filler.store.message).toBe('来源快照已写入文档，等待保存确认');
  });

  it.each([401, 403])('invalidates the fill context when source sync preview returns %s', async (status) => {
    const error = Object.assign(new Error(status === 401 ? '填写凭证已过期，请重新打开文档' : '任务快照同步失败'), { status });
    vi.mocked(requestSourceSyncPreview).mockRejectedValue(error);
    const { filler, ready } = fixture({
      getRuntimeOptions: () => ({
        ...context,
        bizModule: 'eln.record', bizScene: 'eln.record.fill', scope: 'onlyoffice:eln-record-fill',
      }),
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    await filler.previewSourceSync();
    await filler.previewSourceSync();

    expect(filler.store.status).toBe('error');
    expect(filler.store.message).toBe(error.message);
    expect(filler.canFill()).toBe(false);
    expect(filler.canSynchronizeSource.value).toBe(false);
    expect(requestSourceSyncPreview).toHaveBeenCalledOnce();
  });

  it('enters an error state when SDK initialization times out', async () => {
    vi.useFakeTimers();
    const { filler } = fixture();

    await vi.advanceTimersByTimeAsync(5_000);

    expect(filler.store.status).toBe('error');
    expect(filler.store.message).toBe('ONLYOFFICE SDK 初始化超时');
    vi.useRealTimers();
  });

  it('reports an apply failure instead of remaining in working state', async () => {
    const applyFields = vi.fn().mockRejectedValue(new Error('文档写入失败'));
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {},
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: false,
          steps: [{ id: 'step-fill', name: '填写', order: 1 }],
        },
        controlSteps: { 'eln.field.name': 'step-fill' },
        repeatGroupSteps: {},
      },
    });
    const { filler, ready } = fixture({ applyFields });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));
    filler.store.setDraft('eln.field.name', '样品');

    await filler.apply();

    expect(filler.store.status).toBe('error');
    expect(filler.store.message).toBe('文档写入失败');
    expect(filler.store.fieldErrors).toEqual({ 'eln.field.name': '文档写入失败' });
    expect(filler.stepProgress.value[0]?.status).toBe('problem');
  });

  it('does not apply changes when canFill is false', async () => {
    const applyFields = vi.fn().mockResolvedValue([]);
    const { filler, ready } = fixture({ applyFields, getRuntimeOptions: () => ({ ...context, canFill: false }) });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));
    filler.store.setDraft('eln.field.name', '样品');

    await filler.apply();

    expect(applyFields).not.toHaveBeenCalled();
    expect(filler.store.message).toBe('当前记录仅可查看');
  });

  it('applies the selected checkbox and only changed peers from its runtime group', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.already_clear': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: false },
        'eln.field.failed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
        'eln.field.passed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: false },
      },
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: true,
          steps: [{ id: 'step-1', name: '检验', order: 1 }],
        },
        controlSteps: {
          'eln.field.already_clear': 'step-1',
          'eln.field.failed': 'step-1',
          'eln.field.passed': 'step-1',
        },
        repeatGroupSteps: {},
      },
    });
    const applyFields = vi.fn().mockImplementation(async (changes) =>
      changes.map((change: { tag: string }) => ({ ok: true, tag: change.tag })));
    const { filler, ready } = fixture({
      applyFields,
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '合格', currentValue: false, internalId: '1', options: [], tag: 'eln.field.passed', type: 'checkbox' },
        { alias: '不合格', currentValue: true, internalId: '2', options: [], tag: 'eln.field.failed', type: 'checkbox' },
        { alias: '已清空', currentValue: false, internalId: '3', options: [], tag: 'eln.field.already_clear', type: 'checkbox' },
      ]),
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    filler.store.setDraft('eln.field.passed', true);
    await filler.apply();

    expect(applyFields).toHaveBeenCalledOnce();
    expect(applyFields).toHaveBeenCalledWith([
      { tag: 'eln.field.passed', type: 'checkbox', value: true },
      { tag: 'eln.field.failed', type: 'checkbox', value: false },
    ]);
  });

  it('switches the visible step without discarding cross-step drafts', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {},
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: false,
          steps: [
            { id: 'step-prepare', name: '准备', order: 1 },
            { id: 'step-test', name: '检验', order: 2 },
          ],
        },
        controlSteps: {
          'eln.field.sample': 'step-prepare',
          'eln.field.result': 'step-test',
        },
        repeatGroupSteps: {},
      },
    });
    const fields = [
      { alias: '样品', currentValue: '', internalId: '1', options: [], tag: 'eln.field.sample', type: 'text' as const },
      { alias: '结果', currentValue: '', internalId: '2', options: [], tag: 'eln.field.result', type: 'text' as const },
    ];
    const applyFields = vi.fn().mockImplementation(async (changes) => changes.map((change: { tag: string }) => ({ ok: true, tag: change.tag })));
    const { filler, ready } = fixture({ applyFields, inspectFields: vi.fn().mockResolvedValue(fields) });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    expect(filler.isMultiStep.value).toBe(true);
    expect(filler.currentStepId.value).toBe('step-prepare');
    expect(filler.visibleFields.value.map((field) => field.tag)).toEqual(['eln.field.sample']);
    filler.store.setDraft('eln.field.sample', 'S-001');

    filler.selectStep('step-test');
    expect(filler.visibleFields.value.map((field) => field.tag)).toEqual(['eln.field.result']);
    filler.store.setDraft('eln.field.result', '合格');
    await filler.apply();

    expect(applyFields).toHaveBeenCalledWith([
      { tag: 'eln.field.sample', type: 'text', value: 'S-001' },
      { tag: 'eln.field.result', type: 'text', value: '合格' },
    ]);
  });

  it('assigns dotted primary field codes and their references to the same step', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {},
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: false,
          steps: [
            { id: 'step-test', name: '检验', order: 1 },
            { id: 'step-review', name: '复核', order: 2 },
          ],
        },
        controlSteps: {
          'eln.field.inspection.result': 'step-test',
          'eln.field.review.conclusion': 'step-review',
        },
        repeatGroupSteps: {},
      },
    });
    const { filler, ready } = fixture({
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '检验结果', currentValue: '', internalId: '1', options: [], tag: 'eln.field.inspection.result', type: 'text' },
        { alias: '检验结果引用', currentValue: '', internalId: '2', options: [], tag: 'eln.ref.inspection.result.instance_1', type: 'text' },
        { alias: '复核结论', currentValue: '', internalId: '3', options: [], tag: 'eln.field.review.conclusion', type: 'text' },
      ]),
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    expect(filler.visibleFields.value.map((field) => field.tag)).toEqual([
      'eln.field.inspection.result',
      'eln.ref.inspection.result.instance_1',
    ]);
    filler.selectStep('step-review');
    expect(filler.visibleFields.value.map((field) => field.tag)).toEqual(['eln.field.review.conclusion']);
  });

  it('derives each step status from required drafts, repeat minimums, and field errors', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.sample': { required: true, type: 'text', value: '' },
      },
      repeatGroups: [{ activeRowCount: 0, groupCode: 'results', maxRows: 3, minRows: 1, rows: [] }],
      stepStructure: {
        configuration: {
          singleStep: false,
          steps: [
            { id: 'step-prepare', name: '准备', order: 1 },
            { id: 'step-test', name: '检验', order: 2 },
          ],
        },
        controlSteps: { 'eln.field.sample': 'step-prepare' },
        repeatGroupSteps: { results: 'step-test' },
      },
    });
    const { filler, ready } = fixture({
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '样品', currentValue: '', internalId: '1', options: [], tag: 'eln.field.sample', type: 'text' },
      ]),
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    expect(filler.stepProgress.value.map(({ status }) => status)).toEqual(['in-progress', 'in-progress']);
    filler.store.setDraft('eln.field.sample', 'S-001');
    expect(filler.stepProgress.value[0]?.status).toBe('complete');
    filler.store.fieldErrors['eln.field.sample'] = '值不符合选项';
    expect(filler.stepProgress.value[0]?.status).toBe('problem');
    delete filler.store.fieldErrors['eln.field.sample'];
    filler.store.formulaResultsStale = false;
    filler.store.fields[0]!.formulaStatus = 'ERROR';
    expect(filler.stepProgress.value[0]?.status).toBe('problem');
    filler.store.fields[0]!.formulaStatus = 'MISSING_INPUT';
    expect(filler.stepProgress.value[0]?.status).toBe('in-progress');
    filler.store.fields[0]!.formulaStatus = 'SUCCESS';
    expect(filler.stepProgress.value[0]?.status).toBe('complete');
  });

  it('marks loaded formula results stale after any editable draft changes', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.input': { required: true, type: 'text', value: '2' },
        'eln.field.result': { formulaStatus: 'SUCCESS', required: true, type: 'text', value: '4' },
      },
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: false,
          steps: [
            { id: 'step-input', name: '录入', order: 1 },
            { id: 'step-result', name: '计算', order: 2 },
          ],
        },
        controlSteps: {
          'eln.field.input': 'step-input',
          'eln.field.result': 'step-result',
        },
        repeatGroupSteps: {},
      },
    });
    const { filler, ready } = fixture({
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '输入', currentValue: '2', internalId: '1', options: [], tag: 'eln.field.input', type: 'text' },
        { alias: '结果', currentValue: '4', internalId: '2', options: [], tag: 'eln.field.result', type: 'text' },
      ]),
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    expect(filler.stepProgress.value.map(({ status }) => status)).toEqual(['complete', 'complete']);
    filler.store.setDraft('eln.field.input', '3');

    expect(filler.stepProgress.value.map(({ status }) => status)).toEqual(['complete', 'in-progress']);
  });

  it('absorbs an uncontested controlled-document edit into the sidebar draft', async () => {
    const initial = { alias: '结果', currentValue: '原值', internalId: '1_2', options: [], tag: 'eln.field.result', type: 'text' as const };
    const inspectFields = vi.fn()
      .mockResolvedValueOnce([initial])
      .mockResolvedValueOnce([{ ...initial, currentValue: '文档值' }]);
    const { changed, filler, ready } = fixture({ inspectFields });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    changed({ InternalId: '1_2', Tag: 'eln.field.result' });
    await vi.waitFor(() => expect(filler.store.fields[0]?.draftValue).toBe('文档值'));

    expect(filler.store.fields[0]?.documentValue).toBe('文档值');
    expect(filler.store.conflicts).toEqual({});
  });

  it('cancels the selected peer after a grouped checkbox is checked in the document', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.failed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
        'eln.field.passed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: false },
      },
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: true,
          steps: [{ id: 'step-1', name: '检验', order: 1 }],
        },
        controlSteps: {
          'eln.field.failed': 'step-1',
          'eln.field.passed': 'step-1',
        },
        repeatGroupSteps: {},
      },
    });
    const initial = [
      { alias: '合格', currentValue: false, internalId: 'passed', options: [], tag: 'eln.field.passed', type: 'checkbox' as const },
      { alias: '不合格', currentValue: true, internalId: 'failed', options: [], tag: 'eln.field.failed', type: 'checkbox' as const },
    ];
    const inspectFields = vi.fn()
      .mockResolvedValueOnce(initial)
      .mockResolvedValueOnce([{ ...initial[0]!, currentValue: true }, initial[1]!]);
    const applyFields = vi.fn().mockImplementation(async (changes) =>
      changes.map((change: { tag: string }) => ({ ok: true, tag: change.tag })));
    const { changed, filler, ready } = fixture({ applyFields, inspectFields });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    changed({ InternalId: 'passed', Tag: 'eln.field.passed' });

    await vi.waitFor(() => expect(applyFields).toHaveBeenCalledOnce());
    expect(applyFields).toHaveBeenCalledWith([
      { tag: 'eln.field.failed', type: 'checkbox', value: false },
    ]);
    expect(filler.store.fields.map((field) => field.draftValue)).toEqual([true, false]);
    expect(filler.store.fields.map((field) => field.documentValue)).toEqual([true, false]);
    expect(filler.store.documentSyncError).toBe('');
  });

  it('keeps existing selections when a direct document check stays within maxSelected', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.failed': {
          checkboxGroupCode: 'inspection_result', checkboxGroupMaxSelected: 2, checkboxGroupMinSelected: 0, type: 'checkbox', value: false,
        },
        'eln.field.passed': {
          checkboxGroupCode: 'inspection_result', checkboxGroupMaxSelected: 2, checkboxGroupMinSelected: 0, type: 'checkbox', value: true,
        },
      },
      repeatGroups: [],
    });
    const initial = [
      { alias: '合格', currentValue: true, internalId: 'passed', options: [], tag: 'eln.field.passed', type: 'checkbox' as const },
      { alias: '不合格', currentValue: false, internalId: 'failed', options: [], tag: 'eln.field.failed', type: 'checkbox' as const },
    ];
    const inspectFields = vi.fn()
      .mockResolvedValueOnce(initial)
      .mockResolvedValueOnce([initial[0]!, { ...initial[1]!, currentValue: true }]);
    const applyFields = vi.fn().mockResolvedValue([]);
    const { changed, filler, ready } = fixture({ applyFields, inspectFields });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    changed({ InternalId: 'failed', Tag: 'eln.field.failed' });
    await vi.waitFor(() => expect(filler.store.fields[1]?.documentValue).toBe(true));

    expect(applyFields).not.toHaveBeenCalled();
    expect(filler.store.fields.map((field) => field.draftValue)).toEqual([true, true]);
  });

  it('writes back a direct document check that exceeds maxSelected', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.failed': {
          checkboxGroupCode: 'inspection_result', checkboxGroupMaxSelected: 2, checkboxGroupMinSelected: 0, type: 'checkbox', value: true,
        },
        'eln.field.passed': {
          checkboxGroupCode: 'inspection_result', checkboxGroupMaxSelected: 2, checkboxGroupMinSelected: 0, type: 'checkbox', value: true,
        },
        'eln.field.reviewed': {
          checkboxGroupCode: 'inspection_result', checkboxGroupMaxSelected: 2, checkboxGroupMinSelected: 0, type: 'checkbox', value: false,
        },
      },
      repeatGroups: [],
    });
    const initial = [
      { alias: '合格', currentValue: true, internalId: 'passed', options: [], tag: 'eln.field.passed', type: 'checkbox' as const },
      { alias: '不合格', currentValue: true, internalId: 'failed', options: [], tag: 'eln.field.failed', type: 'checkbox' as const },
      { alias: '复核', currentValue: false, internalId: 'reviewed', options: [], tag: 'eln.field.reviewed', type: 'checkbox' as const },
    ];
    const inspectFields = vi.fn()
      .mockResolvedValueOnce(initial)
      .mockResolvedValueOnce([initial[0]!, initial[1]!, { ...initial[2]!, currentValue: true }]);
    const applyFields = vi.fn().mockImplementation(async (changes) =>
      changes.map((change: { tag: string }) => ({ ok: true, tag: change.tag })));
    const { changed, filler, ready } = fixture({ applyFields, inspectFields });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    changed({ InternalId: 'reviewed', Tag: 'eln.field.reviewed' });
    await vi.waitFor(() => expect(applyFields).toHaveBeenCalledOnce());

    expect(applyFields).toHaveBeenCalledWith([
      { tag: 'eln.field.reviewed', type: 'checkbox', value: false },
    ]);
    expect(filler.store.fields.map((field) => field.draftValue)).toEqual([true, true, false]);
    expect(filler.store.checkboxGroupNotice).toBe('选择组“inspection_result”最多选择 2 项');
  });

  it('reports an ambiguous grouped checkbox state on initialization and blocks apply', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.failed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
        'eln.field.passed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
      },
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: true,
          steps: [{ id: 'step-1', name: '检验', order: 1 }],
        },
        controlSteps: {
          'eln.field.failed': 'step-1',
          'eln.field.passed': 'step-1',
        },
        repeatGroupSteps: {},
      },
    });
    const applyFields = vi.fn().mockResolvedValue([]);
    const { filler, ready } = fixture({
      applyFields,
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '合格', currentValue: false, internalId: 'passed', options: [], tag: 'eln.field.passed', type: 'checkbox' },
        { alias: '不合格', currentValue: false, internalId: 'failed', options: [], tag: 'eln.field.failed', type: 'checkbox' },
      ]),
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    expect(filler.store.checkboxGroupError).toBe('选择组“inspection_result”最多选择 1 项');
    await filler.apply();
    expect(applyFields).not.toHaveBeenCalled();
  });

  it('rejects an ambiguous document checkbox group before restoring persisted values', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.failed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: false },
        'eln.field.passed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
      },
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: true,
          steps: [{ id: 'step-1', name: '检验', order: 1 }],
        },
        controlSteps: {
          'eln.field.failed': 'step-1',
          'eln.field.passed': 'step-1',
        },
        repeatGroupSteps: {},
      },
    });
    const applyFields = vi.fn().mockResolvedValue([]);
    const { filler, ready } = fixture({
      applyFields,
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '合格', currentValue: true, internalId: 'passed', options: [], tag: 'eln.field.passed', type: 'checkbox' },
        { alias: '不合格', currentValue: true, internalId: 'failed', options: [], tag: 'eln.field.failed', type: 'checkbox' },
      ]),
    });

    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    expect(filler.store.checkboxGroupError).toBe('选择组“inspection_result”最多选择 1 项');
    expect(filler.store.fields.map((field) => field.draftValue)).toEqual([true, true]);
    expect(applyFields).not.toHaveBeenCalled();
  });

  it('blocks repeat-row operations while a checkbox group is ambiguous', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.failed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
        'eln.field.passed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
      },
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: true,
          steps: [{ id: 'step-1', name: '检验', order: 1 }],
        },
        controlSteps: {
          'eln.field.failed': 'step-1',
          'eln.field.passed': 'step-1',
        },
        repeatGroupSteps: {},
      },
    });
    const applyFields = vi.fn().mockResolvedValue([]);
    const { filler, ready } = fixture({
      applyFields,
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '合格', currentValue: false, internalId: 'passed', options: [], tag: 'eln.field.passed', type: 'checkbox' },
        { alias: '不合格', currentValue: false, internalId: 'failed', options: [], tag: 'eln.field.failed', type: 'checkbox' },
      ]),
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    await filler.appendRepeatRow('results');

    expect(applyFields).not.toHaveBeenCalled();
    expect(appendRepeatRow).not.toHaveBeenCalled();
    expect(filler.store.repeatError).toBe('请先处理选择组错误');
  });

  it('re-reads the document and keeps a field error when automatic deselection fails', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.failed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
        'eln.field.passed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: false },
      },
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: true,
          steps: [{ id: 'step-1', name: '检验', order: 1 }],
        },
        controlSteps: {
          'eln.field.failed': 'step-1',
          'eln.field.passed': 'step-1',
        },
        repeatGroupSteps: {},
      },
    });
    const initial = [
      { alias: '合格', currentValue: false, internalId: 'passed', options: [], tag: 'eln.field.passed', type: 'checkbox' as const },
      { alias: '不合格', currentValue: true, internalId: 'failed', options: [], tag: 'eln.field.failed', type: 'checkbox' as const },
    ];
    const invalidDocument = [{ ...initial[0]!, currentValue: true }, initial[1]!];
    const inspectFields = vi.fn()
      .mockResolvedValueOnce(initial)
      .mockResolvedValueOnce(invalidDocument)
      .mockResolvedValueOnce(invalidDocument);
    const applyFields = vi.fn().mockResolvedValue([
      { error: '自动取消失败', ok: false, tag: 'eln.field.failed' },
    ]);
    const { changed, filler, ready } = fixture({ applyFields, inspectFields });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    changed({ InternalId: 'passed', Tag: 'eln.field.passed' });

    await vi.waitFor(() => expect(inspectFields).toHaveBeenCalledTimes(3));
    expect(filler.store.fieldErrors).toEqual({ 'eln.field.failed': '自动取消失败' });
    expect(filler.store.fields[1]).toMatchObject({
      currentValue: true,
      documentValue: true,
      draftValue: false,
    });
  });

  it('enforces independently selected checkbox groups from one debounced document read', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.failed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
        'eln.field.no': { checkboxGroupCode: 'confirmation', type: 'checkbox', value: true },
        'eln.field.passed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: false },
        'eln.field.yes': { checkboxGroupCode: 'confirmation', type: 'checkbox', value: false },
      },
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: true,
          steps: [{ id: 'step-1', name: '检验', order: 1 }],
        },
        controlSteps: {
          'eln.field.failed': 'step-1',
          'eln.field.no': 'step-1',
          'eln.field.passed': 'step-1',
          'eln.field.yes': 'step-1',
        },
        repeatGroupSteps: {},
      },
    });
    const initial = [
      { alias: '合格', currentValue: false, internalId: 'passed', options: [], tag: 'eln.field.passed', type: 'checkbox' as const },
      { alias: '不合格', currentValue: true, internalId: 'failed', options: [], tag: 'eln.field.failed', type: 'checkbox' as const },
      { alias: '是', currentValue: false, internalId: 'yes', options: [], tag: 'eln.field.yes', type: 'checkbox' as const },
      { alias: '否', currentValue: true, internalId: 'no', options: [], tag: 'eln.field.no', type: 'checkbox' as const },
    ];
    const inspectFields = vi.fn()
      .mockResolvedValueOnce(initial)
      .mockResolvedValueOnce([
        { ...initial[0]!, currentValue: true },
        initial[1]!,
        { ...initial[2]!, currentValue: true },
        initial[3]!,
      ]);
    const applyFields = vi.fn().mockImplementation(async (changes) =>
      changes.map((change: { tag: string }) => ({ ok: true, tag: change.tag })));
    const { changed, filler, ready } = fixture({ applyFields, inspectFields });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    changed({ InternalId: 'passed', Tag: 'eln.field.passed' });
    changed({ InternalId: 'yes', Tag: 'eln.field.yes' });

    await vi.waitFor(() => expect(applyFields).toHaveBeenCalledOnce());
    expect(applyFields).toHaveBeenCalledWith([
      { tag: 'eln.field.failed', type: 'checkbox', value: false },
      { tag: 'eln.field.no', type: 'checkbox', value: false },
    ]);
  });

  it('isolates direct grouped checkbox selection to one repeated runtime row', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.repeat.results.row-1.failed': { checkboxGroupCode: 'row_result', type: 'checkbox', value: true },
        'eln.repeat.results.row-1.passed': { checkboxGroupCode: 'row_result', type: 'checkbox', value: false },
        'eln.repeat.results.row-2.failed': { checkboxGroupCode: 'row_result', type: 'checkbox', value: true },
        'eln.repeat.results.row-2.passed': { checkboxGroupCode: 'row_result', type: 'checkbox', value: false },
      },
      repeatGroups: [],
    });
    const initial = [
      { alias: '第1行合格', currentValue: false, internalId: 'row-1-passed', options: [], tag: 'eln.repeat.results.row-1.passed', type: 'checkbox' as const },
      { alias: '第1行不合格', currentValue: true, internalId: 'row-1-failed', options: [], tag: 'eln.repeat.results.row-1.failed', type: 'checkbox' as const },
      { alias: '第2行合格', currentValue: false, internalId: 'row-2-passed', options: [], tag: 'eln.repeat.results.row-2.passed', type: 'checkbox' as const },
      { alias: '第2行不合格', currentValue: true, internalId: 'row-2-failed', options: [], tag: 'eln.repeat.results.row-2.failed', type: 'checkbox' as const },
    ];
    const inspectFields = vi.fn()
      .mockResolvedValueOnce(initial)
      .mockResolvedValueOnce([{ ...initial[0]!, currentValue: true }, ...initial.slice(1)]);
    const applyFields = vi.fn().mockImplementation(async (changes) =>
      changes.map((change: { tag: string }) => ({ ok: true, tag: change.tag })));
    const { changed, filler, ready } = fixture({ applyFields, inspectFields });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    changed({ InternalId: 'row-1-passed', Tag: 'eln.repeat.results.row-1.passed' });

    await vi.waitFor(() => expect(applyFields).toHaveBeenCalledOnce());
    expect(applyFields).toHaveBeenCalledWith([
      { tag: 'eln.repeat.results.row-1.failed', type: 'checkbox', value: false },
    ]);
    expect(filler.store.fields.map((field) => field.draftValue)).toEqual([true, false, false, true]);
  });

  it('delays plugin-generated checkbox events and does not recursively enforce the group', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.failed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
        'eln.field.passed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: false },
      },
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: true,
          steps: [{ id: 'step-1', name: '检验', order: 1 }],
        },
        controlSteps: {
          'eln.field.failed': 'step-1',
          'eln.field.passed': 'step-1',
        },
        repeatGroupSteps: {},
      },
    });
    const initial = [
      { alias: '合格', currentValue: false, internalId: 'passed', options: [], tag: 'eln.field.passed', type: 'checkbox' as const },
      { alias: '不合格', currentValue: true, internalId: 'failed', options: [], tag: 'eln.field.failed', type: 'checkbox' as const },
    ];
    const selectedDocument = [{ ...initial[0]!, currentValue: true }, initial[1]!];
    const enforcedDocument = [selectedDocument[0]!, { ...initial[1]!, currentValue: false }];
    const inspectFields = vi.fn()
      .mockResolvedValueOnce(initial)
      .mockResolvedValueOnce(selectedDocument)
      .mockResolvedValueOnce(enforcedDocument);
    let emitPluginChange = () => undefined;
    const applyFields = vi.fn().mockImplementation(async (changes) => {
      emitPluginChange();
      return changes.map((change: { tag: string }) => ({ ok: true, tag: change.tag }));
    });
    const fixtureResult = fixture({ applyFields, inspectFields });
    emitPluginChange = () => fixtureResult.changed({ InternalId: 'failed', Tag: 'eln.field.failed' });
    fixtureResult.ready();
    await vi.waitFor(() => expect(fixtureResult.filler.store.status).toBe('ready'));

    fixtureResult.changed({ InternalId: 'passed', Tag: 'eln.field.passed' });

    await vi.waitFor(() => expect(inspectFields).toHaveBeenCalledTimes(3));
    expect(applyFields).toHaveBeenCalledOnce();
    expect(fixtureResult.filler.store.fields.map((field) => field.documentValue)).toEqual([true, false]);
    expect(fixtureResult.filler.store.fields.map((field) => field.draftValue)).toEqual([true, false]);
  });

  it('preserves unrelated field errors after automatic checkbox deselection succeeds', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.failed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
        'eln.field.note': { type: 'text', value: '' },
        'eln.field.passed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: false },
      },
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: true,
          steps: [{ id: 'step-1', name: '检验', order: 1 }],
        },
        controlSteps: {
          'eln.field.failed': 'step-1',
          'eln.field.note': 'step-1',
          'eln.field.passed': 'step-1',
        },
        repeatGroupSteps: {},
      },
    });
    const initial = [
      { alias: '合格', currentValue: false, internalId: 'passed', options: [], tag: 'eln.field.passed', type: 'checkbox' as const },
      { alias: '不合格', currentValue: true, internalId: 'failed', options: [], tag: 'eln.field.failed', type: 'checkbox' as const },
      { alias: '备注', currentValue: '', internalId: 'note', options: [], tag: 'eln.field.note', type: 'text' as const },
    ];
    const inspectFields = vi.fn()
      .mockResolvedValueOnce(initial)
      .mockResolvedValueOnce([{ ...initial[0]!, currentValue: true }, ...initial.slice(1)]);
    const applyFields = vi.fn().mockImplementation(async (changes) =>
      changes.map((change: { tag: string }) => ({ ok: true, tag: change.tag })));
    const { changed, filler, ready } = fixture({ applyFields, inspectFields });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));
    filler.store.fieldErrors['eln.field.note'] = '备注写入失败';

    changed({ InternalId: 'passed', Tag: 'eln.field.passed' });

    await vi.waitFor(() => expect(applyFields).toHaveBeenCalledOnce());
    expect(filler.store.fieldErrors).toEqual({ 'eln.field.note': '备注写入失败' });
  });

  it('uses re-read document values when the sidebar already has a pending group selection', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.field.failed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: true },
        'eln.field.passed': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: false },
      },
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: true,
          steps: [{ id: 'step-1', name: '检验', order: 1 }],
        },
        controlSteps: {
          'eln.field.failed': 'step-1',
          'eln.field.passed': 'step-1',
        },
        repeatGroupSteps: {},
      },
    });
    const initial = [
      { alias: '合格', currentValue: false, internalId: 'passed', options: [], tag: 'eln.field.passed', type: 'checkbox' as const },
      { alias: '不合格', currentValue: true, internalId: 'failed', options: [], tag: 'eln.field.failed', type: 'checkbox' as const },
    ];
    const inspectFields = vi.fn()
      .mockResolvedValueOnce(initial)
      .mockResolvedValueOnce([{ ...initial[0]!, currentValue: true }, initial[1]!]);
    const applyFields = vi.fn().mockImplementation(async (changes) =>
      changes.map((change: { tag: string }) => ({ ok: true, tag: change.tag })));
    const { changed, filler, ready } = fixture({ applyFields, inspectFields });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));
    filler.store.setDraft('eln.field.passed', true);

    changed({ InternalId: 'passed', Tag: 'eln.field.passed' });

    await vi.waitFor(() => expect(applyFields).toHaveBeenCalledOnce());
    expect(applyFields).toHaveBeenCalledWith([
      { tag: 'eln.field.failed', type: 'checkbox', value: false },
    ]);
  });

  it('blocks unified apply for a cross-step document conflict and follows document focus', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {},
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: false,
          steps: [
            { id: 'step-first', name: '第一步', order: 1 },
            { id: 'step-second', name: '第二步', order: 2 },
          ],
        },
        controlSteps: { 'eln.field.first': 'step-first', 'eln.field.second': 'step-second' },
        repeatGroupSteps: {},
      },
    });
    const initial = [
      { alias: '第一项', currentValue: 'A', internalId: '1', options: [], tag: 'eln.field.first', type: 'text' as const },
      { alias: '第二项', currentValue: 'B', internalId: '2', options: [], tag: 'eln.field.second', type: 'text' as const },
    ];
    const inspectFields = vi.fn()
      .mockResolvedValueOnce(initial)
      .mockResolvedValueOnce([initial[0], { ...initial[1], currentValue: '文档值' }]);
    const applyFields = vi.fn().mockResolvedValue([]);
    const { changed, filler, focused, ready } = fixture({ applyFields, inspectFields });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));
    filler.store.setDraft('eln.field.second', '侧栏值');

    changed({ InternalId: '2', Tag: 'eln.field.second' });
    await vi.waitFor(() => expect(filler.conflictCount.value).toBe(1));
    expect(filler.stepProgress.value.map(({ status }) => status)).toEqual(['in-progress', 'problem']);
    await filler.apply();
    expect(applyFields).not.toHaveBeenCalled();

    focused({ InternalId: '2', Tag: 'eln.field.second' });
    expect(filler.currentStepId.value).toBe('step-second');
    expect(filler.store.focusedTag).toBe('eln.field.second');
  });

  it('locates a sidebar field in the document by its stable internal id', async () => {
    const selectControl = vi.fn().mockResolvedValue(undefined);
    const { filler, ready } = fixture({ selectControl });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    await filler.focusField('eln.field.name');

    expect(selectControl).toHaveBeenCalledWith('1');
    expect(filler.store.focusedTag).toBe('eln.field.name');
  });

  it('locates the first primary control in document order when selecting a directory step', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {},
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: false,
          steps: [{ id: 'step-fill', name: '填写', order: 1 }],
        },
        controlSteps: { 'eln.field.name': 'step-fill' },
        repeatGroupSteps: {},
      },
    });
    const selectControl = vi.fn().mockResolvedValue(undefined);
    const { filler, ready } = fixture({
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '名称引用', currentValue: '', internalId: 'reference-1', options: [], tag: 'eln.ref.name.instance_1', type: 'text' },
        { alias: '名称', currentValue: '', internalId: 'primary-1', options: [], tag: 'eln.field.name', type: 'text' },
      ]),
      selectControl,
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    await filler.focusFirstFieldInStep('step-fill');

    expect(selectControl).toHaveBeenCalledOnce();
    expect(selectControl).toHaveBeenCalledWith('primary-1');
    expect(filler.store.focusedTag).toBe('eln.field.name');
  });

  it('restores record current state and synchronizes matching references in one document apply', async () => {
    const applyFields = vi.fn().mockResolvedValue([
      { tag: 'eln.field.name', ok: true },
      { tag: 'eln.ref.name.instance_1', ok: true },
    ]);
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: { 'eln.field.name': { type: 'text', value: '服务端当前态' } },
      repeatGroups: [],
    });
    const { filler, ready } = fixture({
      applyFields,
      getRuntimeOptions: () => ({
        ...context,
        bizModule: 'eln.record',
        bizScene: 'eln.record.fill',
        scope: 'onlyoffice:eln-record-fill',
      }),
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '名称', currentValue: '过期值', internalId: '1', options: [], tag: 'eln.field.name', type: 'text' },
        { alias: '名称引用', currentValue: '过期引用', internalId: '2', options: [], tag: 'eln.ref.name.instance_1', type: 'text' },
      ]),
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    expect(filler.store.fields.map((field) => field.draftValue)).toEqual(['服务端当前态', '服务端当前态']);
    expect(applyFields).toHaveBeenNthCalledWith(1, [
      { tag: 'eln.field.name', type: 'text', value: '服务端当前态' },
      { tag: 'eln.ref.name.instance_1', type: 'text', value: '服务端当前态' },
    ]);
    filler.store.setDraft('eln.field.name', '新值');
    await filler.apply();

    expect(applyFields).toHaveBeenNthCalledWith(2, [
      { tag: 'eln.field.name', type: 'text', value: '新值' },
      { tag: 'eln.ref.name.instance_1', type: 'text', value: '新值' },
    ]);
  });

  it('reuses the same operation code when document row cloning is retried', async () => {
    vi.mocked(appendRepeatRow).mockResolvedValue({ groupCode: 'results', rowId: 'row-1', rowOrder: 1, status: 'PREPARED' });
    const appendDocumentRow = vi.fn().mockRejectedValueOnce(new Error('文档克隆失败')).mockResolvedValueOnce([
      'eln.repeat.results.row-1.value',
    ]);
    const inspectFields = vi.fn().mockResolvedValue([
      { alias: '结果', currentValue: '', internalId: 'prototype', options: [], tag: 'eln.repeat.results.prototype.value', type: 'text' },
    ]);
    const { filler, ready } = fixture({
      appendRepeatRow: appendDocumentRow,
      getRuntimeOptions: () => ({
        ...context,
        bizModule: 'eln.record',
        bizScene: 'eln.record.fill',
        scope: 'onlyoffice:eln-record-fill',
      }),
      inspectFields,
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    await filler.appendRepeatRow('results');
    expect(filler.store.repeatError).toBe('文档克隆失败');
    await filler.appendRepeatRow('results');

    expect(vi.mocked(appendRepeatRow)).toHaveBeenCalledTimes(2);
    expect(vi.mocked(appendRepeatRow).mock.calls[0]?.[1].operationCode).toBe(
      vi.mocked(appendRepeatRow).mock.calls[1]?.[1].operationCode,
    );
    expect(appendDocumentRow).toHaveBeenCalledTimes(2);
    expect(filler.store.repeatError).toBe('');
    expect(filler.store.message).toBe('已新增第 1 行');
  });

  it('reports an ambiguous checkbox group after a repeated row full refresh', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {
        'eln.repeat.results.row-1.failed': { checkboxGroupCode: 'row_result', type: 'checkbox', value: false },
        'eln.repeat.results.row-1.passed': { checkboxGroupCode: 'row_result', type: 'checkbox', value: true },
      },
      repeatGroups: [{
        activeRowCount: 1,
        groupCode: 'results',
        maxRows: 3,
        minRows: 0,
        rows: [{ groupCode: 'results', rowId: 'row-1', rowOrder: 1, status: 'ACTIVE' }],
      }],
    });
    vi.mocked(appendRepeatRow).mockResolvedValue({
      groupCode: 'results', rowId: 'row-2', rowOrder: 2, status: 'PREPARED',
    });
    const row1 = [
      { alias: '第1行合格', currentValue: true, internalId: 'row-1-passed', options: [], tag: 'eln.repeat.results.row-1.passed', type: 'checkbox' as const },
      { alias: '第1行不合格', currentValue: false, internalId: 'row-1-failed', options: [], tag: 'eln.repeat.results.row-1.failed', type: 'checkbox' as const },
    ];
    const row2 = [
      { alias: '第2行合格', currentValue: true, internalId: 'row-2-passed', options: [], tag: 'eln.repeat.results.row-2.passed', type: 'checkbox' as const },
      { alias: '第2行不合格', currentValue: true, internalId: 'row-2-failed', options: [], tag: 'eln.repeat.results.row-2.failed', type: 'checkbox' as const },
    ];
    const inspectFields = vi.fn().mockResolvedValueOnce(row1).mockResolvedValueOnce([...row1, ...row2]);
    const { filler, ready } = fixture({ inspectFields });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    await filler.appendRepeatRow('results');

    expect(filler.store.checkboxGroupError).toBe('选择组“row_result”最多选择 1 项');
  });

  it('marks the owning step as a problem when pending drafts fail before a repeat operation', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: {},
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: false,
          steps: [{ id: 'step-fill', name: '填写', order: 1 }],
        },
        controlSteps: { 'eln.field.name': 'step-fill' },
        repeatGroupSteps: {},
      },
    });
    const applyFields = vi.fn().mockRejectedValue(new Error('草稿写入失败'));
    const { filler, ready } = fixture({
      applyFields,
      getRuntimeOptions: () => ({
        ...context,
        bizModule: 'eln.record',
        bizScene: 'eln.record.fill',
        scope: 'onlyoffice:eln-record-fill',
      }),
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));
    filler.store.setDraft('eln.field.name', '样品');

    await filler.appendRepeatRow('results');

    expect(vi.mocked(appendRepeatRow)).not.toHaveBeenCalled();
    expect(filler.store.fieldErrors).toEqual({ 'eln.field.name': '草稿写入失败' });
    expect(filler.stepProgress.value[0]?.status).toBe('problem');
  });

  it('restores the server delete intent with the same operation code when document deletion fails', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: { 'eln.repeat.results.row-1.value': { type: 'text', value: '合格' } },
      repeatGroups: [{
        activeRowCount: 1, groupCode: 'results', maxRows: 3, minRows: 0,
        rows: [{ groupCode: 'results', rowId: 'row-1', rowOrder: 1, status: 'ACTIVE' }],
      }],
    });
    vi.mocked(requestDeleteRepeatRow).mockImplementation(async (_context, form) => ({
      ...form, deleteOperationCode: form.operationCode, deleteReason: form.reason, rowOrder: 1, status: 'DELETE_PREPARED',
    }));
    vi.mocked(requestRestoreRepeatRow).mockImplementation(async (_context, form) => ({
      ...form, rowOrder: 1, status: 'ACTIVE',
    }));
    const removeRepeatRow = vi.fn().mockRejectedValue(new Error('文档删行失败'));
    const { filler, ready } = fixture({
      getRuntimeOptions: () => ({ ...context, bizModule: 'eln.record', bizScene: 'eln.record.fill', scope: 'onlyoffice:eln-record-fill' }),
      inspectFields: vi.fn().mockResolvedValue([
        { alias: '结果', currentValue: '', internalId: 'prototype', options: [], tag: 'eln.repeat.results.prototype.value', type: 'text' },
        { alias: '结果', currentValue: '合格', internalId: 'row-1', options: [], tag: 'eln.repeat.results.row-1.value', type: 'text' },
      ]),
      removeRepeatRow,
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    await filler.deleteRepeatRow('results', 'row-1', '录入错误');

    const deleteCode = vi.mocked(requestDeleteRepeatRow).mock.calls[0]?.[1].operationCode;
    expect(vi.mocked(requestRestoreRepeatRow).mock.calls[0]?.[1].operationCode).toBe(deleteCode);
    expect(filler.store.repeatError).toBe('文档删行失败');
    expect(filler.store.repeatGroups[0]?.rows[0]?.status).toBe('ACTIVE');
  });

  it('recreates and refills a missing pending-delete row before restoring server state', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: { 'eln.repeat.results.row-1.value': { type: 'text', value: '待恢复' } },
      repeatGroups: [{
        activeRowCount: 1, groupCode: 'results', maxRows: 3, minRows: 0,
        rows: [{
          deleteOperationCode: 'delete-1', deleteReason: '录入错误', groupCode: 'results',
          rowId: 'row-1', rowOrder: 1, status: 'DELETE_PREPARED',
        }],
      }],
    });
    vi.mocked(requestRestoreRepeatRow).mockResolvedValue({
      groupCode: 'results', rowId: 'row-1', rowOrder: 1, status: 'ACTIVE',
    });
    const prototype = { alias: '结果', currentValue: '', internalId: 'prototype', options: [], tag: 'eln.repeat.results.prototype.value', type: 'text' as const };
    const runtime = { ...prototype, currentValue: '', internalId: 'row-1', tag: 'eln.repeat.results.row-1.value' };
    const inspectFields = vi.fn().mockResolvedValueOnce([prototype]).mockResolvedValueOnce([prototype, runtime]);
    const appendDocumentRow = vi.fn().mockResolvedValue(['eln.repeat.results.row-1.value']);
    const applyFields = vi.fn().mockResolvedValue([{ ok: true, tag: runtime.tag }]);
    const { filler, ready } = fixture({
      appendRepeatRow: appendDocumentRow,
      applyFields,
      getRuntimeOptions: () => ({ ...context, bizModule: 'eln.record', bizScene: 'eln.record.fill', scope: 'onlyoffice:eln-record-fill' }),
      inspectFields,
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));

    await filler.restoreRepeatRow('results', 'row-1');

    expect(appendDocumentRow).toHaveBeenCalledWith('results', 'row-1');
    expect(applyFields).toHaveBeenCalledWith([
      { tag: runtime.tag, type: 'text', value: '待恢复' },
    ]);
    expect(vi.mocked(requestRestoreRepeatRow)).toHaveBeenCalledWith(expect.anything(), {
      groupCode: 'results', operationCode: 'delete-1', rowId: 'row-1',
    });
    expect(filler.store.repeatGroups[0]?.rows[0]?.status).toBe('ACTIVE');
  });

  it('restores the latest in-memory row values after deleting and restoring in one session', async () => {
    vi.mocked(fetchFillData).mockResolvedValue({
      fields: { 'eln.repeat.results.row-1.value': { type: 'text', value: '服务端旧值' } },
      repeatGroups: [{
        activeRowCount: 1, groupCode: 'results', maxRows: 3, minRows: 0,
        rows: [{ groupCode: 'results', rowId: 'row-1', rowOrder: 1, status: 'ACTIVE' }],
      }],
    });
    vi.mocked(requestDeleteRepeatRow).mockImplementation(async (_context, form) => ({
      ...form, deleteOperationCode: form.operationCode, deleteReason: form.reason,
      rowOrder: 1, status: 'DELETE_PREPARED',
    }));
    vi.mocked(requestRestoreRepeatRow).mockResolvedValue({
      groupCode: 'results', rowId: 'row-1', rowOrder: 1, status: 'ACTIVE',
    });
    const prototype = { alias: '结果', currentValue: '', internalId: 'prototype', options: [], tag: 'eln.repeat.results.prototype.value', type: 'text' as const };
    const runtime = { ...prototype, currentValue: '服务端旧值', internalId: 'row-1', tag: 'eln.repeat.results.row-1.value' };
    const restoredRuntime = { ...runtime, currentValue: '' };
    const inspectFields = vi.fn()
      .mockResolvedValueOnce([prototype, runtime])
      .mockResolvedValueOnce([prototype])
      .mockResolvedValueOnce([prototype, restoredRuntime]);
    const applyFields = vi.fn().mockImplementation(async (changes) => changes.map((change: { tag: string }) => ({ ok: true, tag: change.tag })));
    const { filler, ready } = fixture({
      applyFields,
      getRuntimeOptions: () => ({ ...context, bizModule: 'eln.record', bizScene: 'eln.record.fill', scope: 'onlyoffice:eln-record-fill' }),
      inspectFields,
    });
    ready();
    await vi.waitFor(() => expect(filler.store.status).toBe('ready'));
    filler.store.setDraft(runtime.tag, '未保存新值');

    await filler.deleteRepeatRow('results', 'row-1', '重新录入');
    await filler.restoreRepeatRow('results', 'row-1');

    expect(applyFields).toHaveBeenNthCalledWith(1, [{ tag: runtime.tag, type: 'text', value: '未保存新值' }]);
    expect(applyFields).toHaveBeenNthCalledWith(2, [{ tag: runtime.tag, type: 'text', value: '未保存新值' }]);
    expect(filler.store.fields.find((field) => field.tag === runtime.tag)?.draftValue).toBe('未保存新值');
  });
});
