import type {
  TemplateAnnotationContext,
  TemplateBinding,
  TemplateCancelForm,
  TemplateChangeCheckboxGroupForm,
  TemplateChangeCheckboxGroupRuleForm,
  TemplateChangeCheck,
  TemplateChangeConfirmForm,
  TemplateOperationForm,
  TemplatePrivateFieldForm,
  TemplateRepairContext,
  TemplateReserveCrossTemplateScalarBindingForm,
  TemplateReserveBindingForm,
  TemplateUpgradeCheckForm,
  TemplateUpgradeConfirmForm,
  TemplateVersionCopyConfirmForm,
} from '../domain/types';
import type {
  FormulaBindingRequest,
  FormulaBindingLockSnapshot,
  FormulaMappingCheck,
  FormulaMappingContext,
  TemplateFormulaBinding,
} from '../domain/formula-mapping';
import type { CrossTemplateScalarCandidate, CrossTemplateSourceTemplate } from '../domain/types';

const ANNOTATION_PATH = '/api/eln/onlyoffice/templateAnnotation';

interface ActionResult<T> {
  code: number;
  data?: T;
  msg: string;
}

export interface TemplateAnnotationApiContext {
  apiBaseUrl: string;
  signal?: AbortSignal;
  ticket: string;
}

export interface TemplateAnnotationClient {
  activateBinding: typeof activateBinding;
  cancelBinding: typeof cancelBinding;
  changeCheckboxGroup: typeof changeCheckboxGroup;
  changeCheckboxGroupRule: typeof changeCheckboxGroupRule;
  checkBindingRemoval: typeof checkBindingRemoval;
  checkBindingVersionUpgrade: typeof checkBindingVersionUpgrade;
  checkReferencePromotion: typeof checkReferencePromotion;
  confirmBindingRemoval: typeof confirmBindingRemoval;
  confirmBindingVersionUpgrade: typeof confirmBindingVersionUpgrade;
  confirmReferencePromotion: typeof confirmReferencePromotion;
  confirmRepair: typeof confirmRepair;
  confirmVersionCopy: typeof confirmVersionCopy;
  fetchAnnotationContext: typeof fetchAnnotationContext;
  fetchCrossTemplateScalarCandidates: typeof fetchCrossTemplateScalarCandidates;
  fetchCrossTemplateSourceTemplates: typeof fetchCrossTemplateSourceTemplates;
  fetchRepairContext: typeof fetchRepairContext;
  reservePrimaryBinding: typeof reservePrimaryBinding;
  reserveCrossTemplateScalarBinding: typeof reserveCrossTemplateScalarBinding;
  reservePrivateField: typeof reservePrivateField;
  reserveReferenceBinding: typeof reserveReferenceBinding;
  suggestCode: typeof suggestCode;
  activateFormulaBinding: typeof activateFormulaBinding;
  cancelFormulaBinding: typeof cancelFormulaBinding;
  checkFormulaBindingRemoval: typeof checkFormulaBindingRemoval;
  checkFormulaBindingUpgrade: typeof checkFormulaBindingUpgrade;
  confirmFormulaBindingRemoval: typeof confirmFormulaBindingRemoval;
  confirmFormulaBindingUpgrade: typeof confirmFormulaBindingUpgrade;
  confirmFormulaVersionCopy: typeof confirmFormulaVersionCopy;
  fetchFormulaMappings: typeof fetchFormulaMappings;
  reserveFormulaBinding: typeof reserveFormulaBinding;
}

export class TemplateAnnotationApiError extends Error {
  readonly status: number;

  constructor(status: number, message: string) {
    super(message);
    this.name = 'TemplateAnnotationApiError';
    this.status = status;
  }
}

async function requestJson<T>(context: TemplateAnnotationApiContext, path: string, method: 'GET' | 'POST', body?: unknown): Promise<T> {
  const headers: Record<string, string> = { Authorization: `Ticket ${context.ticket}` };
  if (body !== undefined) headers['Content-Type'] = 'application/json';
  const response = await fetch(`${context.apiBaseUrl.replace(/\/+$/u, '')}${ANNOTATION_PATH}${path}`, {
    method,
    headers,
    body: body === undefined ? undefined : JSON.stringify(body),
    signal: context.signal,
  });
  const payload = await readActionResult<T>(response);

  if (!response.ok) {
    throw new TemplateAnnotationApiError(response.status, payload?.msg || response.statusText || '标注请求失败');
  }
  if (!payload || typeof payload.code !== 'number' || typeof payload.msg !== 'string') {
    throw new TemplateAnnotationApiError(response.status, '标注接口响应无效');
  }
  if (payload.code !== 200) {
    throw new TemplateAnnotationApiError(payload.code, payload.msg);
  }

  return payload.data as T;
}

async function readActionResult<T>(response: Response): Promise<ActionResult<T> | null> {
  try {
    return (await response.json()) as ActionResult<T>;
  } catch {
    return null;
  }
}

function bindingPath(bindingId: string, suffix: string): string {
  return `/bindings/${encodeURIComponent(bindingId)}/${suffix}`;
}

function formulaBindingPath(bindingId: string, suffix: string): string {
  return `/formulaBindings/${encodeURIComponent(bindingId)}/${suffix}`;
}

export function fetchAnnotationContext(context: TemplateAnnotationApiContext): Promise<TemplateAnnotationContext> {
  return requestJson(context, '/context', 'GET');
}

export async function suggestCode(context: TemplateAnnotationApiContext, form: { text: string }): Promise<string> {
  const result = await requestJson<{ code: string }>(context, '/codeSuggestions', 'POST', form);
  if (!result || typeof result.code !== 'string' || !/^[a-z0-9_]{1,80}$/u.test(result.code)) {
    throw new TemplateAnnotationApiError(200, '编码生成接口响应无效');
  }
  return result.code;
}

export function reservePrivateField(context: TemplateAnnotationApiContext, form: TemplatePrivateFieldForm): Promise<TemplateBinding> {
  return requestJson(context, '/privateFields/reserve', 'POST', form);
}

export function reservePrimaryBinding(context: TemplateAnnotationApiContext, form: TemplateReserveBindingForm): Promise<TemplateBinding> {
  return requestJson(context, '/bindings/reserve', 'POST', form);
}

export function changeCheckboxGroup(
  context: TemplateAnnotationApiContext,
  bindingId: string,
  form: TemplateChangeCheckboxGroupForm,
): Promise<TemplateBinding> {
  return requestJson(context, bindingPath(bindingId, 'checkboxGroup'), 'POST', form);
}

export function changeCheckboxGroupRule(
  context: TemplateAnnotationApiContext,
  groupCode: string,
  form: TemplateChangeCheckboxGroupRuleForm,
): Promise<import('../domain/types').TemplateCheckboxGroup> {
  return requestJson(context, `/checkboxGroups/${encodeURIComponent(groupCode)}`, 'POST', form);
}

export function fetchCrossTemplateSourceTemplates(context: TemplateAnnotationApiContext): Promise<CrossTemplateSourceTemplate[]> {
  return requestJson(context, '/crossTemplateSourceTemplates', 'GET');
}

export function fetchCrossTemplateScalarCandidates(
  context: TemplateAnnotationApiContext,
  sourceTemplateId: string,
): Promise<CrossTemplateScalarCandidate[]> {
  return requestJson(context, `/crossTemplateTextCandidates?sourceTemplateId=${encodeURIComponent(sourceTemplateId)}`, 'GET');
}

export function reserveCrossTemplateScalarBinding(
  context: TemplateAnnotationApiContext,
  form: TemplateReserveCrossTemplateScalarBindingForm,
): Promise<TemplateBinding> {
  return requestJson(context, '/crossTemplateTextBindings/reserve', 'POST', form);
}

export function reserveReferenceBinding(context: TemplateAnnotationApiContext, bindingId: string, form: TemplateOperationForm): Promise<TemplateBinding> {
  return requestJson(context, bindingPath(bindingId, 'references/reserve'), 'POST', form);
}

export function activateBinding(context: TemplateAnnotationApiContext, bindingId: string, form: TemplateOperationForm): Promise<void> {
  return requestJson(context, bindingPath(bindingId, 'activate'), 'POST', form);
}

export function cancelBinding(context: TemplateAnnotationApiContext, bindingId: string, form: TemplateCancelForm): Promise<void> {
  return requestJson(context, bindingPath(bindingId, 'cancel'), 'POST', form);
}

export function fetchRepairContext(context: TemplateAnnotationApiContext, bindingId: string): Promise<TemplateRepairContext> {
  return requestJson(context, bindingPath(bindingId, 'repairContext'), 'GET');
}

export function confirmRepair(context: TemplateAnnotationApiContext, bindingId: string, form: TemplateChangeConfirmForm): Promise<void> {
  return requestJson(context, bindingPath(bindingId, 'repairConfirm'), 'POST', form);
}

export function checkBindingVersionUpgrade(
  context: TemplateAnnotationApiContext,
  bindingId: string,
  form: TemplateUpgradeCheckForm,
): Promise<TemplateChangeCheck> {
  return requestJson(context, bindingPath(bindingId, 'versionUpgradeCheck'), 'POST', form);
}

export function confirmBindingVersionUpgrade(context: TemplateAnnotationApiContext, bindingId: string, form: TemplateUpgradeConfirmForm): Promise<void> {
  return requestJson(context, bindingPath(bindingId, 'versionUpgradeConfirm'), 'POST', form);
}

export function checkBindingRemoval(context: TemplateAnnotationApiContext, bindingId: string): Promise<TemplateChangeCheck> {
  return requestJson(context, bindingPath(bindingId, 'removeCheck'), 'POST');
}

export function confirmBindingRemoval(context: TemplateAnnotationApiContext, bindingId: string, form: TemplateChangeConfirmForm): Promise<void> {
  return requestJson(context, bindingPath(bindingId, 'removeConfirm'), 'POST', form);
}

export function checkReferencePromotion(context: TemplateAnnotationApiContext, bindingId: string): Promise<TemplateChangeCheck> {
  return requestJson(context, bindingPath(bindingId, 'promoteCheck'), 'POST');
}

export function confirmReferencePromotion(context: TemplateAnnotationApiContext, bindingId: string, form: TemplateChangeConfirmForm): Promise<void> {
  return requestJson(context, bindingPath(bindingId, 'promoteConfirm'), 'POST', form);
}

export function confirmVersionCopy(context: TemplateAnnotationApiContext, form: TemplateVersionCopyConfirmForm): Promise<void> {
  return requestJson(context, '/versionCopy/confirm', 'POST', form);
}

export function fetchFormulaMappings(context: TemplateAnnotationApiContext): Promise<FormulaMappingContext> {
  return requestJson(context, '/formulaContext', 'GET');
}

export function reserveFormulaBinding(
  context: TemplateAnnotationApiContext,
  form: FormulaBindingRequest & { jieguoSuoKuaizhao: FormulaBindingLockSnapshot[] },
): Promise<TemplateFormulaBinding> {
  return requestJson(context, '/formulaBindings/reserve', 'POST', form);
}

export function activateFormulaBinding(
  context: TemplateAnnotationApiContext,
  bindingId: string,
  form: { controls: FormulaControlSnapshot[]; expectedVersion: number; operationCode: string },
): Promise<void> {
  return requestJson(context, formulaBindingPath(bindingId, 'activate'), 'POST', form);
}

export function cancelFormulaBinding(
  context: TemplateAnnotationApiContext,
  bindingId: string,
  form: { expectedVersion: number; operationCode: string; reason: string },
): Promise<void> {
  return requestJson(context, formulaBindingPath(bindingId, 'cancel'), 'POST', form);
}

export function checkFormulaBindingUpgrade(
  context: TemplateAnnotationApiContext,
  bindingId: string,
  form: { formulaVersionId: string; operationCode: string; parameterBindings: Record<string, string>; resultBindingId: string },
): Promise<FormulaMappingCheck> {
  return requestJson(context, formulaBindingPath(bindingId, 'upgradeCheck'), 'POST', form);
}

export function confirmFormulaBindingUpgrade(
  context: TemplateAnnotationApiContext,
  bindingId: string,
  form: { controls: FormulaControlSnapshot[]; expectedVersion: number; operationCode: string; target: FormulaBindingRequest },
): Promise<void> {
  return requestJson(context, formulaBindingPath(bindingId, 'upgradeConfirm'), 'POST', form);
}

export function checkFormulaBindingRemoval(context: TemplateAnnotationApiContext, bindingId: string): Promise<FormulaMappingCheck> {
  return requestJson(context, formulaBindingPath(bindingId, 'removeCheck'), 'POST');
}

export function confirmFormulaBindingRemoval(
  context: TemplateAnnotationApiContext,
  bindingId: string,
  form: { controls: FormulaControlSnapshot[]; expectedVersion: number; operationCode: string },
): Promise<void> {
  return requestJson(context, formulaBindingPath(bindingId, 'removeConfirm'), 'POST', form);
}

export function confirmFormulaVersionCopy(
  context: TemplateAnnotationApiContext,
  form: { controls: FormulaControlSnapshot[]; expectedVersions: Record<string, number>; upgradeOperationCode: string },
): Promise<void> {
  return requestJson(context, '/formulaBindings/versionCopy/confirm', 'POST', form);
}

export interface FormulaControlSnapshot {
  bindingId: string;
  controlRole: string;
  controlTag: string;
  lock: string;
}

export const templateAnnotationClient: TemplateAnnotationClient = {
  activateBinding,
  cancelBinding,
  changeCheckboxGroup,
  changeCheckboxGroupRule,
  checkBindingRemoval,
  checkBindingVersionUpgrade,
  checkReferencePromotion,
  confirmBindingRemoval,
  confirmBindingVersionUpgrade,
  confirmReferencePromotion,
  confirmRepair,
  confirmVersionCopy,
  fetchAnnotationContext,
  fetchCrossTemplateScalarCandidates,
  fetchCrossTemplateSourceTemplates,
  fetchRepairContext,
  reservePrimaryBinding,
  reserveCrossTemplateScalarBinding,
  reservePrivateField,
  reserveReferenceBinding,
  suggestCode,
  activateFormulaBinding,
  cancelFormulaBinding,
  checkFormulaBindingRemoval,
  checkFormulaBindingUpgrade,
  confirmFormulaBindingRemoval,
  confirmFormulaBindingUpgrade,
  confirmFormulaVersionCopy,
  fetchFormulaMappings,
  reserveFormulaBinding,
};
