import type { FormulaControlSnapshot, TemplateAnnotationApiContext, TemplateAnnotationClient } from '../api/template-annotation';
import type { PluginContext } from '../domain/plugin-context';
import type {
  TemplateBinding,
  TemplateChangeCheck,
  TemplateControlSnapshot,
  CrossTemplateScalarCandidate,
  CrossTemplateSourceTemplate,
  TemplateFieldOption,
  TemplateCheckboxGroup,
  TemplateCheckboxGroupDraft,
  TemplatePrivateFieldDraft,
  TemplateRepairContext,
  TemplateRepeatGroup,
  TemplateStepConfiguration,
} from '../domain/types';
import type { OnlyOfficeBridge } from '../onlyoffice/bridge';
import type {
  FormulaBindingLockSnapshot,
  FormulaBindingRequest,
  FormulaResultLockSnapshot,
  TemplateFormulaBinding,
  TemplateFormulaVersion,
} from '../domain/formula-mapping';

import { TemplateAnnotationApiError, templateAnnotationClient } from '../api/template-annotation';
import { ref } from 'vue';
import { generateOpaqueId } from '../domain/tags';
import { buildFormulaBindingRequest } from '../domain/formula-mapping';
import { toTemplateFieldOptions } from '../domain/types';
import { useAnnotationStore } from '../stores/annotation';
import { usePluginContextStore } from '../stores/plugin-context';

const SDK_READY_TIMEOUT_MS = 5_000;

function message(error: unknown): string {
  return error instanceof Error ? error.message : String(error);
}
function countTag(controls: Array<{ Tag?: string }>, tag: string): number {
  return controls.filter((item) => item.Tag === tag).length;
}
function isExpiredTicket(error: unknown): boolean {
  return error instanceof TemplateAnnotationApiError && (error.status === 401 || error.status === 403);
}
function isCrossTemplateReadOnlyBinding(binding: TemplateBinding): boolean {
  return binding.readOnly === true && binding.valueSource === 'CROSS_TEMPLATE';
}

class FormulaConfirmAmbiguousError extends Error {
  constructor() {
    super('公式升级确认状态待确认，请刷新后继续');
  }
}

export function useTemplateAnnotator(bridge: OnlyOfficeBridge, client: TemplateAnnotationClient = templateAnnotationClient) {
  const store = useAnnotationStore();
  const contextStore = usePluginContextStore();
  const bindingState = ref<TemplateBinding[]>([]);
  const fieldState = ref<TemplateFieldOption[]>([]);
  const checkboxGroupState = ref<TemplateCheckboxGroup[]>([]);
  const crossTemplateScalarCandidateState = ref<CrossTemplateScalarCandidate[]>([]);
  const crossTemplateSourceTemplateState = ref<CrossTemplateSourceTemplate[]>([]);
  const repeatGroupState = ref<TemplateRepeatGroup[]>([]);
  const stepConfigurationState = ref<null | TemplateStepConfiguration>(null);
  const currentStepIdState = ref<null | string>(null);
  const formulaBindingState = ref<TemplateFormulaBinding[]>([]);
  const formulaVersionState = ref<TemplateFormulaVersion[]>([]);
  const pendingRepair = ref<null | TemplateRepairContext>(null);
  let disposed = false;
  let inFlight = false;
  let instanceContext: null | PluginContext = null;
  let readyTimeout: ReturnType<typeof setTimeout> | undefined;
  let trackRevisionsDisabled = false;
  let trackRevisionsPreparation: Promise<void> | undefined;
  const abortController = new AbortController();
  const pendingCheckedChanges = new Map<string, TemplateChangeCheck>();
  let pendingRepairConfirmation: null | TemplateRepairContext = null;

  function apiContext(): TemplateAnnotationApiContext {
    if (disposed || !instanceContext?.ticket) throw new Error('插件运行上下文不可用');
    return { apiBaseUrl: instanceContext.apiBaseUrl, ticket: instanceContext.ticket, signal: abortController.signal };
  }

  function setWorking(text: string, initializing = false): void {
    if (disposed) return;
    store.setStatus(initializing ? 'initializing' : 'working');
    store.setOperation({ kind: 'working', message: text });
  }

  function clearReadyTimeout(): void {
    if (readyTimeout !== undefined) clearTimeout(readyTimeout);
    readyTimeout = undefined;
  }

  async function prepareDocumentMutation(): Promise<void> {
    if (trackRevisionsDisabled) return;
    if (!trackRevisionsPreparation) {
      trackRevisionsPreparation = bridge
        .disableTrackRevisions()
        .then(() => {
          if (!disposed) trackRevisionsDisabled = true;
        })
        .finally(() => {
          trackRevisionsPreparation = undefined;
        });
    }
    await trackRevisionsPreparation;
  }

  function applyStepConfiguration(configuration: TemplateStepConfiguration): void {
    stepConfigurationState.value = configuration;
    if (configuration.singleStep) {
      currentStepIdState.value = configuration.steps[0]?.id ?? null;
      return;
    }
    if (!configuration.steps.some((step) => step.id === currentStepIdState.value)) currentStepIdState.value = null;
  }

  function requireCurrentStepId(): string {
    if (currentStepIdState.value) return currentStepIdState.value;
    if (stepConfigurationState.value?.singleStep) throw new Error('模板步骤配置无效');
    throw new Error('多步骤模板必须选择当前步骤');
  }

  function setCurrentStepId(stepId: null | string): void {
    if (stepId === null) {
      currentStepIdState.value = null;
      return;
    }
    if (!stepConfigurationState.value?.steps.some((step) => step.id === stepId)) throw new Error('当前步骤不存在');
    currentStepIdState.value = stepId;
  }

  function expireTicket(error: unknown): void {
    if (isExpiredTicket(error)) {
      instanceContext = null;
      contextStore.reset();
      store.setStatus('error');
    }
  }

  async function exclusive<T>(operation: () => Promise<T>): Promise<T> {
    if (disposed) throw new Error('插件已关闭');
    if (inFlight) throw new Error('已有操作正在执行');
    if (!instanceContext) throw new Error('插件运行上下文不可用');
    inFlight = true;
    try {
      return await operation();
    } catch (error) {
      if (!disposed) {
        expireTicket(error);
        if (!isExpiredTicket(error) && store.status === 'working') {
          store.setStatus('ready');
          store.setOperation({ kind: 'error', message: message(error) });
        }
      }
      throw error;
    } finally {
      inFlight = false;
    }
  }

  function bindingsForSnapshots(snapshots: TemplateControlSnapshot[]): TemplateBinding[] {
    return snapshots.map((snapshot) => {
      const current = bindingState.value.find((item) => item.id === snapshot.bindingId);
      if (!current) throw new Error(`控件快照绑定不存在：${snapshot.bindingId}`);
      return {
        ...current,
        controlAlias: snapshot.controlAlias ?? current.controlAlias,
        controlConfig: snapshot.controlConfig ?? current.controlConfig,
        controlRole: snapshot.controlRole ?? current.controlRole,
        controlTag: snapshot.controlTag,
        controlType: snapshot.controlType ?? current.controlType,
        fieldVersionId: snapshot.fieldVersionId ?? current.fieldVersionId,
        primaryBindingId: snapshot.primaryBindingId ?? null,
      };
    });
  }

  function formulaResultBinding(formula: TemplateFormulaBinding, bindings = bindingState.value): TemplateBinding {
    const result = bindings.find((item) => item.id === formula.resultBindingId && item.status === 'ACTIVE' && item.controlRole === 'PRIMARY');
    if (!result) throw new Error('公式结果字段不存在或不是 ACTIVE 主字段');
    return result;
  }

  function formulaResultControls(resultBindingId: string, lock: string, bindings = bindingState.value): FormulaControlSnapshot[] {
    const primary = bindings.find((item) => item.id === resultBindingId && item.status === 'ACTIVE' && item.controlRole === 'PRIMARY');
    if (!primary) throw new Error('公式结果字段不存在或不是 ACTIVE 主字段');
    return [primary, ...bindings.filter((item) => item.status === 'ACTIVE' && item.controlRole === 'REFERENCE' && item.primaryBindingId === primary.id)].map(
      (item) => ({ bindingId: item.id, controlTag: item.controlTag, controlRole: item.controlRole, lock }),
    );
  }

  function expectedFormulaVersion(expectedVersions: Record<string, number>, bindingId: string): number {
    const version = expectedVersions[bindingId];
    if (typeof version !== 'number') throw new Error('公式映射预检版本缺失');
    return version;
  }

  function formulaOriginalLockControls(
    resultBindingId: string,
    snapshot: FormulaResultLockSnapshot,
    bindings = bindingState.value,
  ): FormulaBindingLockSnapshot[] {
    const expected = formulaResultControls(resultBindingId, '', bindings);
    const locksByTag = new Map(snapshot.locks.map((item) => [item.tag, item.lock]));
    if (
      snapshot.primaryTag !== expected[0]?.controlTag ||
      locksByTag.size !== expected.length ||
      snapshot.locks.length !== expected.length ||
      expected.some((item) => !locksByTag.has(item.controlTag))
    ) {
      throw new Error('公式结果锁快照与当前绑定不一致');
    }
    return expected.map((item) => ({ ...item, lock: locksByTag.get(item.controlTag)! }));
  }

  function savedFormulaResultLockSnapshot(formula: TemplateFormulaBinding, bindings = bindingState.value): FormulaResultLockSnapshot {
    const saved = formula.jieguoSuoKuaizhao;
    const expected = formulaResultControls(formula.resultBindingId, '', bindings);
    if (!Array.isArray(saved)) throw new Error('公式 PENDING 缺少服务端原始锁快照');
    const snapshots = new Map(saved.map((item) => [item.bindingId, item]));
    if (
      snapshots.size !== expected.length ||
      saved.length !== expected.length ||
      expected.some((item) => {
        const snapshot = snapshots.get(item.bindingId);
        return !snapshot || snapshot.controlTag !== item.controlTag || snapshot.controlRole !== item.controlRole || typeof snapshot.lock !== 'string';
      })
    ) {
      throw new Error('公式 PENDING 服务端原始锁快照不完整');
    }
    return {
      primaryTag: expected[0]!.controlTag,
      locks: expected.map((item) => ({ tag: item.controlTag, lock: snapshots.get(item.bindingId)!.lock })),
    };
  }

  async function reconcilePendingFormulaBindings(formulas: TemplateFormulaBinding[], bindings: TemplateBinding[]): Promise<boolean> {
    let needsReconciliation = false;
    for (const formula of formulas.filter((item) => item.status === 'PENDING')) {
      try {
        const result = formulaResultBinding(formula, bindings);
        if (await bridge.isFormulaResultLocked(result.controlTag)) {
          await client.activateFormulaBinding(apiContext(), formula.id, {
            controls: formulaResultControls(formula.resultBindingId, 'sdtContentLocked', bindings),
            expectedVersion: formula.version,
            operationCode: formula.operationCode,
          });
          formula.status = 'ACTIVE';
        } else {
          await prepareDocumentMutation();
          if (disposed) return needsReconciliation;
          await bridge.restoreFormulaResultLocks(savedFormulaResultLockSnapshot(formula, bindings));
          await client.cancelFormulaBinding(apiContext(), formula.id, {
            expectedVersion: formula.version,
            operationCode: formula.operationCode,
            reason: 'PENDING_LOCK_INCOMPLETE',
          });
          formula.status = 'CANCELLED';
        }
      } catch (error) {
        if (isExpiredTicket(error)) throw error;
        needsReconciliation = true;
        store.setOperation({ kind: 'error', message: `公式待确认需对账：${message(error)}` });
      }
    }
    return needsReconciliation;
  }

  async function reconcilePendingCrossTemplateBindings(bindings: TemplateBinding[], controls: Array<{ InternalId?: string; Lock?: string; Tag?: string }>): Promise<boolean> {
    let needsReconciliation = false;
    for (const binding of bindings.filter((item) => item.status === 'PENDING' && isCrossTemplateReadOnlyBinding(item))) {
      try {
        const matches = controls.filter((control) => control.Tag === binding.controlTag);
        if (matches.length === 1 && matches[0]?.Lock === 'sdtContentLocked') {
          await client.activateBinding(apiContext(), binding.id, { operationCode: generateOpaqueId('cross-template-reconcile') });
          binding.status = 'ACTIVE';
          continue;
        }
        if (matches.length > 1 || (matches.length === 1 && !matches[0]?.InternalId)) {
          throw new Error('跨模板 PENDING 控件无法唯一恢复');
        }
        if (matches[0]?.InternalId) await bridge.deleteControl(matches[0].InternalId);
        await client.cancelBinding(apiContext(), binding.id, {
          operationCode: generateOpaqueId('cross-template-cancel'),
          reason: 'PENDING_LOCK_INCOMPLETE',
        });
        binding.status = 'CANCELLED';
      } catch (error) {
        if (isExpiredTicket(error)) throw error;
        needsReconciliation = true;
        store.setOperation({ kind: 'error', message: `跨模板字段待确认需对账：${message(error)}` });
      }
    }
    return needsReconciliation;
  }

  async function confirmCopiedFormulaBindings(
    formulas: { bindings: TemplateFormulaBinding[]; copiedPendingBindingIds?: string[]; upgradeOperationCode?: string },
    bindings: TemplateBinding[],
  ): Promise<void> {
    const copiedIds = new Set(formulas.copiedPendingBindingIds ?? []);
    if (!copiedIds.size) return;
    if (!formulas.upgradeOperationCode) throw new Error('公式复制 PENDING 缺少升级操作码');
    const copied = formulas.bindings.filter((item) => copiedIds.has(item.id));
    if (copied.length !== copiedIds.size || copied.some((item) => item.status !== 'PENDING')) throw new Error('公式复制 PENDING 上下文不一致');
    for (const formula of copied) {
      const result = formulaResultBinding(formula, bindings);
      if (!(await bridge.isFormulaResultLocked(result.controlTag))) throw new Error('公式复制结果控件未完整锁定');
    }
    await client.confirmFormulaVersionCopy(apiContext(), {
      controls: copied.flatMap((item) => formulaResultControls(item.resultBindingId, 'sdtContentLocked', bindings)),
      expectedVersions: Object.fromEntries(copied.map((item) => [item.id, item.version])),
      upgradeOperationCode: formulas.upgradeOperationCode,
    });
    formulas.bindings = formulas.bindings.map((item) => (copiedIds.has(item.id) ? { ...item, status: 'ACTIVE' } : item));
  }

  function confirmation(check: TemplateChangeCheck, controls: Array<{ Tag?: string }>) {
    return {
      operationCode: check.operationCode,
      expectedVersions: check.expectedVersions,
      controls: bridge.snapshotControls(bindingsForSnapshots(check.controls), controls),
      observedControlTags: controls.flatMap((control) => (typeof control.Tag === 'string' && control.Tag.trim().length > 0 ? [control.Tag] : [])),
    };
  }

  async function locateControl(controls: Array<{ InternalId?: string; Tag?: string }>, locateTag: string): Promise<void> {
    const matches = controls.filter((item) => item.Tag === locateTag);
    if (matches.length === 0) throw new Error(`定位目标 Tag 不存在：${locateTag}`);
    if (matches.length > 1) throw new Error(`定位目标 Tag 出现多次：${locateTag}`);
    if (!matches[0]?.InternalId) throw new Error(`定位目标控件缺少 InternalId：${locateTag}`);
    await bridge.selectControl(matches[0].InternalId, abortController.signal);
  }

  async function confirmCopiedBindings(
    context: Awaited<ReturnType<TemplateAnnotationClient['fetchAnnotationContext']>>,
    formulas: Awaited<ReturnType<TemplateAnnotationClient['fetchFormulaMappings']>>,
    controls: Array<{ Tag?: string }>,
  ): Promise<boolean> {
    const copiedIds = new Set(context.copiedPendingBindingIds || []);
    const pending = [...copiedIds].map((id) => {
      const binding = context.bindings.find((item) => item.id === id);
      if (!binding || binding.status !== 'PENDING') throw new Error('复制绑定上下文不一致');
      return binding;
    });
    if (!pending.length) return false;
    if (!context.upgradeOperationCode) throw new Error('复制绑定缺少升版操作码');
    const inheritedFormulaLockBindingIds = new Set(formulas.inheritedFormulaLockBindingIds ?? []);
    const snapshots = bridge.snapshotControls(pending, controls, { inheritedFormulaLockBindingIds });
    if (snapshots.length !== pending.length) throw new Error('升版复制控件快照不完整');
    await client.confirmVersionCopy(apiContext(), {
      upgradeOperationCode: context.upgradeOperationCode,
      expectedVersions: Object.fromEntries(pending.map((item) => [item.id, item.version])),
      controls: snapshots,
    });
    if (disposed) return true;
    context.bindings = context.bindings.map((item) => (copiedIds.has(item.id) ? { ...item, status: 'ACTIVE' } : item));
    return true;
  }

  async function initialize(): Promise<void> {
    clearReadyTimeout();
    setWorking('正在读取模板标注...', true);
    try {
      const locateTag = instanceContext?.locateControlTag;
      if (locateTag) {
        const controls = await bridge.listControls();
        if (disposed) return;
        await locateControl(controls, locateTag);
        if (disposed) return;
        store.setControls(controls);
        store.setStatus('ready');
        store.setOperation({ kind: 'success', message: '目标控件已定位' });
        return;
      }
      await prepareDocumentMutation();
      if (disposed) return;
      const controls = await bridge.listControls();
      if (disposed) return;
      const context = await client.fetchAnnotationContext(apiContext());
      if (disposed) return;
      let formulaContext = await client.fetchFormulaMappings(apiContext());
      if (disposed) return;
      const copiedBindingsConfirmed = await confirmCopiedBindings(context, formulaContext, controls);
      if (disposed) return;
      if (copiedBindingsConfirmed) formulaContext = await client.fetchFormulaMappings(apiContext());
      if (disposed) return;
      bindingState.value = [...context.bindings];
      fieldState.value = toTemplateFieldOptions(context.fields);
      checkboxGroupState.value = [...(context.checkboxGroups ?? [])];
      repeatGroupState.value = [...(context.repeatGroups ?? [])];
      applyStepConfiguration(context.stepConfiguration);
      formulaBindingState.value = [...formulaContext.bindings];
      formulaVersionState.value = [...formulaContext.versions];
      const crossTemplateNeedsReconciliation = await reconcilePendingCrossTemplateBindings(bindingState.value, controls);
      if (disposed) return;
      await confirmCopiedFormulaBindings(formulaContext, bindingState.value);
      if (disposed) return;
      formulaBindingState.value = [...formulaContext.bindings];
      const formulaNeedsReconciliation = await reconcilePendingFormulaBindings(formulaBindingState.value, bindingState.value);
      if (disposed) return;
      store.setControls(controls);
      store.setStatus('ready');
      if (!formulaNeedsReconciliation && !crossTemplateNeedsReconciliation) store.setOperation({ kind: 'success', message: '模板标注已加载' });
      if (instanceContext?.repairBindingId) await prepareRepairMode(instanceContext.repairBindingId, controls);
    } catch (error) {
      if (!disposed) {
        expireTicket(error);
        store.setStatus('error');
        store.setOperation({ kind: 'error', message: `读取模板标注失败：${message(error)}` });
      }
      throw error;
    }
  }

  async function prepareRepairMode(bindingId: string, controls: Array<{ InternalId?: string; Tag?: string }>): Promise<void> {
    const current = bindingState.value.find((item) => item.id === bindingId && item.status === 'ACTIVE');
    if (!current) throw new Error('修复绑定不存在或状态已变化');
    const matches = controls.filter((item) => item.Tag === current.controlTag);
    if (matches.length > 1) throw new Error('修复目标 Tag 在文档中出现多次');
    if (matches.length === 1) {
      if (matches[0]?.InternalId) await bridge.selectControl(matches[0].InternalId, abortController.signal);
      if (disposed) return;
      pendingRepair.value = null;
      store.setOperation({ kind: 'success', message: '目标控件已定位' });
      return;
    }
    const check = await client.fetchRepairContext(apiContext(), bindingId);
    if (disposed) return;
    if (check.binding.id !== bindingId || check.binding.controlTag !== current.controlTag) throw new Error('修复上下文与目标绑定不一致');
    pendingRepair.value = check;
    store.setOperation({ kind: 'success', message: '请在文档中选择位置后恢复控件' });
  }

  async function refresh(): Promise<void> {
    return exclusive(async () => {
      setWorking('正在刷新模板标注...');
      try {
        const controls = await bridge.listControls();
        if (disposed) return;
        const locateTag = instanceContext?.locateControlTag;
        if (locateTag) {
          await locateControl(controls, locateTag);
          if (disposed) return;
          store.setControls(controls);
          store.setStatus('ready');
          store.setOperation({ kind: 'success', message: '目标控件已定位' });
          return;
        }
        const context = await client.fetchAnnotationContext(apiContext());
        if (disposed) return;
        const formulaContext = await client.fetchFormulaMappings(apiContext());
        if (disposed) return;
        bindingState.value = [...context.bindings];
        fieldState.value = toTemplateFieldOptions(context.fields);
        checkboxGroupState.value = [...(context.checkboxGroups ?? [])];
        repeatGroupState.value = [...(context.repeatGroups ?? [])];
        applyStepConfiguration(context.stepConfiguration);
        formulaBindingState.value = [...formulaContext.bindings];
        formulaVersionState.value = [...formulaContext.versions];
        store.setControls(controls);
        store.setStatus('ready');
        store.setOperation({ kind: 'success', message: '模板标注已刷新' });
      } catch (error) {
        if (!disposed) store.setOperation({ kind: 'error', message: `刷新失败：${message(error)}` });
        throw error;
      }
    });
  }

  function loadCrossTemplateSourceTemplates(): Promise<void> {
    return exclusive(async () => {
      setWorking('正在读取来源模板...');
      crossTemplateScalarCandidateState.value = [];
      const templates = await client.fetchCrossTemplateSourceTemplates(apiContext());
      if (disposed) return;
      crossTemplateSourceTemplateState.value = Array.isArray(templates) ? templates : [];
      store.setStatus('ready');
      store.setOperation({ kind: 'success', message: '来源模板已加载' });
    });
  }

  function loadCrossTemplateScalarCandidates(sourceTemplateId: string): Promise<void> {
    return exclusive(async () => {
      setWorking('正在读取模板字段...');
      crossTemplateScalarCandidateState.value = [];
      const candidates = await client.fetchCrossTemplateScalarCandidates(apiContext(), sourceTemplateId);
      if (disposed) return;
      crossTemplateScalarCandidateState.value = Array.isArray(candidates) ? candidates : [];
      store.setStatus('ready');
      store.setOperation({ kind: 'success', message: '模板字段已加载' });
    });
  }

  async function insertReserved(reserve: () => Promise<TemplateBinding>, referencePrimaryTag?: string, readOnly = false): Promise<void> {
    let reserved: TemplateBinding | undefined;
    let insertedControl: { InternalId?: string; Lock?: string; Tag?: string } | undefined;
    let readOnlyInsertAttempted = false;
    let activationAttempted = false;
    let confirmedControls: Array<{ InternalId?: string; Lock?: string; Tag?: string }> | undefined;
    try {
      reserved = await reserve();
      if (disposed) return;
      const before = await bridge.listControls();
      if (disposed) return;
      if (countTag(before, reserved.controlTag) !== 0) throw new Error('目标 Tag 已存在');
      await prepareDocumentMutation();
      if (disposed) return;
      if (referencePrimaryTag) await bridge.insertReference(reserved, referencePrimaryTag, abortController.signal);
      else if (readOnly) {
        readOnlyInsertAttempted = true;
        await bridge.insertReadOnlyBinding(reserved, abortController.signal);
      }
      else await bridge.insertBinding(reserved, abortController.signal);
      // 销毁后不再补偿写 DOCX/API；PENDING 残留由重开对账与模板体检处理。
      if (disposed) return;
      const after = await bridge.listControls();
      if (disposed) return;
      if (countTag(after, reserved.controlTag) !== 1) throw new Error('插入后 Tag 计数不是 1');
      if (readOnly) {
        insertedControl = after.find((control) => control.Tag === reserved!.controlTag);
        if (insertedControl?.Lock !== 'sdtContentLocked') throw new Error('跨模板字段控件未锁定');
      }
      confirmedControls = after;
      activationAttempted = true;
      await client.activateBinding(apiContext(), reserved.id, { operationCode: generateOpaqueId('activate') });
      if (disposed) return;
      bindingState.value = [...bindingState.value, { ...reserved, status: 'ACTIVE' }];
      store.setControls(after);
      store.setStatus('ready');
      store.setOperation({ kind: 'success', message: `已标注字段：${reserved.controlAlias || reserved.fieldCode}` });
    } catch (error) {
      let rollbackError: unknown;
      let preserveForReconciliation = false;
      if (!disposed && readOnly && activationAttempted && reserved && !isExpiredTicket(error)) {
        try {
          const context = await client.fetchAnnotationContext(apiContext());
          if (disposed) return;
          const persisted = context.bindings.find((binding) => binding.id === reserved!.id);
          if (persisted?.status === 'ACTIVE' && isCrossTemplateReadOnlyBinding(persisted)) {
            bindingState.value = [...bindingState.value.filter((binding) => binding.id !== persisted.id), persisted];
            store.setControls(confirmedControls ?? []);
            store.setStatus('ready');
            store.setOperation({ kind: 'success', message: `已标注字段：${persisted.controlAlias || persisted.fieldCode}` });
            return;
          }
          if (!persisted || persisted.status !== 'PENDING') {
            preserveForReconciliation = true;
            rollbackError = new Error('跨模板字段激活结果待对账');
          }
        } catch (statusError) {
          if (isExpiredTicket(statusError)) throw statusError;
          preserveForReconciliation = true;
          rollbackError = new Error('跨模板字段激活结果待对账');
        }
      }
      if (!disposed && readOnlyInsertAttempted && !insertedControl && reserved && !isExpiredTicket(error)) {
        try {
          const controls = await bridge.listControls();
          const matches = controls.filter((control) => control.Tag === reserved!.controlTag);
          if (matches.length > 1 || (matches.length === 1 && !matches[0]?.InternalId)) {
            throw new Error('跨模板失败控件无法唯一补偿');
          }
          insertedControl = matches[0];
        } catch (enumerateError) {
          rollbackError = enumerateError;
        }
      }
      if (!disposed && insertedControl?.InternalId && !rollbackError && !isExpiredTicket(error)) {
        try {
          await bridge.deleteControl(insertedControl.InternalId);
        } catch (deleteError) {
          rollbackError = deleteError;
        }
      }
      if (!disposed && reserved?.status === 'PENDING' && !rollbackError && !isExpiredTicket(error)) {
        try {
          await client.cancelBinding(apiContext(), reserved.id, { operationCode: generateOpaqueId('cancel'), reason: message(error) });
        } catch {
          /* 保留原始错误 */
        }
      }
      if (!disposed) {
        store.setStatus(contextStore.context ? 'ready' : 'error');
        store.setOperation({
          kind: 'error',
          message: preserveForReconciliation
            ? message(rollbackError)
            : rollbackError
              ? `标注失败，控件删除待对账：${message(rollbackError)}`
              : `标注失败：${message(error)}`,
        });
      }
      throw preserveForReconciliation ? rollbackError : error;
    }
  }

  function insertSelectedField(field: TemplateFieldOption, checkboxGroup?: TemplateCheckboxGroupDraft): Promise<void> {
    return exclusive(async () => {
      if (bindingState.value.some((item) => item.status === 'ACTIVE' && item.controlRole === 'PRIMARY' && item.fieldId === field.fieldId))
        throw new Error('该字段已有主控件，请插入引用');
      const stepId = requireCurrentStepId();
      setWorking('正在标注当前位置...');
      await insertReserved(() =>
        client.reservePrimaryBinding(apiContext(), {
          checkboxGroupCode: field.type === 'CHECKBOX' ? checkboxGroup?.groupCode.trim() || undefined : undefined,
          checkboxGroupMaxSelected: field.type === 'CHECKBOX' ? checkboxGroup?.maxSelected : undefined,
          checkboxGroupMinSelected: field.type === 'CHECKBOX' ? checkboxGroup?.minSelected : undefined,
          fieldId: field.fieldId,
          fieldVersionId: field.fieldVersionId,
          operationCode: generateOpaqueId('reserve'),
          repeatRequest: false,
          stepId,
        }),
      );
    });
  }

  function suggestCode(text: string): Promise<string> {
    return client.suggestCode(apiContext(), { text });
  }

  function insertRepeatField(field: TemplateFieldOption, group: TemplateRepeatGroup, checkboxGroup?: TemplateCheckboxGroupDraft): Promise<void> {
    return exclusive(async () => {
      const normalizedGroup = group.groupCode.trim();
      const { maxRows, minRows } = group;
      if (!/^[\w-]{1,80}$/u.test(normalizedGroup)) throw new Error('重复组编码无效');
      if (!Number.isInteger(minRows) || !Number.isInteger(maxRows) || minRows < 0 || maxRows < minRows || maxRows > 1000) throw new Error('重复组行数约束无效');
      const existingGroup = repeatGroupState.value.find((item) => item.groupCode === normalizedGroup);
      if (existingGroup && (!existingGroup.stepId || !stepConfigurationState.value?.steps.some((step) => step.id === existingGroup.stepId))) {
        throw new Error('重复组步骤归属无效');
      }
      const stepId = existingGroup?.stepId ?? requireCurrentStepId();
      setWorking('正在标注重复行...');
      await insertReserved(() =>
        client.reservePrimaryBinding(apiContext(), {
          checkboxGroupCode: field.type === 'CHECKBOX' ? checkboxGroup?.groupCode.trim() || undefined : undefined,
          checkboxGroupMaxSelected: field.type === 'CHECKBOX' ? checkboxGroup?.maxSelected : undefined,
          checkboxGroupMinSelected: field.type === 'CHECKBOX' ? checkboxGroup?.minSelected : undefined,
          fieldId: field.fieldId,
          fieldVersionId: field.fieldVersionId,
          maxRows,
          minRows,
          operationCode: generateOpaqueId('repeat-reserve'),
          repeatGroupCode: normalizedGroup,
          repeatPrototypeRowId: 'prototype',
          repeatRequest: true,
          stepId,
        }),
      );
      if (!disposed && !repeatGroupState.value.some((item) => item.groupCode === normalizedGroup)) {
        repeatGroupState.value = [...repeatGroupState.value, { groupCode: normalizedGroup, minRows, maxRows, stepId }].sort((left, right) =>
          left.groupCode.localeCompare(right.groupCode),
        );
      }
    });
  }

  function changeCheckboxGroup(bindingId: string, checkboxGroup: null | TemplateCheckboxGroupDraft, expectedVersion: number): Promise<void> {
    return exclusive(async () => {
      setWorking('正在更新选择组...');
      const updated = await client.changeCheckboxGroup(apiContext(), bindingId, {
        checkboxGroupCode: checkboxGroup?.groupCode.trim() || null,
        checkboxGroupMaxSelected: checkboxGroup?.maxSelected,
        checkboxGroupMinSelected: checkboxGroup?.minSelected,
        expectedVersion,
        operationCode: generateOpaqueId('checkbox-group'),
      });
      if (disposed) return;
      bindingState.value = bindingState.value.map((binding) => (binding.id === updated.id ? updated : binding));
      store.setStatus('ready');
      store.setOperation({ kind: 'success', message: updated.checkboxGroupCode ? `已加入选择组：${updated.checkboxGroupCode}` : '已移出选择组' });
    });
  }

  function changeCheckboxGroupRule(
    groupCode: string,
    minSelected: number,
    maxSelected: null | number,
    expectedVersion: number,
  ): Promise<void> {
    return exclusive(async () => {
      setWorking('正在更新选择组规则...');
      const updated = await client.changeCheckboxGroupRule(apiContext(), groupCode, {
        expectedVersion,
        maxSelected,
        minSelected,
        operationCode: generateOpaqueId('checkbox-group-rule'),
      });
      if (disposed) return;
      checkboxGroupState.value = checkboxGroupState.value.map((group) => (group.groupCode === updated.groupCode ? updated : group));
      store.setStatus('ready');
      store.setOperation({ kind: 'success', message: `已更新选择组规则：${updated.groupCode}` });
    });
  }

  function insertCrossTemplateScalar(candidate: CrossTemplateScalarCandidate): Promise<void> {
    return exclusive(async () => {
      const stepId = requireCurrentStepId();
      setWorking('正在标注跨模板字段值...');
      await insertReserved(
        () =>
          client.reserveCrossTemplateScalarBinding(apiContext(), {
            operationCode: generateOpaqueId('cross-template-reserve'),
            repeatRequest: false,
            sourceFieldId: candidate.sourceFieldId,
            sourceFieldVersionId: candidate.sourceFieldVersionId,
            sourceTemplateCode: candidate.sourceTemplateCode,
            sourceTemplateId: candidate.sourceTemplateId,
            stepId,
          }),
        undefined,
        true,
      );
    });
  }

  function createPrivateField(draft: TemplatePrivateFieldDraft): Promise<void> {
    return exclusive(async () => {
      const stepId = requireCurrentStepId();
      setWorking('正在创建本模板字段...');
      await insertReserved(() => client.reservePrivateField(apiContext(), { ...draft, operationCode: generateOpaqueId('private'), stepId }));
      if (disposed) return;
      const context = await client.fetchAnnotationContext(apiContext());
      if (disposed) return;
      bindingState.value = [...context.bindings];
      fieldState.value = toTemplateFieldOptions(context.fields);
      checkboxGroupState.value = [...(context.checkboxGroups ?? [])];
      repeatGroupState.value = [...(context.repeatGroups ?? [])];
      applyStepConfiguration(context.stepConfiguration);
    });
  }

  function insertReference(primaryBindingId: string): Promise<void> {
    return exclusive(async () => {
      const primary = bindingState.value.find((item) => item.id === primaryBindingId && item.status === 'ACTIVE' && item.controlRole === 'PRIMARY');
      if (!primary) throw new Error('主字段绑定不存在或状态已变化');
      setWorking('正在插入字段引用...');
      await insertReserved(
        () => client.reserveReferenceBinding(apiContext(), primary.id, { operationCode: generateOpaqueId('reference') }),
        primary.controlTag,
      );
    });
  }

  function reserveFormulaBinding(request: Omit<FormulaBindingRequest, 'operationCode'>): Promise<void> {
    return exclusive(async () => {
      const resultBinding = formulaResultBinding({ ...request, id: '', operationCode: '', status: 'PENDING', version: 0 });
      setWorking('正在锁定公式结果字段...');
      let reserved: TemplateFormulaBinding | undefined;
      let lockSnapshot: FormulaResultLockSnapshot | undefined;
      try {
        await prepareDocumentMutation();
        if (disposed) return;
        lockSnapshot = await bridge.lockFormulaResult(resultBinding.controlTag);
        if (disposed) return;
        reserved = await client.reserveFormulaBinding(apiContext(), {
          ...buildFormulaBindingRequest({ ...request, operationCode: generateOpaqueId('formula-reserve') }),
          jieguoSuoKuaizhao: formulaOriginalLockControls(request.resultBindingId, lockSnapshot),
        });
        if (disposed) return;
        await client.activateFormulaBinding(apiContext(), reserved.id, {
          controls: formulaResultControls(reserved.resultBindingId, 'sdtContentLocked'),
          expectedVersion: reserved.version,
          operationCode: reserved.operationCode,
        });
        if (disposed) return;
        formulaBindingState.value = [...formulaBindingState.value, { ...reserved, status: 'ACTIVE' }];
        store.setStatus('ready');
        store.setOperation({ kind: 'success', message: '公式映射已启用' });
      } catch (error) {
        let recoveryError: unknown;
        if (!disposed && lockSnapshot && !isExpiredTicket(error)) {
          try {
            await bridge.restoreFormulaResultLocks(lockSnapshot);
          } catch (restoreError) {
            recoveryError = restoreError;
          }
        }
        if (!disposed && reserved?.status === 'PENDING' && !recoveryError && !isExpiredTicket(error)) {
          try {
            await client.cancelFormulaBinding(apiContext(), reserved.id, {
              expectedVersion: reserved.version,
              operationCode: reserved.operationCode,
              reason: message(error),
            });
          } catch {
            /* 保留原始失败 */
          }
        }
        if (!disposed) {
          store.setStatus(contextStore.context ? 'ready' : 'error');
          store.setOperation({
            kind: 'error',
            message: recoveryError ? `公式映射失败，结果锁需对账：${message(recoveryError)}` : `公式映射失败：${message(error)}`,
          });
        }
        throw error;
      }
    });
  }

  function repairBindingId(bindingId: string): Promise<void> {
    return exclusive(async () => {
      const current = bindingState.value.find((item) => item.id === bindingId);
      if (!current) throw new Error('修复绑定不存在');
      setWorking('正在恢复控件...');
      const before = await bridge.listControls();
      if (disposed) return;
      const matches = before.filter((item) => item.Tag === current.controlTag);
      if (matches.length > 1) throw new Error('修复目标 Tag 在文档中出现多次');
      if (matches.length === 1) {
        if (!matches[0]?.InternalId) throw new Error('修复目标控件缺少 InternalId');
        const retry = pendingRepairConfirmation?.binding.id === bindingId ? pendingRepairConfirmation : null;
        if (retry) {
          try {
            await client.confirmRepair(apiContext(), bindingId, confirmation(retry, before));
            pendingRepairConfirmation = null;
          } catch (error) {
            if (!(error instanceof TypeError)) pendingRepairConfirmation = null;
            throw error;
          }
          if (disposed) return;
        }
        await bridge.selectControl(matches[0].InternalId, abortController.signal);
        if (disposed) return;
        if (!disposed) {
          pendingRepair.value = null;
          store.setControls(before);
          store.setStatus('ready');
          store.setOperation({ kind: 'success', message: retry ? '控件已恢复' : '目标控件已定位' });
        }
        return;
      }
      let check = pendingRepair.value?.binding.id === bindingId ? pendingRepair.value : null;
      if (!check) {
        check = await client.fetchRepairContext(apiContext(), bindingId);
        if (disposed) return;
      }
      await prepareDocumentMutation();
      if (disposed) return;
      await bridge.insertBinding(check.binding, abortController.signal);
      if (disposed) return;
      const after = await bridge.listControls();
      if (disposed) return;
      if (countTag(after, check.binding.controlTag) !== 1) throw new Error('修复后 Tag 计数不是 1');
      pendingRepairConfirmation = check;
      try {
        await client.confirmRepair(apiContext(), bindingId, confirmation(check, after));
        pendingRepairConfirmation = null;
      } catch (error) {
        if (!(error instanceof TypeError)) pendingRepairConfirmation = null;
        throw error;
      }
      if (disposed) return;
      if (!disposed) {
        pendingRepair.value = null;
        store.setControls(after);
        store.setStatus('ready');
        store.setOperation({ kind: 'success', message: '控件已恢复' });
      }
    });
  }

  function removeFormulaBinding(bindingId: string): Promise<void> {
    return exclusive(async () => {
      const current = formulaBindingState.value.find((item) => item.id === bindingId && item.status === 'ACTIVE');
      if (!current) throw new Error('公式映射不存在或状态已变化');
      const resultBinding = formulaResultBinding(current);
      setWorking('正在解除公式结果锁...');
      const check = await client.checkFormulaBindingRemoval(apiContext(), bindingId);
      if (check.downstreamFormulaBindingIds.length > 0) {
        throw new Error('公式结果仍被下游公式引用');
      }
      await prepareDocumentMutation();
      if (disposed) return;
      const lockedSnapshot = await bridge.lockFormulaResult(resultBinding.controlTag);
      try {
        await bridge.unlockFormulaResult(resultBinding.controlTag);
        await client.confirmFormulaBindingRemoval(apiContext(), bindingId, {
          controls: formulaResultControls(current.resultBindingId, ''),
          expectedVersion: expectedFormulaVersion(check.expectedVersions, bindingId),
          operationCode: check.operationCode,
        });
        formulaBindingState.value = formulaBindingState.value.map((item) => (item.id === bindingId ? { ...item, status: 'REMOVED' } : item));
        store.setStatus('ready');
        store.setOperation({ kind: 'success', message: '公式映射已移除' });
      } catch (error) {
        if (!isExpiredTicket(error))
          try {
            await bridge.restoreFormulaResultLocks(lockedSnapshot);
          } catch (restoreError) {
            store.setOperation({ kind: 'error', message: `公式结果锁恢复失败，模板发布将被阻止：${message(restoreError)}` });
          }
        throw error;
      }
    });
  }

  function upgradeFormulaBinding(bindingId: string, request: Omit<FormulaBindingRequest, 'operationCode'>): Promise<void> {
    return exclusive(async () => {
      const current = formulaBindingState.value.find((item) => item.id === bindingId && item.status === 'ACTIVE');
      if (!current) throw new Error('公式映射不存在或状态已变化');
      setWorking('正在升级公式映射...');
      const check = await client.checkFormulaBindingUpgrade(
        apiContext(),
        bindingId,
        buildFormulaBindingRequest({ ...request, operationCode: generateOpaqueId('formula-upgrade') }),
      );
      const targetResult = formulaResultBinding({ ...request, id: bindingId, operationCode: '', status: 'ACTIVE', version: current.version });
      const currentResult = formulaResultBinding(current);
      await prepareDocumentMutation();
      if (disposed) return;
      const snapshots = [await bridge.lockFormulaResult(targetResult.controlTag)];
      if (targetResult.controlTag !== currentResult.controlTag) {
        try {
          const oldLocked = await bridge.lockFormulaResult(currentResult.controlTag);
          snapshots.push(oldLocked);
          await bridge.unlockFormulaResult(currentResult.controlTag);
        } catch (error) {
          if (!isExpiredTicket(error)) await bridge.restoreFormulaResultLocks(snapshots[0]!);
          throw error;
        }
      }
      try {
        const confirm = {
          controls: formulaResultControls(request.resultBindingId, 'sdtContentLocked'),
          expectedVersion: expectedFormulaVersion(check.expectedVersions, bindingId),
          operationCode: check.operationCode,
          target: buildFormulaBindingRequest({ ...request, operationCode: check.operationCode }),
        };
        try {
          await client.confirmFormulaBindingUpgrade(apiContext(), bindingId, confirm);
        } catch (error) {
          if (!(error instanceof TypeError)) throw error;
          try {
            await client.confirmFormulaBindingUpgrade(apiContext(), bindingId, confirm);
          } catch (retryError) {
            if (retryError instanceof TypeError) throw new FormulaConfirmAmbiguousError();
            throw retryError;
          }
        }
        formulaBindingState.value = formulaBindingState.value.map((item) =>
          item.id === bindingId ? { ...item, ...request, operationCode: check.operationCode, status: 'ACTIVE' } : item,
        );
        store.setStatus('ready');
        store.setOperation({ kind: 'success', message: '公式映射已升级' });
      } catch (error) {
        if (!isExpiredTicket(error) && !(error instanceof FormulaConfirmAmbiguousError))
          for (const snapshot of snapshots) {
            try {
              await bridge.restoreFormulaResultLocks(snapshot);
            } catch {
              /* 保留后端失败并要求后续对账 */
            }
          }
        if (!disposed && error instanceof FormulaConfirmAmbiguousError) {
          store.setStatus('ready');
          store.setOperation({ kind: 'error', message: `公式升级待对账：${message(error)}` });
        }
        throw error;
      }
    });
  }

  async function executeCheckedChange(
    operationKey: string,
    bindingId: string,
    operationLabel: string,
    check: () => Promise<TemplateChangeCheck>,
    confirm: (form: ReturnType<typeof confirmation>) => Promise<void>,
  ): Promise<void> {
    try {
      const result = pendingCheckedChanges.get(operationKey) ?? (await check());
      if (disposed) return;
      await prepareDocumentMutation();
      if (disposed) return;
      await bridge.applyControlPlan(bindingState.value, result.controls, abortController.signal);
      if (disposed) return;
      const controls = await bridge.listControls();
      if (disposed) return;
      const form = confirmation(result, controls);
      try {
        await confirm(form);
        pendingCheckedChanges.delete(operationKey);
      } catch (error) {
        if (!disposed && error instanceof TypeError) pendingCheckedChanges.set(operationKey, result);
        else pendingCheckedChanges.delete(operationKey);
        throw error;
      }
      if (disposed) return;
      const desiredIds = new Set(result.controls.map((item) => item.bindingId));
      const affectedIds = new Set([bindingId, ...Object.keys(result.expectedVersions)]);
      bindingState.value = bindingState.value.map((item) =>
        desiredIds.has(item.id)
          ? bindingsForSnapshots(result.controls).find((value) => value.id === item.id)!
          : affectedIds.has(item.id) && item.status === 'ACTIVE'
            ? { ...item, status: 'REMOVED' }
            : item,
      );
      store.setControls(controls);
    } catch (error) {
      if (!disposed) store.setOperation({ kind: 'error', message: `${operationLabel}失败：${message(error)}` });
      throw error;
    }
  }

  function removeBinding(bindingId: string): Promise<void> {
    return exclusive(async () =>
      executeCheckedChange(
        `remove:${bindingId}`,
        bindingId,
        '删除',
        () => client.checkBindingRemoval(apiContext(), bindingId),
        (form) => client.confirmBindingRemoval(apiContext(), bindingId, form),
      ),
    );
  }

  function removeOrphanControl(internalId: string): Promise<void> {
    return exclusive(async () => {
      setWorking('正在删除异常控件...');
      await prepareDocumentMutation();
      if (disposed) return;
      const controls = await bridge.deleteControl(internalId);
      if (disposed) return;
      store.setControls(controls);
      store.setStatus('ready');
      store.setOperation({ kind: 'success', message: '异常控件已删除' });
    });
  }

  function promoteReference(bindingId: string): Promise<void> {
    return exclusive(async () =>
      executeCheckedChange(
        `promote:${bindingId}`,
        bindingId,
        '设为主字段',
        () => client.checkReferencePromotion(apiContext(), bindingId),
        (form) => client.confirmReferencePromotion(apiContext(), bindingId, form),
      ),
    );
  }

  function upgradeBinding(bindingId: string, targetFieldVersionId: string): Promise<void> {
    return exclusive(async () =>
      executeCheckedChange(
        `upgrade:${bindingId}:${targetFieldVersionId}`,
        bindingId,
        '字段升级',
        () => client.checkBindingVersionUpgrade(apiContext(), bindingId, { targetFieldVersionId }),
        (form) => client.confirmBindingVersionUpgrade(apiContext(), bindingId, { ...form, targetFieldVersionId }),
      ),
    );
  }

  function select(internalId: string): Promise<void> {
    return exclusive(() => bridge.selectControl(internalId, abortController.signal));
  }

  const cleanup = bridge.bindLifecycle({
    onReady: () => {
      if (disposed || !contextStore.initialize(bridge.getRuntimeOptions())) {
        if (!disposed) {
          store.setStatus('error');
          store.setOperation({ kind: 'error', message: contextStore.error });
        }
        return;
      }
      instanceContext = contextStore.context;
      void initialize().catch(() => undefined);
    },
    onThemeChanged: () => undefined,
  });
  readyTimeout = setTimeout(() => {
    readyTimeout = undefined;
    if (!disposed && store.status === 'initializing') {
      store.setStatus('error');
      store.setOperation({ kind: 'error', message: 'ONLYOFFICE SDK 初始化超时' });
    }
  }, SDK_READY_TIMEOUT_MS);

  function dispose(): void {
    if (disposed) return;
    disposed = true;
    clearReadyTimeout();
    abortController.abort();
    cleanup();
    bindingState.value = [];
    fieldState.value = [];
    checkboxGroupState.value = [];
    crossTemplateScalarCandidateState.value = [];
    crossTemplateSourceTemplateState.value = [];
    repeatGroupState.value = [];
    stepConfigurationState.value = null;
    currentStepIdState.value = null;
    formulaBindingState.value = [];
    formulaVersionState.value = [];
    pendingRepair.value = null;
    pendingRepairConfirmation = null;
    pendingCheckedChanges.clear();
    instanceContext = null;
    contextStore.reset();
  }

  return {
    contextStore,
    store,
    initialize,
    refresh,
    loadCrossTemplateSourceTemplates,
    loadCrossTemplateScalarCandidates,
    suggestCode,
    insertSelectedField,
    insertRepeatField,
    changeCheckboxGroup,
    changeCheckboxGroupRule,
    insertCrossTemplateScalar,
    createPrivateField,
    insertReference,
    repairBindingId,
    removeBinding,
    removeOrphanControl,
    removeFormulaBinding,
    promoteReference,
    reserveFormulaBinding,
    upgradeBinding,
    upgradeFormulaBinding,
    select,
    setCurrentStepId,
    dispose,
    get bindings() {
      return bindingState.value;
    },
    get fields() {
      return fieldState.value;
    },
    get checkboxGroups() {
      return checkboxGroupState.value;
    },
    get crossTemplateScalarCandidates() {
      return crossTemplateScalarCandidateState.value;
    },
    get crossTemplateSourceTemplates() {
      return crossTemplateSourceTemplateState.value;
    },
    get repeatGroups() {
      return repeatGroupState.value;
    },
    get stepConfiguration() {
      return stepConfigurationState.value;
    },
    get currentStepId() {
      return currentStepIdState.value;
    },
    get formulaBindings() {
      return formulaBindingState.value;
    },
    get formulaVersions() {
      return formulaVersionState.value;
    },
    get pendingRepairBindingId() {
      return pendingRepair.value?.binding.id ?? null;
    },
    get repairMode() {
      return Boolean(instanceContext?.repairBindingId);
    },
  };
}
