import type { DocumentField, FieldApplyResult, FieldChange } from '../domain/types';
import { applyFieldsCommand, cloneRepeatRowCommand, inspectFieldsCommand, removeRepeatRowCommand } from './commands';
import type { ApplyResult, AscGlobal, InspectResult, RepeatCloneResult } from './types';

export interface EditorControlEvent {
  InternalId?: unknown;
  Tag?: unknown;
}

interface LifecycleCallbacks {
  onControlChanged: (control: EditorControlEvent) => void;
  onControlFocused: (control: EditorControlEvent) => void;
  onReady: () => void;
  onThemeChanged: (theme: unknown) => void;
}

export interface OnlyOfficeBridge {
  applyFields(changes: FieldChange[]): Promise<FieldApplyResult[]>;
  appendRepeatRow(groupCode: string, rowId: string, beforeRowId?: string): Promise<string[]>;
  bindLifecycle(callbacks: LifecycleCallbacks): () => void;
  getRuntimeOptions(): unknown;
  inspectFields(): Promise<DocumentField[]>;
  removeRepeatRow(groupCode: string, rowId: string): Promise<string[]>;
  selectControl(internalId: string): Promise<void>;
  setFillOnlyMode(enabled: boolean): Promise<void>;
}

function parseResult<T>(raw: unknown): T {
  const result: unknown = typeof raw === 'string' ? JSON.parse(raw) : raw;
  if (typeof result !== 'object' || result === null || Array.isArray(result) || !('ok' in result) || typeof result.ok !== 'boolean') {
    throw new Error('编辑器命令返回无效结果');
  }
  return result as T;
}

export function createOnlyOfficeBridge(asc: AscGlobal): OnlyOfficeBridge {
  let fillOnlyMode = false;
  let documentCommandQueue: Promise<void> = Promise.resolve();

  function executeMethod(name: string, args: unknown[] = []): Promise<unknown> {
    return new Promise((resolve, reject) => {
      try {
        asc.plugin.executeMethod(name, args, resolve);
      } catch (error) {
        reject(error);
      }
    });
  }

  function enqueueDocumentCommand<T>(operation: () => Promise<T>): Promise<T> {
    const result = documentCommandQueue.then(operation, operation);
    documentCommandQueue = result.then(() => undefined, () => undefined);
    return result;
  }

  async function applyFillOnlyRestriction(): Promise<void> {
    try {
      await executeMethod('SetEditingRestrictions', ['forms']);
    } catch (error) {
      try {
        await executeMethod('SetEditingRestrictions', ['readOnly']);
      } catch {
        // Preserve the first failure; read-only is a best-effort fail-closed fallback.
      }
      throw error;
    }
  }

  function callCommandRaw<T>(command: () => string, scopeKey: string, payload: unknown): Promise<T> {
    return new Promise((resolve, reject) => {
      asc.scope[scopeKey] = payload;
      try {
        asc.plugin.callCommand(command, false, true, (raw) => {
          try { resolve(parseResult<T>(raw)); } catch (error) { reject(error); }
          finally { delete asc.scope[scopeKey]; }
        });
      } catch (error) {
        delete asc.scope[scopeKey];
        reject(error);
      }
    });
  }

  function callCommand<T>(command: () => string, scopeKey: string, payload: unknown): Promise<T> {
    if (!fillOnlyMode) return callCommandRaw<T>(command, scopeKey, payload);
    return enqueueDocumentCommand(async () => {
      await executeMethod('SetEditingRestrictions', ['none']);
      try {
        return await callCommandRaw<T>(command, scopeKey, payload);
      } finally {
        await applyFillOnlyRestriction();
      }
    });
  }

  async function inspectFields(): Promise<DocumentField[]> {
    const result = await callCommand<InspectResult>(inspectFieldsCommand, 'elnRecordFillInspect', {});
    if (!result.ok) throw new Error(result.error || '读取内容控件失败');
    return result.fields;
  }

  async function applyFields(changes: FieldChange[]): Promise<FieldApplyResult[]> {
    const result = await callCommand<ApplyResult>(applyFieldsCommand, 'elnRecordFillChanges', changes);
    if (!result.ok) throw new Error(result.error || '应用字段失败');
    return result.results;
  }

  async function appendRepeatRow(groupCode: string, rowId: string, beforeRowId?: string): Promise<string[]> {
    const result = await callCommand<RepeatCloneResult>(cloneRepeatRowCommand, 'elnRecordFillRepeatRow', {
      ...(beforeRowId ? { beforeRowId } : {}), groupCode, rowId,
    });
    if (!result.ok || !Array.isArray(result.tags)) throw new Error(result.error || '新增文档重复行失败');
    return result.tags;
  }

  async function removeRepeatRow(groupCode: string, rowId: string): Promise<string[]> {
    const result = await callCommand<RepeatCloneResult>(removeRepeatRowCommand, 'elnRecordFillRepeatRow', { groupCode, rowId });
    if (!result.ok || !Array.isArray(result.tags)) throw new Error(result.error || '删除文档重复行失败');
    return result.tags;
  }

  async function selectControl(internalId: string): Promise<void> {
    if (!internalId) throw new Error('字段控件缺少内部 ID');
    if (await executeMethod('SelectContentControl', [internalId]) === false) {
      throw new Error('编辑器未能定位字段');
    }
  }

  function bindLifecycle(callbacks: LifecycleCallbacks): () => void {
    const originalInit = asc.plugin.init;
    const originalTheme = asc.plugin.onThemeChanged;
    const changeEvent = 'onChangeContentControl';
    const focusEvent = 'onFocusContentControl';
    const onControlChanged = (value: unknown) => callbacks.onControlChanged(
      typeof value === 'object' && value !== null ? value as EditorControlEvent : {},
    );
    const onControlFocused = (value: unknown) => callbacks.onControlFocused(
      typeof value === 'object' && value !== null ? value as EditorControlEvent : {},
    );
    let eventsAttached = false;
    const onReady = () => {
      if (!eventsAttached) {
        asc.plugin.attachEditorEvent?.(changeEvent, onControlChanged);
        asc.plugin.attachEditorEvent?.(focusEvent, onControlFocused);
        eventsAttached = true;
      }
      callbacks.onReady();
    };
    asc.plugin.init = onReady;
    asc.plugin.onThemeChanged = (theme) => { asc.plugin.onThemeChangedBase(theme); callbacks.onThemeChanged(theme); };
    return () => {
      if (asc.plugin.init === onReady) asc.plugin.init = originalInit;
      asc.plugin.onThemeChanged = originalTheme;
      if (eventsAttached) {
        asc.plugin.detachEditorEvent?.(changeEvent);
        asc.plugin.detachEditorEvent?.(focusEvent);
      }
    };
  }

  return {
    applyFields,
    appendRepeatRow,
    bindLifecycle,
    getRuntimeOptions: () => asc.plugin.info?.options,
    inspectFields,
    removeRepeatRow,
    selectControl,
    setFillOnlyMode: (enabled) => enqueueDocumentCommand(async () => {
      fillOnlyMode = enabled;
      if (enabled) await applyFillOnlyRestriction();
      else await executeMethod('SetEditingRestrictions', ['none']);
    }),
  };
}
