import { countTag } from '../domain/field-types';
import { parseTag } from '../domain/tags';
import type { FieldDefinition } from '../domain/types';
import { disableTrackRevisionsCommand, insertTypedControlCommand, listContentControlsCommand, writeTextDefaultCommand } from './commands';
import type { AscGlobal, OfficeContentControl, OnlyOfficeCommand } from './types';

interface CommandResult {
  controls?: OfficeContentControl[];
  error?: string;
  ok: boolean;
}

interface LifecycleCallbacks {
  onReady: () => void;
  onThemeChanged: (theme: unknown) => void;
}

interface LifecycleBinding {
  callbacks: LifecycleCallbacks;
}

interface LifecycleState {
  bindings: LifecycleBinding[];
  button: NonNullable<AscGlobal['plugin']['button']>;
  init: NonNullable<AscGlobal['plugin']['init']>;
  onThemeChanged: NonNullable<AscGlobal['plugin']['onThemeChanged']>;
  originalButton: AscGlobal['plugin']['button'];
  originalInit: AscGlobal['plugin']['init'];
  originalThemeChanged: AscGlobal['plugin']['onThemeChanged'];
}

export interface OnlyOfficeBridge {
  bindLifecycle(callbacks: LifecycleCallbacks): () => void;
  close(): void;
  deleteControl(internalId: string): Promise<OfficeContentControl[]>;
  disableTrackRevisions(): Promise<void>;
  getRuntimeOptions(): unknown;
  insertField(definition: FieldDefinition): Promise<OfficeContentControl[]>;
  listControls(): Promise<OfficeContentControl[]>;
  selectControl(internalId: string): Promise<void>;
}

const lifecycleStates = new WeakMap<AscGlobal, LifecycleState>();

function toError(error: unknown): Error {
  return error instanceof Error ? error : new Error(String(error));
}

function isCommandResult(value: unknown): value is CommandResult {
  return typeof value === 'object' && value !== null && !Array.isArray(value) && 'ok' in value && typeof value.ok === 'boolean';
}

function bindLifecycleHandlers(asc: AscGlobal, callbacks: LifecycleCallbacks): () => void {
  let state = lifecycleStates.get(asc);

  if (!state) {
    const bindings: LifecycleBinding[] = [];
    const currentBinding = () => bindings[bindings.length - 1];
    state = {
      bindings,
      originalInit: asc.plugin.init,
      originalButton: asc.plugin.button,
      originalThemeChanged: asc.plugin.onThemeChanged,
      init: () => currentBinding()?.callbacks.onReady(),
      button: (id) => {
        if (id === -1 && currentBinding()) {
          asc.plugin.executeCommand('close', '');
        }
      },
      onThemeChanged: (theme) => {
        if (currentBinding()) {
          asc.plugin.onThemeChangedBase(theme);
          currentBinding()?.callbacks.onThemeChanged(theme);
        }
      },
    };
    lifecycleStates.set(asc, state);
    asc.plugin.init = state.init;
    asc.plugin.button = state.button;
    asc.plugin.onThemeChanged = state.onThemeChanged;
  }

  const binding = { callbacks };
  state.bindings.push(binding);
  let active = true;

  return () => {
    if (!active) {
      return;
    }
    active = false;
    const index = state.bindings.indexOf(binding);
    if (index >= 0) {
      state.bindings.splice(index, 1);
    }
    if (state.bindings.length > 0) {
      return;
    }

    if (asc.plugin.init === state.init) {
      asc.plugin.init = state.originalInit;
    }
    if (asc.plugin.button === state.button) {
      asc.plugin.button = state.originalButton;
    }
    if (asc.plugin.onThemeChanged === state.onThemeChanged) {
      asc.plugin.onThemeChanged = state.originalThemeChanged;
    }
    if (lifecycleStates.get(asc) === state) {
      lifecycleStates.delete(asc);
    }
  };
}

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

  function executeCommand(command: OnlyOfficeCommand, scopeKey: string, payload: unknown): Promise<CommandResult> {
    return new Promise((resolve, reject) => {
      const clearScope = () => {
        if (asc.scope[scopeKey] === payload) {
          delete asc.scope[scopeKey];
        }
      };

      try {
        asc.scope[scopeKey] = payload;
        asc.plugin.callCommand(command, false, true, (raw) => {
          try {
            const result: unknown = typeof raw === 'string' ? JSON.parse(raw) : raw;

            if (!isCommandResult(result)) {
              reject(new Error('编辑器命令返回无效结果'));
              return;
            }

            if (!result.ok) {
              reject(new Error(typeof result.error === 'string' ? result.error : '编辑器命令执行失败'));
              return;
            }

            resolve(result);
          } catch {
            reject(new Error('编辑器命令返回无效结果'));
          } finally {
            clearScope();
          }
        });
      } catch (error: unknown) {
        clearScope();
        reject(toError(error));
      }
    });
  }

  async function readControls(errorMessage: string): Promise<OfficeContentControl[]> {
    const result = await executeCommand(listContentControlsCommand, 'elnTemplateList', {});

    if (!Array.isArray(result.controls)) {
      throw new Error(errorMessage);
    }

    return result.controls;
  }

  function listControls(): Promise<OfficeContentControl[]> {
    return readControls('编辑器未返回内容控件列表');
  }

  async function disableTrackRevisions(): Promise<void> {
    await executeCommand(disableTrackRevisionsCommand, 'elnTemplateReview', {});
  }

  async function insertField(definition: FieldDefinition): Promise<OfficeContentControl[]> {
    const before = await listControls();
    const beforeCount = countTag(before, definition.tag);

    if (beforeCount > 0) {
      throw new Error('字段 ID 已存在');
    }

    const alias = definition.alias.trim();
    if (before.some((control) => parseTag(control.Tag) && (control.Alias || '').trim() === alias)) {
      throw new Error('字段名称已存在');
    }

    if (definition.controlType === 'text') {
      const created = (await executeMethod('AddContentControl', [
        2,
        {
          Tag: definition.tag,
          Alias: definition.alias,
          Lock: 3,
          PlaceHolderText: definition.placeholder,
          Appearance: 1,
          Color: definition.color,
        },
      ])) as OfficeContentControl | null | undefined;

      if (!created?.InternalId) {
        throw new Error('编辑器未返回新内容控件');
      }

      if (definition.defaultValue) {
        try {
          await executeCommand(writeTextDefaultCommand, 'elnTemplateTextDefault', {
            tag: definition.tag,
            value: definition.defaultValue,
          });
        } catch (error: unknown) {
          throw new Error(`控件已创建，但默认值写入失败：${toError(error).message}`);
        }
      }
    } else {
      const isDropdown = definition.controlType === 'dropdown';
      await executeCommand(insertTypedControlCommand, 'elnTemplateInsert', {
        controlType: definition.controlType,
        tag: definition.tag,
        alias: definition.alias,
        placeholder: definition.placeholder,
        defaultValue: isDropdown ? '' : definition.defaultValue,
        defaultChecked: isDropdown ? false : definition.defaultChecked,
        options: isDropdown ? definition.options : [],
        selectedIndex: isDropdown ? definition.selectedIndex : -1,
        color: definition.color,
      });
    }

    const after = await readControls('插入后无法读取内容控件列表');
    if (countTag(after, definition.tag) !== beforeCount + 1) {
      throw new Error('插入后 Tag 计数未增加');
    }

    return after;
  }

  async function selectControl(internalId: string): Promise<void> {
    if (!internalId) {
      throw new Error('该内容控件没有可用的内部 ID');
    }

    const controls = await listControls();
    if (!controls.some((control) => control.InternalId === internalId)) {
      throw new Error('定位目标已不存在，请刷新字段列表');
    }

    const result = await executeMethod('SelectContentControl', [internalId]);
    if (result === false) {
      throw new Error('编辑器未能定位字段');
    }
  }

  async function deleteControl(internalId: string): Promise<OfficeContentControl[]> {
    if (!internalId) {
      throw new Error('该内容控件没有可用的内部 ID');
    }

    const before = await listControls();
    if (!before.some((control) => control.InternalId === internalId)) {
      throw new Error('删除目标已不存在，请刷新字段列表');
    }

    if ((await executeMethod('RemoveContentControl', [internalId])) === false) {
      throw new Error('编辑器未能移除字段控件');
    }

    const after = await readControls('解除后无法读取内容控件列表');
    if (after.some((control) => control.InternalId === internalId)) {
      throw new Error('解除后字段仍然存在');
    }
    return after;
  }

  function close(): void {
    asc.plugin.executeCommand('close', '');
  }

  function getRuntimeOptions(): unknown {
    return asc.plugin.info?.options;
  }

  function bindLifecycle(callbacks: LifecycleCallbacks): () => void {
    return bindLifecycleHandlers(asc, callbacks);
  }

  return { bindLifecycle, close, deleteControl, disableTrackRevisions, getRuntimeOptions, insertField, listControls, selectControl };
}
