import { describe, expect, it, vi } from 'vitest';

import type { FieldDefinition } from '../../src/domain/types';
import { createOnlyOfficeBridge } from '../../src/onlyoffice/bridge';
import {
  disableTrackRevisionsCommand,
  insertTypedControlCommand,
  listContentControlsCommand,
  readControlTextCommand,
  setFormulaResultLockCommand,
  updateControlSpecCommand,
} from '../../src/onlyoffice/commands';
import type { TemplateBinding, TemplateControlSnapshot } from '../../src/domain/types';
import type { AscGlobal, OfficeContentControl } from '../../src/onlyoffice/types';
import { installOnlyOfficeGlobals, makeApiFixture } from '../fixtures/onlyoffice';

interface AscFixture {
  asc: AscGlobal;
  callCommands: Array<{ command: () => string; payload: unknown; scopeKey: string }>;
  closeCalls: Array<[string, string]>;
  listCommands: Array<{ command: () => string; payload: unknown; scopeKey: string }>;
  methodCalls: Array<[string, unknown[]]>;
}

interface AscFixtureOptions {
  autoCreateContentControl?: boolean;
  commandResults?: unknown[];
  methodResults?: unknown[];
}

const singleColor = { R: 23, G: 109, B: 92 };
type TextFieldDefinition = FieldDefinition & { controlType: 'text' };

function control(Tag: string, InternalId = `id-${Tag}`): OfficeContentControl {
  return { Alias: Tag, InternalId, Tag };
}

function textDefinition(overrides: Partial<TextFieldDefinition> = {}): FieldDefinition {
  return {
    controlType: 'text',
    fieldCode: 'result',
    alias: '结果',
    tag: 'eln.field.result',
    color: singleColor,
    placeholder: '请输入结果',
    defaultValue: '',
    defaultChecked: false,
    ...overrides,
  };
}

function typedDefinition(controlType: 'checkbox' | 'date' | 'dropdown' | 'multiline'): FieldDefinition {
  const base = {
    fieldCode: 'result',
    alias: '结果',
    tag: 'eln.field.result',
    color: singleColor,
  };

  if (controlType === 'dropdown') {
    return {
      ...base,
      controlType,
      placeholder: '请选择结果',
      options: [
        { display: '合格', value: 'pass' },
        { display: '不合格', value: 'fail' },
      ],
      selectedIndex: 1,
    };
  }

  return {
    ...base,
    controlType,
    placeholder: controlType === 'checkbox' ? '结果' : '请输入结果',
    defaultValue: controlType === 'date' ? '2026-08-11' : controlType === 'multiline' ? '第一行\n第二行' : '',
    defaultChecked: controlType === 'checkbox',
  };
}

function createAscFixture(options: AscFixtureOptions = {}): AscFixture {
  const methodResults = [...(options.methodResults ?? [])];
  const commandResults = [...(options.commandResults ?? [])];
  const methodCalls: Array<[string, unknown[]]> = [];
  const callCommands: AscFixture['callCommands'] = [];
  const listCommands: AscFixture['listCommands'] = [];
  const closeCalls: Array<[string, string]> = [];
  const scope: AscGlobal['scope'] = {};

  const asc: AscGlobal = {
    scope,
    plugin: {
      executeMethod(name, args, callback) {
        if (name === 'GetCurrentContentControl') {
          callback(null);
          return true;
        }
        methodCalls.push([name, args]);
        const result = methodResults.shift();
        if (name === 'AddContentControl' && result === undefined && options.autoCreateContentControl !== false) {
          const properties = args[1] as { Tag?: unknown } | undefined;
          const tag = typeof properties?.Tag === 'string' ? properties.Tag : '';
          callback({ InternalId: `id-${tag}`, Tag: tag });
          return true;
        }
        callback(result);
        return true;
      },
      callCommand(command, _isClose, _isCalc, callback) {
        const scopeKey = Object.keys(scope)[0] ?? '';
        if (command === listContentControlsCommand) {
          listCommands.push({ command, scopeKey, payload: scope[scopeKey] });
          callback({ ok: true, controls: methodResults.shift() });
          return true;
        }
        callCommands.push({ command, scopeKey, payload: scope[scopeKey] });
        callback(commandResults.length > 0 ? commandResults.shift() : { ok: true });
        return true;
      },
      executeCommand(command, data) {
        closeCalls.push([command, data]);
      },
      onThemeChangedBase: vi.fn(),
    },
  };

  return { asc, callCommands, closeCalls, listCommands, methodCalls };
}

describe('ONLYOFFICE bridge', () => {
  it('通过编辑器方法读取当前选中文本', async () => {
    const fixture = createAscFixture({ methodResults: [' 是否合格 '] });

    await expect(createOnlyOfficeBridge(fixture.asc).readSelectedText()).resolves.toBe(' 是否合格 ');
    expect(fixture.methodCalls).toEqual([['GetSelectedText', [{ Math: false, Numbering: false }]]]);
  });

  it('按结果主控件 Tag 锁定并恢复主控件及引用快照', async () => {
    const fixture = createAscFixture({
      commandResults: [
        { ok: true, snapshot: { primaryTag: 'eln.field.result', locks: [{ tag: 'eln.field.result', lock: 'unlocked' }] } },
        { ok: true, snapshot: { primaryTag: 'eln.field.result', locks: [{ tag: 'eln.field.result', lock: 'unlocked' }] } },
      ],
    });
    const bridge = createOnlyOfficeBridge(fixture.asc);
    const snapshot = await bridge.lockFormulaResult('eln.field.result');
    await bridge.restoreFormulaResultLocks(snapshot);

    expect(fixture.callCommands).toEqual([
      { command: setFormulaResultLockCommand, scopeKey: 'elnTemplateFormulaLocks', payload: { mode: 'lock', primaryTag: 'eln.field.result' } },
      { command: setFormulaResultLockCommand, scopeKey: 'elnTemplateFormulaLocks', payload: { mode: 'restore', snapshot } },
    ]);
  });
  it('原样读取插件运行 options', () => {
    const fixture = createAscFixture();
    const options = { context: true };
    fixture.asc.plugin.info = { options };

    expect(createOnlyOfficeBridge(fixture.asc).getRuntimeOptions()).toBe(options);
  });

  it('通过扫描 command 回读具体类型并清理列表 scope', async () => {
    const expected = [{ ...control('eln.field.result'), ControlType: 'date' as const }];
    const fixture = createAscFixture({ methodResults: [expected] });

    await expect(createOnlyOfficeBridge(fixture.asc).listControls()).resolves.toEqual(expected);
    expect(fixture.listCommands).toEqual([{ command: listContentControlsCommand, scopeKey: 'elnTemplateList', payload: {} }]);
    expect(fixture.asc.scope.elnTemplateList).toBeUndefined();
    expect(fixture.methodCalls).toHaveLength(0);
  });

  it('拒绝扫描 command 的非数组内容控件结果', async () => {
    const fixture = createAscFixture({ methodResults: [null] });

    await expect(createOnlyOfficeBridge(fixture.asc).listControls()).rejects.toThrow('编辑器未返回内容控件列表');
  });

  it('拒绝扫描 command 的同步异常', async () => {
    const fixture = createAscFixture();
    fixture.asc.plugin.callCommand = () => {
      throw new Error('method crashed');
    };

    await expect(createOnlyOfficeBridge(fixture.asc).listControls()).rejects.toThrow('method crashed');
  });
});

describe('ONLYOFFICE command bridge', () => {
  it('解除公式结果锁时使用 ONLYOFFICE 9.4 接受的 unlocked 值', () => {
    const apiFixture = makeApiFixture();
    const result = apiFixture.makeControl('text', 'eln.field.result');
    result.lock = 'contentLocked';
    const locks: string[] = [];
    result.SetLock = (value) => {
      locks.push(value);
      if (value !== 'unlocked') return false;
      result.lock = value;
      return true;
    };
    apiFixture.document.controls = [result];
    installOnlyOfficeGlobals(apiFixture.Api, {
      elnTemplateFormulaLocks: { mode: 'unlock', primaryTag: 'eln.field.result' },
    });

    expect(JSON.parse(setFormulaResultLockCommand())).toMatchObject({ ok: true });
    expect(locks).toEqual(['unlocked']);
  });

  it('关闭文档内部的修订跟踪状态', async () => {
    const fixture = createAscFixture({ commandResults: [{ ok: true }, { hasRevisionChanges: false, ok: true, trackRevisions: false }] });

    await expect(createOnlyOfficeBridge(fixture.asc).disableTrackRevisions()).resolves.toBeUndefined();
    expect(fixture.callCommands).toEqual([
      {
        command: disableTrackRevisionsCommand,
        scopeKey: 'elnTemplateReview',
        payload: {},
      },
      {
        command: expect.any(Function),
        scopeKey: 'elnTemplateReviewState',
        payload: {},
      },
    ]);
  });

  it('写命令回调缺失时仍通过独立只读命令确认修订已关闭', async () => {
    const fixture = createAscFixture({ commandResults: [undefined, { hasRevisionChanges: false, ok: true, trackRevisions: false }] });

    await expect(createOnlyOfficeBridge(fixture.asc).disableTrackRevisions()).resolves.toBeUndefined();
    expect(fixture.callCommands.map((item) => item.scopeKey)).toEqual(['elnTemplateReview', 'elnTemplateReviewState']);
  });

  it('独立回读仍为开启状态时禁止模板标注', async () => {
    const fixture = createAscFixture({ commandResults: [undefined, { hasRevisionChanges: false, ok: true, trackRevisions: true }] });

    await expect(createOnlyOfficeBridge(fixture.asc).disableTrackRevisions()).rejects.toThrow('修订跟踪仍处于开启状态');
  });

  it('独立回读仍有历史修订时禁止模板标注', async () => {
    const fixture = createAscFixture({ commandResults: [undefined, { hasRevisionChanges: true, ok: true, trackRevisions: false }] });

    await expect(createOnlyOfficeBridge(fixture.asc).disableTrackRevisions()).rejects.toThrow('历史修订仍未全部接受');
  });

  it.each([
    ['JSON 字符串', JSON.stringify({ ok: true })],
    ['对象', { ok: true }],
  ])('写入 scope，并接受%s命令结果', async (_label, commandResult) => {
    const tag = 'eln.field.result';
    const fixture = createAscFixture({ methodResults: [[], [control(tag)]], commandResults: [commandResult] });

    await expect(createOnlyOfficeBridge(fixture.asc).insertField(typedDefinition('checkbox'))).resolves.toEqual([control(tag)]);

    expect(fixture.callCommands).toHaveLength(1);
    expect(fixture.callCommands[0]).toMatchObject({
      command: insertTypedControlCommand,
      scopeKey: 'elnTemplateInsert',
    });
    expect(fixture.asc.scope.elnTemplateInsert).toBeUndefined();
  });

  it.each([
    [{ ok: false, error: '业务失败' }, '业务失败'],
    [{ ok: false }, '编辑器命令执行失败'],
    [{ ok: false, error: 500 }, '编辑器命令执行失败'],
  ])('归一化命令失败 %#', async (commandResult, message) => {
    const fixture = createAscFixture({ methodResults: [[]], commandResults: [commandResult] });

    await expect(createOnlyOfficeBridge(fixture.asc).insertField(typedDefinition('checkbox'))).rejects.toThrow(message);
    expect(fixture.asc.scope.elnTemplateInsert).toBeUndefined();
  });

  it.each([
    ['非法 JSON', 'not-json'],
    ['null', null],
    ['数组', []],
    ['数字', 1],
    ['缺少 ok 的对象', { error: '业务失败' }],
    ['ok 非布尔值', { ok: 'false', error: '业务失败' }],
  ])('拒绝%s命令返回值', async (_label, commandResult) => {
    const fixture = createAscFixture({ methodResults: [[]], commandResults: [commandResult] });

    await expect(createOnlyOfficeBridge(fixture.asc).insertField(typedDefinition('checkbox'))).rejects.toThrow('编辑器命令返回无效结果');
    expect(fixture.asc.scope.elnTemplateInsert).toBeUndefined();
  });

  it('归一化 callCommand 同步异常并清理 scope', async () => {
    const fixture = createAscFixture({ methodResults: [[]] });
    fixture.asc.plugin.callCommand = () => {
      throw new Error('command crashed');
    };

    await expect(createOnlyOfficeBridge(fixture.asc).insertField(typedDefinition('checkbox'))).rejects.toThrow('command crashed');
    expect(fixture.asc.scope.elnTemplateInsert).toBeUndefined();
  });
});

describe('字段插入', () => {
  it('插入前拒绝已存在的 Tag，且不执行插入', async () => {
    const fixture = createAscFixture({ methodResults: [[control('eln.field.result')]] });

    await expect(createOnlyOfficeBridge(fixture.asc).insertField(textDefinition())).rejects.toThrow('字段 ID 已存在');
    expect(fixture.methodCalls).toHaveLength(0);
    expect(fixture.listCommands).toHaveLength(1);
    expect(fixture.callCommands).toHaveLength(0);
  });

  it('插入前拒绝现有 ELN 控件的同名 Alias，且忽略首尾空格', async () => {
    const fixture = createAscFixture({
      methodResults: [[{ Alias: ' 结果 ', InternalId: 'existing-1', Tag: 'eln.field.other_result' }]],
    });

    await expect(createOnlyOfficeBridge(fixture.asc).insertField(textDefinition())).rejects.toThrow('字段名称已存在');
    expect(fixture.methodCalls).toHaveLength(0);
    expect(fixture.listCommands).toHaveLength(1);
    expect(fixture.callCommands).toHaveLength(0);
  });

  it('非 ELN 内容控件的同名 Alias 不参与字段名称校验', async () => {
    const definition = textDefinition();
    const after = [{ Alias: '结果', InternalId: 'custom-1', Tag: 'custom.control' }, control(definition.tag)];
    const fixture = createAscFixture({
      methodResults: [[{ Alias: '结果', InternalId: 'custom-1', Tag: 'custom.control' }], undefined, after],
    });

    await expect(createOnlyOfficeBridge(fixture.asc).insertField(definition)).resolves.toBe(after);
  });

  it('文本字段通过原生接口插入并携带完整控件属性', async () => {
    const definition = textDefinition();
    const after = [control(definition.tag)];
    const fixture = createAscFixture({ methodResults: [[], undefined, after] });

    await expect(createOnlyOfficeBridge(fixture.asc).insertField(definition)).resolves.toBe(after);
    expect(fixture.methodCalls).toEqual([
      [
        'AddContentControl',
        [
          2,
          {
            Alias: definition.alias,
            Appearance: 1,
            Color: definition.color,
            Lock: 3,
            PlaceHolderText: definition.placeholder,
            Tag: definition.tag,
          },
        ],
      ],
    ]);
    expect(fixture.callCommands).toEqual([]);
    expect(fixture.listCommands).toHaveLength(2);
  });

  it('单行文本使用编辑器保存的光标位置插入', async () => {
    const definition = textDefinition();
    const after = [control(definition.tag)];
    const fixture = createAscFixture({
      commandResults: [{ error: 'API command 无法定位当前光标', ok: false }],
      methodResults: [[], undefined, after],
    });

    await expect(createOnlyOfficeBridge(fixture.asc).insertField(definition)).resolves.toBe(after);
    expect(fixture.methodCalls).toEqual([
      [
        'AddContentControl',
        [
          2,
          expect.objectContaining({
            Alias: definition.alias,
            PlaceHolderText: definition.placeholder,
            Tag: definition.tag,
          }),
        ],
      ],
    ]);
    expect(fixture.callCommands).toEqual([]);
  });

  it('文本默认值随文本控件一并插入', async () => {
    const definition = textDefinition({ defaultValue: 'S-001' });
    const fixture = createAscFixture({
      methodResults: [
        [],
        undefined,
        [control(definition.tag)],
        undefined,
        undefined,
        { InternalId: `id-${definition.tag}`, Tag: definition.tag, content: 'S-001' },
        [control(definition.tag)],
      ],
    });

    await createOnlyOfficeBridge(fixture.asc).insertField(definition);

    expect(fixture.methodCalls).toEqual([
      ['AddContentControl', [2, expect.objectContaining({ Tag: definition.tag })]],
      ['GetAllContentControls', []],
      ['MoveCursorToContentControl', [`id-${definition.tag}`, true]],
      ['InputText', ['S-001']],
      ['GetCurrentContentControlPr', ['text']],
    ]);
    expect(fixture.callCommands).toEqual([]);
  });

  it('原生文本插入失败时报告光标位置错误且不执行后续命令', async () => {
    const definition = textDefinition();
    const fixture = createAscFixture({ autoCreateContentControl: false, methodResults: [[], undefined] });

    await expect(createOnlyOfficeBridge(fixture.asc).insertField(definition)).rejects.toThrow('编辑器未能在光标位置插入文本控件');
    expect(fixture.methodCalls).toEqual([['AddContentControl', [2, expect.objectContaining({ Tag: definition.tag })]]]);
    expect(fixture.callCommands).toHaveLength(0);
  });

  it.each([
    [
      'dropdown' as const,
      {
        controlType: 'dropdown',
        tag: 'eln.field.result',
        alias: '结果',
        placeholder: '请选择结果',
        defaultValue: '',
        defaultChecked: false,
        options: [
          { display: '合格', value: 'pass' },
          { display: '不合格', value: 'fail' },
        ],
        selectedIndex: 1,
        color: singleColor,
      },
    ],
    [
      'checkbox' as const,
      {
        controlType: 'checkbox',
        tag: 'eln.field.result',
        alias: '结果',
        placeholder: '结果',
        defaultValue: '',
        defaultChecked: true,
        options: [],
        selectedIndex: -1,
        color: singleColor,
      },
    ],
    [
      'date' as const,
      {
        controlType: 'date',
        tag: 'eln.field.result',
        alias: '结果',
        placeholder: '请输入结果',
        defaultValue: '2026-08-11',
        defaultChecked: false,
        options: [],
        selectedIndex: -1,
        color: singleColor,
      },
    ],
    [
      'multiline' as const,
      {
        controlType: 'multiline',
        tag: 'eln.field.result',
        alias: '结果',
        placeholder: '请输入结果',
        defaultValue: '第一行\n第二行',
        defaultChecked: false,
        options: [],
        selectedIndex: -1,
        color: singleColor,
      },
    ],
  ])('%s 字段使用 typed command 的完整兼容 payload', async (controlType, expectedPayload) => {
    const definition = typedDefinition(controlType);
    const fixture = createAscFixture({ methodResults: [[], [control(definition.tag)]] });

    await createOnlyOfficeBridge(fixture.asc).insertField(definition);

    expect(fixture.callCommands).toEqual([
      {
        command: insertTypedControlCommand,
        scopeKey: 'elnTemplateInsert',
        payload: expect.objectContaining(expectedPayload),
      },
    ]);
  });

  it.each([
    ['没有增加', []],
    ['异常增加两个', [control('eln.field.result', 'first'), control('eln.field.result', 'second')]],
  ])('插入后 Tag 计数%s时失败', async (_label, after) => {
    const definition = typedDefinition('checkbox');
    const fixture = createAscFixture({ methodResults: [[], after] });

    await expect(createOnlyOfficeBridge(fixture.asc).insertField(definition)).rejects.toThrow('插入后 Tag 计数未增加');
  });

  it.each([
    ['text', textDefinition(), [[], undefined, null]],
    ['typed', typedDefinition('checkbox'), [[], null]],
  ])('%s 字段插入后枚举结果非数组时使用插入后错误', async (_label, definition, methodResults) => {
    const fixture = createAscFixture({ methodResults });

    await expect(createOnlyOfficeBridge(fixture.asc).insertField(definition)).rejects.toThrow('插入后无法读取内容控件列表');
  });
});

describe('定位、关闭与生命周期', () => {
  it('删除拒绝空的内部 ID', async () => {
    const fixture = createAscFixture();

    await expect(createOnlyOfficeBridge(fixture.asc).deleteControl('')).rejects.toThrow('该内容控件没有可用的内部 ID');
    expect(fixture.methodCalls).toHaveLength(0);
  });

  it('解除目标不存在时不调用移除方法', async () => {
    const fixture = createAscFixture({ methodResults: [[control('eln.field.other', 'other-id')]] });

    await expect(createOnlyOfficeBridge(fixture.asc).deleteControl('stale-id')).rejects.toThrow('删除目标已不存在，请刷新字段列表');
    expect(fixture.methodCalls).toHaveLength(0);
    expect(fixture.listCommands).toHaveLength(1);
  });

  it.each([
    ['移除外壳失败', [[control('eln.field.result', 'control-1')], false], '编辑器未能移除字段控件'],
    ['解除后仍存在', [[control('eln.field.result', 'control-1')], true, [control('eln.field.result', 'control-1')]], '解除后字段仍然存在'],
  ])('%s时拒绝解除', async (_label, methodResults, message) => {
    const fixture = createAscFixture({ methodResults });

    await expect(createOnlyOfficeBridge(fixture.asc).deleteControl('control-1')).rejects.toThrow(message);
  });

  it('只移除内容控件外壳、保留内容并返回解除后的列表', async () => {
    const remaining = [control('eln.field.other', 'other-id')];
    const fixture = createAscFixture({ methodResults: [[control('eln.field.result', 'control-1'), ...remaining], undefined, remaining] });

    await expect(createOnlyOfficeBridge(fixture.asc).deleteControl('control-1')).resolves.toBe(remaining);
    expect(fixture.methodCalls).toEqual([['RemoveContentControl', ['control-1']]]);
    expect(fixture.listCommands).toHaveLength(2);
  });

  it('拒绝空的内部 ID', async () => {
    const fixture = createAscFixture();

    await expect(createOnlyOfficeBridge(fixture.asc).selectControl('')).rejects.toThrow('该内容控件没有可用的内部 ID');
    expect(fixture.methodCalls).toHaveLength(0);
  });

  it('目标已不存在时不调用定位方法', async () => {
    const fixture = createAscFixture({ methodResults: [[control('eln.field.other', 'other-id')]] });

    await expect(createOnlyOfficeBridge(fixture.asc).selectControl('stale-id')).rejects.toThrow('定位目标已不存在，请刷新字段列表');
    expect(fixture.methodCalls).toHaveLength(0);
    expect(fixture.listCommands).toHaveLength(1);
  });

  it('编辑器明确返回 false 时报告定位失败', async () => {
    const fixture = createAscFixture({ methodResults: [[control('eln.field.result', 'control-1')], false] });

    await expect(createOnlyOfficeBridge(fixture.asc).selectControl('control-1')).rejects.toThrow('编辑器未能定位字段');
    expect(fixture.methodCalls).toEqual([['SelectContentControl', ['control-1']]]);
    expect(fixture.listCommands).toHaveLength(1);
  });

  it.each([undefined, true])('通过内部 ID 定位内容控件并兼容返回 %s', async (result) => {
    const fixture = createAscFixture({ methodResults: [[control('eln.field.result', 'control-1')], result] });

    await expect(createOnlyOfficeBridge(fixture.asc).selectControl('control-1')).resolves.toBeUndefined();
    expect(fixture.methodCalls).toEqual([['SelectContentControl', ['control-1']]]);
    expect(fixture.listCommands).toHaveLength(1);
  });

  it('关闭插件窗口', () => {
    const fixture = createAscFixture();

    createOnlyOfficeBridge(fixture.asc).close();

    expect(fixture.closeCalls).toEqual([['close', '']]);
  });

  it('绑定 init、button 和主题回调，并在 cleanup 时恢复旧引用', () => {
    const events: string[] = [];
    const fixture = createAscFixture();
    const oldInit = vi.fn();
    const oldButton = vi.fn();
    const oldTheme = vi.fn();
    fixture.asc.plugin.init = oldInit;
    fixture.asc.plugin.button = oldButton;
    fixture.asc.plugin.onThemeChanged = oldTheme;
    fixture.asc.plugin.onThemeChangedBase = vi.fn(() => events.push('base'));
    const cleanup = createOnlyOfficeBridge(fixture.asc).bindLifecycle({
      onReady: () => events.push('ready'),
      onThemeChanged: () => events.push('theme'),
    });

    fixture.asc.plugin.init?.();
    fixture.asc.plugin.button?.(0);
    fixture.asc.plugin.button?.(-1);
    fixture.asc.plugin.onThemeChanged?.({ type: 'dark' });

    expect(events).toEqual(['ready', 'base', 'theme']);
    expect(fixture.closeCalls).toEqual([['close', '']]);
    cleanup();
    expect(fixture.asc.plugin.init).toBe(oldInit);
    expect(fixture.asc.plugin.button).toBe(oldButton);
    expect(fixture.asc.plugin.onThemeChanged).toBe(oldTheme);
  });

  it('旧实例 cleanup 不覆盖较新实例的生命周期函数', () => {
    const fixture = createAscFixture();
    const firstCleanup = createOnlyOfficeBridge(fixture.asc).bindLifecycle({ onReady: vi.fn(), onThemeChanged: vi.fn() });
    createOnlyOfficeBridge(fixture.asc).bindLifecycle({ onReady: vi.fn(), onThemeChanged: vi.fn() });
    const secondLifecycle = {
      init: fixture.asc.plugin.init,
      button: fixture.asc.plugin.button,
      onThemeChanged: fixture.asc.plugin.onThemeChanged,
    };

    firstCleanup();

    expect(fixture.asc.plugin.init).toBe(secondLifecycle.init);
    expect(fixture.asc.plugin.button).toBe(secondLifecycle.button);
    expect(fixture.asc.plugin.onThemeChanged).toBe(secondLifecycle.onThemeChanged);
  });

  it('交错 cleanup 后恢复绑定前引用且已释放实例不再响应', () => {
    const fixture = createAscFixture();
    const oldInit = vi.fn();
    const oldButton = vi.fn();
    const oldTheme = vi.fn();
    const firstReady = vi.fn();
    const firstTheme = vi.fn();
    const secondReady = vi.fn();
    const secondTheme = vi.fn();
    fixture.asc.plugin.init = oldInit;
    fixture.asc.plugin.button = oldButton;
    fixture.asc.plugin.onThemeChanged = oldTheme;
    const firstCleanup = createOnlyOfficeBridge(fixture.asc).bindLifecycle({
      onReady: firstReady,
      onThemeChanged: firstTheme,
    });
    const secondCleanup = createOnlyOfficeBridge(fixture.asc).bindLifecycle({
      onReady: secondReady,
      onThemeChanged: secondTheme,
    });

    fixture.asc.plugin.init?.();
    fixture.asc.plugin.onThemeChanged?.({ type: 'contrast' });
    expect(firstReady).not.toHaveBeenCalled();
    expect(firstTheme).not.toHaveBeenCalled();
    expect(secondReady).toHaveBeenCalledTimes(1);
    expect(secondTheme).toHaveBeenCalledTimes(1);

    firstCleanup();
    fixture.asc.plugin.init?.();
    fixture.asc.plugin.onThemeChanged?.({ type: 'dark' });

    expect(firstReady).not.toHaveBeenCalled();
    expect(firstTheme).not.toHaveBeenCalled();
    expect(secondReady).toHaveBeenCalledTimes(2);
    expect(secondTheme).toHaveBeenCalledTimes(2);

    secondCleanup();
    expect(fixture.asc.plugin.init).toBe(oldInit);
    expect(fixture.asc.plugin.button).toBe(oldButton);
    expect(fixture.asc.plugin.onThemeChanged).toBe(oldTheme);

    fixture.asc.plugin.init?.();
    fixture.asc.plugin.button?.(-1);
    fixture.asc.plugin.onThemeChanged?.({ type: 'light' });
    expect(firstReady).not.toHaveBeenCalled();
    expect(firstTheme).not.toHaveBeenCalled();
    expect(secondReady).toHaveBeenCalledTimes(2);
    expect(secondTheme).toHaveBeenCalledTimes(2);
    expect(oldInit).toHaveBeenCalledTimes(1);
    expect(oldButton).toHaveBeenCalledWith(-1);
    expect(oldTheme).toHaveBeenCalledWith({ type: 'light' });
  });
});

describe('后端绑定 bridge', () => {
  const primary: TemplateBinding = {
    controlAlias: '结果',
    controlConfig: { placeholder: '请输入结果' },
    controlRole: 'PRIMARY',
    controlTag: 'eln.field.result',
    controlType: 'TEXT',
    fieldCode: 'result',
    fieldId: 'field',
    fieldVersionId: 'version',
    id: 'primary',
    primaryBindingId: null,
    status: 'PENDING',
    templateId: 'template',
    version: 0,
  };

  it('光标位于已有内容控件时拒绝嵌套插入', async () => {
    const fixture = createAscFixture({ commandResults: [{ ok: true }] });
    fixture.asc.plugin.executeMethod = (name, args, callback) => {
      fixture.methodCalls.push([name, args]);
      callback(name === 'GetCurrentContentControl' ? 'outer-control' : { InternalId: 'created' });
      return true;
    };

    await expect(createOnlyOfficeBridge(fixture.asc).insertBinding(primary)).rejects.toThrow('请将光标移到已有字段控件外再插入');

    expect(fixture.methodCalls).toEqual([['GetCurrentContentControl', []]]);
  });

  it('历史空占位配置创建主控件时回退为控件别名，不重复按 Tag 更新', async () => {
    const fixture = createAscFixture();

    await createOnlyOfficeBridge(fixture.asc).insertBinding({ ...primary, controlConfig: { placeholder: '' } });

    expect(fixture.methodCalls).toEqual([
      ['AddContentControl', [2, expect.objectContaining({ Alias: '结果', Lock: 3, PlaceHolderText: '结果', Tag: primary.controlTag })]],
    ]);
    expect(fixture.callCommands).toEqual([]);
  });

  it('文本引用读取主字段当前文本，使用后端 eln.ref Tag 直接插入只读控件', async () => {
    const reference = { ...primary, controlRole: 'REFERENCE', controlTag: 'eln.ref.result.abc', id: 'reference', primaryBindingId: 'primary' };
    const fixture = createAscFixture({
      commandResults: [{ ok: true, text: '12.3' }],
      methodResults: [
        undefined,
        [control(reference.controlTag)],
        undefined,
        undefined,
        { InternalId: `id-${reference.controlTag}`, Tag: reference.controlTag, content: '12.3' },
      ],
    });

    await createOnlyOfficeBridge(fixture.asc).insertReference(reference, primary.controlTag);

    expect(fixture.methodCalls).toEqual([
      ['AddContentControl', [2, expect.objectContaining({ Alias: '结果', Lock: 0, Tag: reference.controlTag })]],
      ['GetAllContentControls', []],
      ['MoveCursorToContentControl', [`id-${reference.controlTag}`, true]],
      ['InputText', ['12.3']],
      ['GetCurrentContentControlPr', ['text']],
    ]);
    expect(fixture.callCommands).toEqual([{ command: readControlTextCommand, scopeKey: 'elnTemplateReadText', payload: { tag: primary.controlTag } }]);
  });

  it('中止后读到引用文本不再创建内容控件', async () => {
    const reference = { ...primary, controlRole: 'REFERENCE', controlTag: 'eln.ref.result.abc', id: 'reference', primaryBindingId: 'primary' };
    const fixture = createAscFixture();
    let completeRead!: (result: unknown) => void;
    fixture.asc.plugin.callCommand = (command, _isClose, _isCalc, callback) => {
      expect(command).toBe(readControlTextCommand);
      completeRead = callback;
      return true;
    };
    const controller = new AbortController();
    const operation = createOnlyOfficeBridge(fixture.asc).insertReference(reference, primary.controlTag, controller.signal);

    controller.abort();
    completeRead({ ok: true, text: '12.3' });
    await operation;

    expect(fixture.methodCalls).not.toContainEqual(expect.arrayContaining(['AddContentControl']));
  });

  it('按后端检查结果删除旧控件并更新主字段与全部引用', async () => {
    const reference = {
      ...primary,
      controlRole: 'REFERENCE',
      controlTag: 'eln.ref.result.abc',
      id: 'reference',
      primaryBindingId: 'primary',
      status: 'ACTIVE',
    };
    const fixture = createAscFixture({
      commandResults: [{ ok: true, updated: 1 }],
      methodResults: [[control(primary.controlTag, 'primary-control'), control(reference.controlTag, 'reference-control')], true],
    });
    const desired: TemplateControlSnapshot[] = [
      {
        bindingId: 'reference',
        controlAlias: '结果',
        controlConfig: {},
        controlRole: 'PRIMARY',
        controlTag: 'eln.field.result',
        controlType: 'TEXT',
        fieldVersionId: 'version',
        primaryBindingId: null,
      },
    ];

    await createOnlyOfficeBridge(fixture.asc).applyControlPlan([{ ...primary, status: 'ACTIVE' }, reference], desired);

    expect(fixture.methodCalls).not.toContainEqual(['RemoveContentControl', ['primary-control']]);
    expect(fixture.callCommands).toContainEqual(
      expect.objectContaining({
        command: updateControlSpecCommand,
        payload: { removals: [primary.controlTag], updates: [expect.objectContaining({ currentTag: reference.controlTag, tag: 'eln.field.result' })] },
      }),
    );
  });

  it('一次计划包含多个删除时在修改文档前拒绝执行', async () => {
    const another = { ...primary, controlTag: 'eln.field.conclusion', fieldCode: 'conclusion', id: 'another', status: 'ACTIVE' };
    const fixture = createAscFixture({
      methodResults: [[control(primary.controlTag, 'primary-control'), control(another.controlTag, 'another-control')]],
    });

    await expect(createOnlyOfficeBridge(fixture.asc).applyControlPlan([{ ...primary, status: 'ACTIVE' }, another], [])).rejects.toThrow(
      '当前阶段不支持一次删除多个控件',
    );

    expect(fixture.methodCalls).not.toContainEqual(expect.arrayContaining(['RemoveContentControl']));
    expect(fixture.callCommands).not.toContainEqual(expect.objectContaining({ command: updateControlSpecCommand }));
  });

  it('字段版本改变控件类型时在发送更新命令前明确拒绝', async () => {
    const fixture = createAscFixture({
      methodResults: [[{ ...control(primary.controlTag, 'primary-control'), ControlType: 'text' }]],
    });
    const desired: TemplateControlSnapshot[] = [
      {
        bindingId: primary.id,
        controlAlias: '结果',
        controlConfig: { dateFormat: 'yyyy-MM-dd' },
        controlRole: 'PRIMARY',
        controlTag: primary.controlTag,
        controlType: 'DATE',
        fieldVersionId: 'version-2',
        primaryBindingId: null,
      },
    ];

    await expect(createOnlyOfficeBridge(fixture.asc).applyControlPlan([{ ...primary, status: 'ACTIVE' }], desired)).rejects.toThrow(
      '当前阶段不支持修改已有控件类型',
    );

    expect(fixture.callCommands).not.toContainEqual(expect.objectContaining({ command: updateControlSpecCommand }));
  });

  it('目标 placeholder 为空但文档旧值非空时在发送更新命令前拒绝', async () => {
    const fixture = createAscFixture({
      methodResults: [
        [
          {
            Alias: '结果',
            ControlConfig: { placeholder: '原提示' },
            ControlRole: 'PRIMARY',
            ControlType: 'text',
            InternalId: 'primary-control',
            Lock: 'unlocked',
            Tag: primary.controlTag,
          },
        ],
      ],
    });
    const desired: TemplateControlSnapshot[] = [
      {
        bindingId: primary.id,
        controlAlias: '结果',
        controlConfig: { placeholder: '' },
        controlRole: 'PRIMARY',
        controlTag: primary.controlTag,
        controlType: 'TEXT',
        fieldVersionId: 'version-2',
        primaryBindingId: null,
      },
    ];

    await expect(createOnlyOfficeBridge(fixture.asc).applyControlPlan([{ ...primary, status: 'ACTIVE' }], desired)).rejects.toThrow(
      '当前编辑器不支持清空已有占位文本',
    );

    expect(fixture.callCommands).not.toContainEqual(expect.objectContaining({ command: updateControlSpecCommand }));
  });

  it('目标和文档 placeholder 均为空时允许发送更新命令', async () => {
    const emptyPlaceholder = { ...primary, controlConfig: { placeholder: '' }, status: 'ACTIVE' };
    const fixture = createAscFixture({
      commandResults: [{ ok: true, updated: 1 }],
      methodResults: [
        [
          {
            Alias: '结果',
            ControlConfig: { placeholder: '' },
            ControlRole: 'PRIMARY',
            ControlType: 'text',
            InternalId: 'primary-control',
            Lock: 'unlocked',
            Tag: primary.controlTag,
          },
        ],
      ],
    });
    const desired: TemplateControlSnapshot[] = [
      {
        bindingId: primary.id,
        controlAlias: '新结果',
        controlConfig: { placeholder: '' },
        controlRole: 'PRIMARY',
        controlTag: primary.controlTag,
        controlType: 'TEXT',
        fieldVersionId: 'version-2',
        primaryBindingId: null,
      },
    ];

    await expect(createOnlyOfficeBridge(fixture.asc).applyControlPlan([emptyPlaceholder], desired)).resolves.toBeUndefined();
    expect(fixture.callCommands).toContainEqual(expect.objectContaining({ command: updateControlSpecCommand }));
  });

  it('同 Tag 版本升级已写入 DOCX 时属性匹配即跳过重复 setter', async () => {
    const fixture = createAscFixture({
      methodResults: [
        [
          {
            Alias: '结果',
            ControlConfig: { placeholder: '请输入结果' },
            ControlRole: 'PRIMARY',
            ControlType: 'text',
            InternalId: 'primary-control',
            Lock: 'unlocked',
            Tag: primary.controlTag,
          },
        ],
      ],
    });
    const desired: TemplateControlSnapshot[] = [
      {
        bindingId: primary.id,
        controlAlias: '结果',
        controlConfig: { placeholder: '请输入结果' },
        controlRole: 'PRIMARY',
        controlTag: primary.controlTag,
        controlType: 'TEXT',
        fieldVersionId: 'version-2',
        primaryBindingId: null,
      },
    ];

    await expect(createOnlyOfficeBridge(fixture.asc).applyControlPlan([{ ...primary, status: 'ACTIVE' }], desired)).resolves.toBeUndefined();

    expect(fixture.callCommands).not.toContainEqual(expect.objectContaining({ command: updateControlSpecCommand }));
  });

  it('CHECKBOX 升级幂等重试忽略目标版本 defaultChecked 与文档当前勾选态差异', async () => {
    const checkbox = {
      ...primary,
      controlConfig: { defaultChecked: false },
      controlType: 'CHECKBOX',
      status: 'ACTIVE',
    };
    const fixture = createAscFixture({
      methodResults: [
        [
          {
            Alias: '结果',
            ControlConfig: { defaultChecked: false },
            ControlRole: 'PRIMARY',
            ControlType: 'checkbox',
            InternalId: 'checkbox-control',
            Lock: 'unlocked',
            Tag: checkbox.controlTag,
          },
        ],
      ],
    });
    const desired: TemplateControlSnapshot[] = [
      {
        bindingId: checkbox.id,
        controlAlias: '结果',
        controlConfig: { defaultChecked: true },
        controlRole: 'PRIMARY',
        controlTag: checkbox.controlTag,
        controlType: 'CHECKBOX',
        fieldVersionId: 'version-2',
        primaryBindingId: null,
      },
    ];

    await expect(createOnlyOfficeBridge(fixture.asc).applyControlPlan([checkbox], desired)).resolves.toBeUndefined();

    expect(fixture.callCommands).not.toContainEqual(expect.objectContaining({ command: updateControlSpecCommand }));
  });

  it('引用提升已写入 DOCX 时识别唯一目标控件并跳过重复改写和删除', async () => {
    const reference = {
      ...primary,
      controlRole: 'REFERENCE',
      controlTag: 'eln.ref.result.abc',
      id: 'reference',
      primaryBindingId: primary.id,
      status: 'ACTIVE',
    };
    const fixture = createAscFixture({
      methodResults: [
        [
          {
            Alias: '结果',
            ControlConfig: { placeholder: '请输入结果' },
            ControlRole: 'PRIMARY',
            ControlType: 'text',
            InternalId: 'promoted-control',
            Lock: 'unlocked',
            Tag: primary.controlTag,
          },
        ],
      ],
    });
    const desired: TemplateControlSnapshot[] = [
      {
        bindingId: reference.id,
        controlAlias: '结果',
        controlConfig: { placeholder: '请输入结果' },
        controlRole: 'PRIMARY',
        controlTag: primary.controlTag,
        controlType: 'TEXT',
        fieldVersionId: 'version',
        primaryBindingId: null,
      },
    ];

    await expect(createOnlyOfficeBridge(fixture.asc).applyControlPlan([{ ...primary, status: 'ACTIVE' }, reference], desired)).resolves.toBeUndefined();

    expect(fixture.callCommands).not.toContainEqual(expect.objectContaining({ command: updateControlSpecCommand }));
    expect(fixture.methodCalls).not.toContainEqual(expect.arrayContaining(['RemoveContentControl']));
  });

  it('删除已写入 DOCX 时允许空计划重试且不再发送删除命令', async () => {
    const fixture = createAscFixture({ methodResults: [[]] });

    await expect(createOnlyOfficeBridge(fixture.asc).applyControlPlan([{ ...primary, status: 'ACTIVE' }], [])).resolves.toBeUndefined();

    expect(fixture.callCommands).not.toContainEqual(expect.objectContaining({ command: updateControlSpecCommand }));
    expect(fixture.methodCalls).not.toContainEqual(expect.arrayContaining(['RemoveContentControl']));
  });

  it('中止后首次枚举返回时不删除或更新控件', async () => {
    const fixture = createAscFixture();
    let completeList!: (result: unknown) => void;
    fixture.asc.plugin.callCommand = (command, _isClose, _isCalc, callback) => {
      expect(command).toBe(listContentControlsCommand);
      completeList = callback;
      return true;
    };
    const controller = new AbortController();
    const operation = createOnlyOfficeBridge(fixture.asc).applyControlPlan([{ ...primary, status: 'ACTIVE' }], [], controller.signal);

    controller.abort();
    completeList({ ok: true, controls: [control(primary.controlTag, 'primary-control')] });
    await operation;

    expect(fixture.methodCalls).not.toContainEqual(['RemoveContentControl', ['primary-control']]);
    expect(fixture.callCommands).not.toContainEqual(expect.objectContaining({ command: updateControlSpecCommand }));
  });

  it('中止后内部枚举返回时不调用编辑器定位控件', async () => {
    const fixture = createAscFixture();
    let completeList!: (result: unknown) => void;
    fixture.asc.plugin.callCommand = (command, _isClose, _isCalc, callback) => {
      expect(command).toBe(listContentControlsCommand);
      completeList = callback;
      return true;
    };
    const controller = new AbortController();
    const operation = createOnlyOfficeBridge(fixture.asc).selectControl('control-1', controller.signal);

    controller.abort();
    completeList({ ok: true, controls: [control(primary.controlTag, 'control-1')] });
    await operation;

    expect(fixture.methodCalls).not.toContainEqual(['SelectContentControl', ['control-1']]);
  });

  it('中止后控件插入返回时不再继续更新属性', async () => {
    const reference = { ...primary, controlRole: 'REFERENCE', controlTag: 'eln.ref.result.abc', id: 'reference', primaryBindingId: 'primary' };
    const fixture = createAscFixture();
    let completeInsert!: (result: unknown) => void;
    fixture.asc.plugin.executeMethod = (name, args, callback) => {
      if (name === 'GetCurrentContentControl') {
        callback(null);
        return true;
      }
      expect(name).toBe('AddContentControl');
      fixture.methodCalls.push([name, args]);
      completeInsert = callback;
      return true;
    };
    const controller = new AbortController();
    const operation = createOnlyOfficeBridge(fixture.asc).insertBinding(reference, controller.signal);

    await vi.waitFor(() => expect(completeInsert).toBeTypeOf('function'));
    controller.abort();
    completeInsert({ InternalId: 'reference-control', Tag: reference.controlTag });
    await operation;

    expect(fixture.methodCalls).toEqual([['AddContentControl', [2, expect.objectContaining({ Tag: reference.controlTag })]]]);
    expect(fixture.callCommands).toEqual([]);
  });

  it('确认快照使用文档实际属性，只从绑定关联不可写入 DOCX 的 ID', () => {
    const bridge = createOnlyOfficeBridge(createAscFixture().asc);
    const controls: OfficeContentControl[] = [
      {
        Alias: '结果',
        ControlConfig: { placeholder: '请输入结果' },
        ControlRole: 'PRIMARY',
        ControlType: 'text',
        InternalId: 'id',
        Lock: 'unlocked',
        Tag: primary.controlTag,
      },
    ];
    expect(bridge.snapshotControls([primary], controls)).toEqual([
      {
        bindingId: 'primary',
        controlAlias: '结果',
        controlConfig: { placeholder: '请输入结果' },
        controlRole: 'PRIMARY',
        controlTag: primary.controlTag,
        controlType: 'TEXT',
        fieldVersionId: 'version',
        primaryBindingId: null,
      },
    ]);
  });

  it('确认快照保留步骤归属和重复组身份', () => {
    const bridge = createOnlyOfficeBridge(createAscFixture().asc);
    const repeat = {
      ...primary,
      controlRole: 'REPEAT',
      controlTag: 'eln.repeat.results.prototype.result',
      primaryBindingId: null,
      repeatGroupCode: 'results',
      repeatPrototypeRowId: 'prototype',
      stepId: 'step-test',
    };
    const controls: OfficeContentControl[] = [
      {
        Alias: '结果',
        ControlConfig: { placeholder: '请输入结果' },
        ControlRole: 'REPEAT',
        ControlType: 'text',
        InternalId: 'repeat-id',
        Lock: 'unlocked',
        Tag: repeat.controlTag,
      },
    ];

    expect(bridge.snapshotControls([repeat], controls)).toEqual([
      expect.objectContaining({
        repeatGroupCode: 'results',
        repeatPrototypeRowId: 'prototype',
        stepId: 'step-test',
      }),
    ]);
  });

  it('升版复制快照显式允许公式结果主控件和引用继承完整锁', () => {
    const bridge = createOnlyOfficeBridge(createAscFixture().asc);
    const lockedReferenceBinding = {
      ...primary,
      controlRole: 'REFERENCE',
      controlTag: 'eln.ref.result.copy',
      id: 'reference',
      primaryBindingId: primary.id,
    };
    const lockedPrimary: OfficeContentControl = {
      Alias: '结果',
      ControlConfig: { placeholder: '请输入结果' },
      ControlRole: 'PRIMARY',
      ControlType: 'text',
      InternalId: 'locked-primary',
      Lock: 'sdtContentLocked',
      Tag: primary.controlTag,
    };
    const lockedReference: OfficeContentControl = {
      ...lockedPrimary,
      ControlRole: 'REFERENCE',
      InternalId: 'locked-reference',
      Tag: lockedReferenceBinding.controlTag,
    };

    expect(() => bridge.snapshotControls([primary], [lockedPrimary])).toThrow('控件属性与绑定不一致');
    expect(
      bridge.snapshotControls([primary, lockedReferenceBinding], [lockedPrimary, lockedReference], {
        inheritedFormulaLockBindingIds: new Set([primary.id, lockedReferenceBinding.id]),
      }),
    ).toEqual([
      expect.objectContaining({ bindingId: 'primary', controlRole: 'PRIMARY' }),
      expect.objectContaining({ bindingId: 'reference', controlRole: 'REFERENCE' }),
    ]);
    expect(() =>
      bridge.snapshotControls([primary, lockedReferenceBinding], [lockedPrimary, lockedReference], { inheritedFormulaLockBindingIds: new Set([primary.id]) }),
    ).toThrow('控件属性与绑定不一致');
  });

  it('快照校验将非复选框历史空 placeholder 规范为控件别名', () => {
    const historical = {
      ...primary,
      controlConfig: { defaultValue: '', placeholder: '' },
    };
    const control: OfficeContentControl = {
      Alias: '结果',
      ControlConfig: { placeholder: '结果' },
      ControlRole: 'PRIMARY',
      ControlType: 'text',
      InternalId: 'historical-placeholder',
      Lock: 'unlocked',
      Tag: historical.controlTag,
    };

    expect(createOnlyOfficeBridge(createAscFixture().asc).snapshotControls([historical], [control])).toEqual([
      {
        bindingId: 'primary',
        controlAlias: '结果',
        controlConfig: { placeholder: '结果' },
        controlRole: 'PRIMARY',
        controlTag: primary.controlTag,
        controlType: 'TEXT',
        fieldVersionId: 'version',
        primaryBindingId: null,
      },
    ]);
  });

  it('CHECKBOX 升级确认快照忽略字段 defaultChecked 与文档当前勾选态差异', () => {
    const checkbox = {
      ...primary,
      controlConfig: { defaultChecked: true },
      controlType: 'CHECKBOX',
      fieldVersionId: 'version-2',
    };
    const controls: OfficeContentControl[] = [
      {
        Alias: '结果',
        ControlConfig: { defaultChecked: false },
        ControlRole: 'PRIMARY',
        ControlType: 'checkbox',
        InternalId: 'checkbox-control',
        Lock: 'unlocked',
        Tag: checkbox.controlTag,
      },
    ];

    expect(createOnlyOfficeBridge(createAscFixture().asc).snapshotControls([checkbox], controls)).toEqual([
      {
        bindingId: checkbox.id,
        controlAlias: '结果',
        controlConfig: {},
        controlRole: 'PRIMARY',
        controlTag: checkbox.controlTag,
        controlType: 'CHECKBOX',
        fieldVersionId: 'version-2',
        primaryBindingId: null,
      },
    ]);
  });

  it('command 实际枚举结果可直接生成规范化快照，比较不受对象键顺序影响', () => {
    const apiFixture = makeApiFixture();
    const dropdownControl = apiFixture.makeControl('dropdown', primary.controlTag);
    dropdownControl.alias = '结果';
    dropdownControl.placeholder = '请选择结果';
    dropdownControl.options = [
      { display: '合格', value: 'PASS' },
      { display: '不合格', value: 'FAIL' },
    ];
    dropdownControl.elements = [{ text: '合格' }];
    apiFixture.document.controls = [dropdownControl];
    installOnlyOfficeGlobals(apiFixture.Api, { elnTemplateList: {} });
    const enumerated = JSON.parse(listContentControlsCommand()) as { controls: OfficeContentControl[] };
    const dropdownBinding: TemplateBinding = {
      ...primary,
      controlConfig: {
        defaultValue: 'PASS',
        options: [
          { value: 'PASS', display: '合格' },
          { value: 'FAIL', display: '不合格' },
        ],
        placeholder: '请选择结果',
      },
      controlType: 'DROPDOWN',
    };

    expect(createOnlyOfficeBridge(createAscFixture().asc).snapshotControls([dropdownBinding], enumerated.controls)).toEqual([
      expect.objectContaining({
        bindingId: 'primary',
        controlConfig: {
          defaultValue: 'PASS',
          options: [
            { display: '合格', value: 'PASS' },
            { display: '不合格', value: 'FAIL' },
          ],
          placeholder: '请选择结果',
        },
      }),
    ]);
  });

  it('引用快照不要求或提交无法从结构属性证明的默认值', () => {
    const reference = {
      ...primary,
      controlConfig: { defaultValue: '原始默认值', placeholder: '请输入结果' },
      controlRole: 'REFERENCE',
      controlTag: 'eln.ref.result.ref1',
      id: 'reference',
      primaryBindingId: primary.id,
    };
    const controls: OfficeContentControl[] = [
      {
        Alias: '结果',
        ControlConfig: { placeholder: '请输入结果' },
        ControlRole: 'REFERENCE',
        ControlType: 'text',
        InternalId: 'reference-control',
        Lock: 'contentLocked',
        Tag: reference.controlTag,
      },
    ];

    expect(createOnlyOfficeBridge(createAscFixture().asc).snapshotControls([reference], controls)[0]?.controlConfig).toEqual({ placeholder: '请输入结果' });
  });

  it('实际枚举属性缺失或与绑定期望不一致时拒绝生成确认快照', () => {
    const bridge = createOnlyOfficeBridge(createAscFixture().asc);
    expect(() =>
      bridge.snapshotControls(
        [primary],
        [
          {
            Alias: '错误名称',
            ControlConfig: { placeholder: '请输入结果' },
            ControlRole: 'PRIMARY',
            ControlType: 'text',
            InternalId: 'id',
            Lock: 'unlocked',
            Tag: primary.controlTag,
          },
        ],
      ),
    ).toThrow('控件属性与绑定不一致');
    expect(() => bridge.snapshotControls([primary], [{ Alias: '结果', ControlType: 'text', InternalId: 'id', Tag: primary.controlTag }])).toThrow(
      '控件枚举属性不完整',
    );
  });

  it('使用后端 DROPDOWN/MULTILINE 控件枚举而不是字段数据类型枚举', async () => {
    const dropdown = { ...primary, controlType: 'DROPDOWN', controlConfig: { options: ['合格', '不合格'], defaultValue: '合格' } };
    const fixture = createAscFixture({ commandResults: [{ ok: true }, { ok: true }] });
    await createOnlyOfficeBridge(fixture.asc).insertBinding(dropdown);
    expect(fixture.callCommands[0]).toMatchObject({
      command: insertTypedControlCommand,
      payload: expect.objectContaining({ controlType: 'dropdown', selectedIndex: 0 }),
    });
  });

  it('新建多行主控件不再按已有控件重配类型', async () => {
    const multiline = { ...primary, controlType: 'MULTILINE', controlConfig: { placeholder: '请输入检验结果' } };
    const fixture = createAscFixture({ commandResults: [{ ok: true }] });

    await createOnlyOfficeBridge(fixture.asc).insertBinding(multiline);

    expect(fixture.callCommands).toEqual([
      expect.objectContaining({
        command: insertTypedControlCommand,
        payload: expect.objectContaining({ controlType: 'multiline' }),
      }),
    ]);
  });

  it('快照把 ONLYOFFICE 9.4 枚举为 TEXT 的块级绑定规范为 MULTILINE', () => {
    const multiline = { ...primary, controlType: 'MULTILINE', controlConfig: { placeholder: '请输入检验结果' } };
    const control: OfficeContentControl = {
      Alias: '结果',
      ControlConfig: { placeholder: '请输入检验结果' },
      ControlRole: 'PRIMARY',
      ControlType: 'text',
      InternalId: 'multiline-control',
      Lock: 'unlocked',
      Tag: multiline.controlTag,
    };

    expect(createOnlyOfficeBridge(createAscFixture().asc).snapshotControls([multiline], [control])).toEqual([
      expect.objectContaining({ controlType: 'MULTILINE' }),
    ]);
  });

  it('日期时间绑定把字段版本格式和完整默认值传给插入命令', async () => {
    const datetime = {
      ...primary,
      controlType: 'DATE',
      controlConfig: {
        dateFormat: 'yyyy-MM-dd HH:mm:ss',
        defaultValue: '2026-08-01 14:35:27',
      },
    };
    const fixture = createAscFixture({ commandResults: [{ ok: true }, { ok: true }] });

    await createOnlyOfficeBridge(fixture.asc).insertBinding(datetime);

    expect(fixture.callCommands[0]).toMatchObject({
      command: insertTypedControlCommand,
      payload: expect.objectContaining({
        controlType: 'date',
        dateFormat: 'yyyy-MM-dd HH:mm:ss',
        defaultValue: '2026-08-01 14:35:27',
      }),
    });
  });
});
