import type { FillFieldType } from '../domain/types';

export function inspectFieldsCommand(): string {
  function errorMessage(error: unknown): string {
    return error instanceof Error ? error.message : String(error);
  }

  function textOf(control: any): string {
    return String(control.GetRange().GetText() ?? '');
  }

  function isPlaceholder(control: any): boolean {
    const json = control.ToJSON?.(false);
    if (typeof json !== 'string') return false;
    try {
      return JSON.parse(json)?.sdtPr?.showingPlcHdr === true;
    } catch {
      return false;
    }
  }

  function isBlockControl(control: any): boolean {
    if (control.IsBlockLevel?.()) return true;
    const json = control.ToJSON?.(false);
    if (typeof json !== 'string') return false;
    try {
      return JSON.parse(json)?.type === 'blockLvlSdt';
    } catch {
      return false;
    }
  }

  function kindOf(control: any): FillFieldType {
    if (control.IsCheckBox?.()) return 'checkbox';
    if (control.IsComboBox?.() || control.IsDropDownList?.()) return 'select';
    if (control.IsDatePicker?.()) return 'date';
    if (isBlockControl(control)) return 'multiline';
    return 'text';
  }

  function dateFormatOf(control: any): string {
    const json = control.ToJSON?.(false);
    if (typeof json !== 'string') throw new Error('无法读取日期控件格式');
    let dateFormat: unknown;
    try {
      dateFormat = JSON.parse(json)?.sdtPr?.date?.dateFormat;
    } catch {
      throw new Error('日期控件结构数据无效');
    }
    if (typeof dateFormat === 'string' && dateFormat.trim()) return dateFormat;
    throw new Error('不支持的日期控件格式');
  }

  function optionsOf(control: any): Array<{ display: string; value: string }> {
    const list = control.GetDropdownList?.();
    if (!list?.GetElementsCount || !list.GetItem) return [];
    const options = [];
    for (let index = 0; index < list.GetElementsCount(); index += 1) {
      const item = list.GetItem(index);
      options.push({ display: String(item.GetText()), value: String(item.GetValue()) });
    }
    return options;
  }

  function readValue(control: any, type: FillFieldType): boolean | string {
    if (isPlaceholder(control)) return type === 'checkbox' ? false : '';
    const text = textOf(control);
    if (type === 'checkbox') return ['☑', '☒'].includes(text);
    if (type === 'select') return optionsOf(control).find((item) => item.display === text)?.value ?? '';
    if (type === 'multiline') return text.replace(/\r\n?/gu, '\n').replace(/\n$/u, '');
    return text;
  }

  try {
    const controls = Api.GetDocument().GetAllContentControls();
    const fields = controls
      .filter((control: any) => /^eln\.(?:field\.[\w.-]+|ref\.[\w.-]+\.[\w-]+|repeat\.[\w-]+\.[\w-]+\.[\w.-]+)$/u.test(control.GetTag()))
      .map((control: any) => {
        const type = kindOf(control);
        return {
          alias: String(control.GetAlias?.() ?? ''),
          currentValue: readValue(control, type),
          ...(type === 'date' ? { dateFormat: dateFormatOf(control) } : {}),
          internalId: String(control.GetInternalId?.() ?? ''),
          options: type === 'select' ? optionsOf(control) : [],
          tag: String(control.GetTag()),
          type,
        };
      });
    const tags = fields.map((field: any) => field.tag);
    if (new Set(tags).size !== tags.length) throw new Error('文档存在重复字段 Tag');
    return JSON.stringify({ ok: true, fields });
  } catch (error) {
    return JSON.stringify({ ok: false, error: errorMessage(error), fields: [] });
  }
}

export function applyFieldsCommand(): string {
  function errorMessage(error: unknown): string {
    return error instanceof Error ? error.message : String(error);
  }

  function textOf(control: any): string {
    return String(control.GetRange().GetText() ?? '');
  }

  function isPlaceholder(control: any): boolean {
    const json = control.ToJSON?.(false);
    if (typeof json !== 'string') return false;
    try {
      return JSON.parse(json)?.sdtPr?.showingPlcHdr === true;
    } catch {
      return false;
    }
  }

  function isBlockControl(control: any): boolean {
    if (control.IsBlockLevel?.()) return true;
    const json = control.ToJSON?.(false);
    if (typeof json !== 'string') return false;
    try {
      return JSON.parse(json)?.type === 'blockLvlSdt';
    } catch {
      return false;
    }
  }

  function kindOf(control: any): FillFieldType {
    if (control.IsCheckBox?.()) return 'checkbox';
    if (control.IsComboBox?.() || control.IsDropDownList?.()) return 'select';
    if (control.IsDatePicker?.()) return 'date';
    if (isBlockControl(control)) return 'multiline';
    return 'text';
  }

  function dateFormatOf(control: any): string {
    const json = control.ToJSON?.(false);
    if (typeof json !== 'string') throw new Error('无法读取日期控件格式');
    let dateFormat: unknown;
    try {
      dateFormat = JSON.parse(json)?.sdtPr?.date?.dateFormat;
    } catch {
      throw new Error('日期控件结构数据无效');
    }
    if (typeof dateFormat === 'string' && dateFormat.trim()) return dateFormat;
    throw new Error('不支持的日期控件格式');
  }

  function optionsOf(control: any): Array<{ display: string; value: string }> {
    const list = control.GetDropdownList?.();
    if (!list?.GetElementsCount || !list.GetItem) return [];
    const options = [];
    for (let index = 0; index < list.GetElementsCount(); index += 1) {
      const item = list.GetItem(index);
      options.push({ display: String(item.GetText()), value: String(item.GetValue()) });
    }
    return options;
  }

  function readValue(control: any, type: FillFieldType): boolean | string {
    if (isPlaceholder(control)) return type === 'checkbox' ? false : '';
    const text = textOf(control);
    if (type === 'checkbox') return ['☑', '☒'].includes(text);
    if (type === 'select') return optionsOf(control).find((item) => item.display === text)?.value ?? '';
    if (type === 'multiline') return text.replace(/\r\n?/gu, '\n').replace(/\n$/u, '');
    return text;
  }

  function writeValue(control: any, type: FillFieldType, value: boolean | string): void {
    const originalTextPr = control.GetRange?.().GetTextPr?.();
    function restoreTextPr(): void {
      if (!originalTextPr) return;
      const range = control.GetRange?.();
      if (!range || typeof range.SetTextPr !== 'function' || !range.SetTextPr(originalTextPr)) {
        throw new Error('保留字段格式失败');
      }
    }
    if (value === '' && (type === 'select' || type === 'date')) {
      if (control.RemoveAllElements() !== true) throw new Error('清空字段失败');
      restoreTextPr();
      return;
    }
    if (type === 'checkbox') {
      control.SetCheckBoxChecked(Boolean(value));
      return;
    }
    if (type === 'select') {
      if (!optionsOf(control).some((item) => item.value === value)) throw new Error('下拉值不在文档选项中');
      control.SelectListItem(value);
      return;
    }
    if (type === 'date') {
      const format = dateFormatOf(control);
      const match = /h/iu.test(format)
        ? /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/u.exec(String(value))
        : /^(\d{4})-(\d{2})-(\d{2})$/u.exec(String(value));
      if (!match) throw new Error('日期格式无效');
      const values = match.slice(1).map(Number);
      const year = values[0]!;
      const month = values[1]!;
      const day = values[2]!;
      const hour = values[3] ?? 0;
      const minute = values[4] ?? 0;
      const second = values[5] ?? 0;
      const date = new Date(year, month - 1, day, hour, minute, second);
      if (
        date.getFullYear() !== year ||
        date.getMonth() !== month - 1 ||
        date.getDate() !== day ||
        date.getHours() !== hour ||
        date.getMinutes() !== minute ||
        date.getSeconds() !== second
      )
        throw new Error('日期格式无效');
      control.SetDate(date);
      return;
    }
    control.RemoveAllElements();
    if (type === 'multiline') {
      String(value)
        .split('\n')
        .forEach((line, index) => {
          const paragraph = Api.CreateParagraph();
          paragraph.AddText(line);
          control.AddElement(paragraph, index);
        });
      restoreTextPr();
      return;
    }
    const run = Api.CreateRun();
    run.AddText(String(value));
    control.AddElement(run, 0);
    restoreTextPr();
  }

  function isReferenceTag(tag: string): boolean {
    return /^eln\.ref\.[\w.-]+\.[\w-]+$/u.test(tag);
  }

  const changes = Asc.scope.elnRecordFillChanges;
  if (!Array.isArray(changes)) return JSON.stringify({ ok: false, error: '缺少字段修改', results: [] });
  const controls = Api.GetDocument().GetAllContentControls();
  const results = changes.map((change) => {
    const matches = controls.filter((control: any) => control.GetTag() === change.tag);
    if (matches.length !== 1) return { tag: change.tag, ok: false, error: '无法按 Tag 定位唯一控件' };
    const control = matches[0];
    const actualType = kindOf(control);
    if (actualType !== change.type) return { tag: change.tag, ok: false, error: '字段类型与文档控件不一致' };
    const before = readValue(control, actualType);
    const originalLock = String(control.GetLock?.() ?? '');
    const unlockForWrite = originalLock === 'sdtContentLocked'
      || (isReferenceTag(change.tag) && originalLock === 'contentLocked');
    let temporarilyUnlocked = false;
    try {
      if (unlockForWrite) {
        if (control.SetLock?.('unlocked') !== true) throw new Error('受控字段解锁失败');
        temporarilyUnlocked = true;
      }
      writeValue(control, actualType, change.value);
      if (readValue(control, actualType) !== change.value) throw new Error('字段写入后回读不一致');
      if (temporarilyUnlocked && control.SetLock?.(originalLock) !== true) throw new Error('受控字段恢复锁失败');
      return { tag: change.tag, ok: true };
    } catch (error) {
      try {
        if (temporarilyUnlocked) writeValue(control, actualType, before);
        if (temporarilyUnlocked) control.SetLock?.(originalLock);
        else writeValue(control, actualType, before);
      } catch {
        /* 原生撤销是最终恢复手段。 */
      }
      return { tag: change.tag, ok: false, error: errorMessage(error) };
    }
  });
  return JSON.stringify({ ok: true, results });
}

export function cloneRepeatRowCommand(): string {
  function errorMessage(error: unknown): string {
    return error instanceof Error ? error.message : String(error);
  }

  function requireMethod(target: any, name: string, message: string): void {
    if (!target || typeof target[name] !== 'function') throw new Error(message);
  }

  function internalId(target: any, message: string): string {
    requireMethod(target, 'GetInternalId', message);
    const value = String(target.GetInternalId() ?? '');
    if (!value) throw new Error(message);
    return value;
  }

  function rowOf(control: any): any {
    requireMethod(control, 'GetParentTableCell', '重复列必须位于表格单元格内');
    const cell = control.GetParentTableCell();
    requireMethod(cell, 'GetParentRow', '重复列所在表格行无效');
    return cell.GetParentRow();
  }

  function tableOf(control: any): any {
    const cell = control.GetParentTableCell();
    requireMethod(cell, 'GetParentTable', '重复列所在表格无效');
    return cell.GetParentTable();
  }

  let targetRow: any = null;
  try {
    const spec = Asc.scope.elnRecordFillRepeatRow;
    if (!spec || !/^[\w-]+$/u.test(spec.groupCode) || !/^[\w-]+$/u.test(spec.rowId) || spec.rowId === 'prototype'
      || (spec.beforeRowId !== undefined && (!/^[\w-]+$/u.test(spec.beforeRowId)
        || spec.beforeRowId === 'prototype' || spec.beforeRowId === spec.rowId))) {
      throw new Error('重复行参数无效');
    }
    const prefix = `eln.repeat.${spec.groupCode}.prototype.`;
    const document = Api.GetDocument();
    const controls = document.GetAllContentControls();
    const prototypes = controls.filter((control: any) => String(control.GetTag?.() ?? '').startsWith(prefix));
    if (!prototypes.length) throw new Error('文档中没有重复组原型行');

    const sourceRow = rowOf(prototypes[0]);
    const sourceTable = tableOf(prototypes[0]);
    const sourceRowId = internalId(sourceRow, '无法识别重复组原型行');
    const sourceTableId = internalId(sourceTable, '无法识别重复组表格');
    const fields = new Set<string>();
    for (const control of prototypes) {
      if (internalId(rowOf(control), '无法识别重复组原型行') !== sourceRowId
        || internalId(tableOf(control), '无法识别重复组表格') !== sourceTableId) {
        throw new Error('重复组原型列不在同一表格行');
      }
      const fieldCode = String(control.GetTag()).slice(prefix.length);
      if (!fieldCode || fields.has(fieldCode)) throw new Error('重复组原型列 Tag 无效或重复');
      fields.add(fieldCode);
    }

    const runtimePrefix = `eln.repeat.${spec.groupCode}.${spec.rowId}.`;
    const existing = controls.filter((control: any) => String(control.GetTag?.() ?? '').startsWith(runtimePrefix));
    if (existing.length) {
      const existingFields = new Set<string>();
      const existingRowId = internalId(rowOf(existing[0]), '无法识别已有重复行');
      for (const control of existing) {
        const fieldCode = String(control.GetTag()).slice(runtimePrefix.length);
        if (!fields.has(fieldCode) || existingFields.has(fieldCode)
          || internalId(rowOf(control), '无法识别已有重复行') !== existingRowId
          || internalId(tableOf(control), '无法识别已有重复行表格') !== sourceTableId) {
          throw new Error('已有重复行控件不完整');
        }
        existingFields.add(fieldCode);
      }
      if (existingFields.size !== fields.size) throw new Error('已有重复行控件不完整');
      return JSON.stringify({ ok: true, tags: existing.map((control: any) => String(control.GetTag())) });
    }

    requireMethod(sourceTable, 'GetRowsCount', '当前编辑器不支持读取表格行');
    requireMethod(sourceTable, 'GetRow', '当前编辑器不支持读取表格行');
    requireMethod(sourceTable, 'AddRow', '当前编辑器不支持新增表格行');
    requireMethod(sourceRow, 'GetCellsCount', '当前编辑器不支持读取原型行单元格');
    requireMethod(sourceRow, 'GetCell', '当前编辑器不支持读取原型行单元格');
    const rowsBefore = Number(sourceTable.GetRowsCount());
    const cellsCount = Number(sourceRow.GetCellsCount());
    if (!Number.isInteger(rowsBefore) || rowsBefore < 1 || !Number.isInteger(cellsCount) || cellsCount < 1) {
      throw new Error('重复组表格结构无效');
    }
    let anchorRow = sourceTable.GetRow(rowsBefore - 1);
    let insertBefore = false;
    let targetIndex = rowsBefore;
    if (spec.beforeRowId) {
      const beforePrefix = `eln.repeat.${spec.groupCode}.${spec.beforeRowId}.`;
      const beforeControls = controls.filter((control: any) => String(control.GetTag?.() ?? '').startsWith(beforePrefix));
      if (!beforeControls.length) throw new Error('恢复位置的下一重复行不存在');
      anchorRow = rowOf(beforeControls[0]);
      if (internalId(tableOf(beforeControls[0]), '无法识别恢复位置表格') !== sourceTableId) {
        throw new Error('恢复位置与重复组原型不在同一表格');
      }
      const anchorRowId = internalId(anchorRow, '无法识别恢复位置行');
      targetIndex = -1;
      for (let rowIndex = 0; rowIndex < rowsBefore; rowIndex += 1) {
        if (internalId(sourceTable.GetRow(rowIndex), '无法识别表格行') === anchorRowId) {
          targetIndex = rowIndex;
          break;
        }
      }
      if (targetIndex < 0) throw new Error('无法定位恢复位置行');
      insertBefore = true;
    }
    requireMethod(anchorRow, 'GetCell', '当前编辑器不支持定位表格锚点行');
    const addedRow = sourceTable.AddRow(anchorRow.GetCell(0), insertBefore);
    if (!addedRow) throw new Error('新增重复行失败');
    targetRow = addedRow;
    if (Number(sourceTable.GetRowsCount()) !== rowsBefore + 1) throw new Error('新增重复行后表格行数未增加');
    if (internalId(targetRow, '无法识别新增重复行')
      !== internalId(sourceTable.GetRow(targetIndex), '无法识别表格目标行')) {
      throw new Error(insertBefore ? '恢复重复行未位于指定顺序' : '新增重复行未位于表格末尾');
    }
    requireMethod(targetRow, 'GetCellsCount', '新增重复行结构无效');
    requireMethod(targetRow, 'GetCell', '新增重复行结构无效');
    if (Number(targetRow.GetCellsCount()) !== cellsCount) throw new Error('新增重复行列数与原型不一致');

    // ONLYOFFICE 9.4 AddRow emits an isolated vMerge restart on every new cell.
    // Clear it before saving so the runtime row preserves the unmerged prototype contract.
    for (let cellIndex = 0; cellIndex < cellsCount; cellIndex += 1) {
      const targetCell = targetRow.GetCell(cellIndex);
      requireMethod(targetCell?.Cell, 'SetVMerge', '当前编辑器不支持清除新增行跨行合并标记');
      targetCell.Cell.SetVMerge(undefined);
    }

    const firstTargetCell = targetRow.GetCell(0);
    requireMethod(firstTargetCell, 'Clear', '无法清空新增行内容');
    if (firstTargetCell.Clear() === false) throw new Error('清空新增行内容失败');

    for (let cellIndex = 0; cellIndex < cellsCount; cellIndex += 1) {
      const sourceCell = sourceRow.GetCell(cellIndex);
      const targetCell = targetRow.GetCell(cellIndex);
      requireMethod(sourceCell, 'GetContent', '无法读取原型行内容');
      requireMethod(targetCell, 'AddElement', '无法复制原型行内容');
      const sourceContent = sourceCell.GetContent();
      requireMethod(sourceContent, 'GetContent', '无法读取原型行内容');
      const elements = sourceContent.GetContent();
      if (!Array.isArray(elements)) throw new Error('原型行内容结构无效');
      for (let elementIndex = 0; elementIndex < elements.length; elementIndex += 1) {
        const element = elements[elementIndex];
        requireMethod(element, 'Copy', '当前编辑器不支持复制原型行内容');
        if (targetCell.AddElement(elementIndex, element.Copy()) === false) throw new Error('复制原型行内容失败');
      }
    }

    const targetRowId = internalId(targetRow, '无法识别新增重复行');
    const copied = document.GetAllContentControls().filter((control: any) => {
      try {
        return internalId(rowOf(control), '无法识别新增重复行') === targetRowId
          && String(control.GetTag?.() ?? '').startsWith(prefix);
      } catch {
        return false;
      }
    });
    if (copied.length !== fields.size) throw new Error('新增重复行控件复制不完整');
    const tags: string[] = [];
    const copiedFields = new Set<string>();
    for (const control of copied) {
      const fieldCode = String(control.GetTag()).slice(prefix.length);
      if (!fields.has(fieldCode) || copiedFields.has(fieldCode)) throw new Error('新增重复行控件 Tag 无效或重复');
      copiedFields.add(fieldCode);
      requireMethod(control, 'SetTag', '当前编辑器不支持重写重复行 Tag');
      const tag = `eln.repeat.${spec.groupCode}.${spec.rowId}.${fieldCode}`;
      if (control.SetTag(tag) !== true || String(control.GetTag()) !== tag) throw new Error('重写重复行 Tag 失败');
      tags.push(tag);
    }
    if (copiedFields.size !== fields.size) throw new Error('新增重复行字段不完整');
    return JSON.stringify({ ok: true, tags });
  } catch (error) {
    let rollbackFailed = false;
    if (targetRow) {
      try {
        rollbackFailed = typeof targetRow.Remove !== 'function' || targetRow.Remove() !== true;
      } catch {
        rollbackFailed = true;
      }
    }
    const message = errorMessage(error);
    return JSON.stringify({ ok: false, error: rollbackFailed ? `${message}；文档回滚失败` : message });
  }
}

export function removeRepeatRowCommand(): string {
  interface InternalIdObject {
    GetInternalId?: () => unknown;
  }
  interface TableRow extends InternalIdObject {
    Remove?: () => unknown;
  }
  type TableObject = InternalIdObject;
  interface TableCell {
    GetParentRow?: () => TableRow;
    GetParentTable?: () => TableObject;
  }
  interface ContentControl {
    GetParentTableCell?: () => TableCell;
    GetTag?: () => unknown;
  }

  function errorMessage(error: unknown): string {
    return error instanceof Error ? error.message : String(error);
  }

  function internalId(target: InternalIdObject, message: string): string {
    if (typeof target?.GetInternalId !== 'function') throw new Error(message);
    const value = String(target.GetInternalId() ?? '');
    if (!value) throw new Error(message);
    return value;
  }

  function rowOf(control: ContentControl): TableRow {
    if (typeof control?.GetParentTableCell !== 'function') throw new Error('重复列必须位于表格单元格内');
    const cell = control.GetParentTableCell();
    if (typeof cell?.GetParentRow !== 'function') throw new Error('重复列所在表格行无效');
    return cell.GetParentRow();
  }

  function tableOf(control: ContentControl): TableObject {
    if (typeof control?.GetParentTableCell !== 'function') throw new Error('重复列必须位于表格单元格内');
    const cell = control.GetParentTableCell();
    if (typeof cell?.GetParentTable !== 'function') throw new Error('重复列所在表格无效');
    return cell.GetParentTable();
  }

  try {
    const spec = Asc.scope.elnRecordFillRepeatRow;
    if (!spec || !/^[\w-]+$/u.test(spec.groupCode) || !/^[\w-]+$/u.test(spec.rowId) || spec.rowId === 'prototype') {
      throw new Error('重复行参数无效');
    }
    const prototypePrefix = `eln.repeat.${spec.groupCode}.prototype.`;
    const runtimePrefix = `eln.repeat.${spec.groupCode}.${spec.rowId}.`;
    const document = Api.GetDocument();
    const controls = document.GetAllContentControls() as ContentControl[];
    const prototypes = controls.filter((control) => String(control.GetTag?.() ?? '').startsWith(prototypePrefix));
    const targets = controls.filter((control) => String(control.GetTag?.() ?? '').startsWith(runtimePrefix));
    if (!prototypes.length) throw new Error('文档中没有重复组原型行');
    if (!targets.length) throw new Error('文档中没有目标重复行');

    const prototypeFields = new Set<string>();
    for (const control of prototypes) {
      const fieldCode = String(control.GetTag?.() ?? '').slice(prototypePrefix.length);
      if (!fieldCode || prototypeFields.has(fieldCode)) throw new Error('重复组原型列 Tag 无效或重复');
      prototypeFields.add(fieldCode);
    }
    const targetRow = rowOf(targets[0]);
    const targetTable = tableOf(targets[0]);
    const targetRowId = internalId(targetRow, '无法识别目标重复行');
    const targetTableId = internalId(targetTable, '无法识别目标重复行表格');
    const targetFields = new Set<string>();
    const tags: string[] = [];
    for (const control of targets) {
      const fieldCode = String(control.GetTag?.() ?? '').slice(runtimePrefix.length);
      if (!prototypeFields.has(fieldCode) || targetFields.has(fieldCode)
        || internalId(rowOf(control), '无法识别目标重复行') !== targetRowId
        || internalId(tableOf(control), '无法识别目标重复行表格') !== targetTableId) {
        throw new Error('目标重复行控件不完整');
      }
      targetFields.add(fieldCode);
      tags.push(String(control.GetTag?.() ?? ''));
    }
    if (targetFields.size !== prototypeFields.size) throw new Error('目标重复行控件不完整');

    for (const control of controls) {
      let sameRow = false;
      try { sameRow = internalId(rowOf(control), '无法识别重复行') === targetRowId; } catch { continue; }
      if (!sameRow) continue;
      const tag = String(control.GetTag?.() ?? '');
      if (tag.startsWith('eln.repeat.') && !tag.startsWith(runtimePrefix)) {
        throw new Error('目标物理行包含其他重复身份');
      }
    }
    if (typeof targetRow.Remove !== 'function') throw new Error('当前编辑器不支持删除表格行');
    if (targetRow.Remove() !== true) throw new Error('删除重复行失败');
    const remaining = (document.GetAllContentControls() as ContentControl[])
      .filter((control) => String(control.GetTag?.() ?? '').startsWith(runtimePrefix));
    if (remaining.length) throw new Error('删除重复行后回读仍存在目标控件');
    return JSON.stringify({ ok: true, tags });
  } catch (error) {
    return JSON.stringify({ ok: false, error: errorMessage(error) });
  }
}
