/**
 * ============================================================
 * document-extraction-contract.ts — 文档提取 LLM 合约
 * ============================================================
 * 功能：
 *   定义非 Excel 文档识别的固定 JSON 合同，并把模型输出校验、
 *   规范化为可直接写入 procurement_materials 的英文字段结构。
 * ============================================================
 */

import { z } from "zod";

export const DOCUMENT_EXTRACTION_PROMPT_KEY = "automation.document_extraction.materials.system";

const EMPTY_FIELD_PLACEHOLDER_PATTERNS = [
  /^(待确认|未知|通用|不限|待识别|待识别文件|暂无|无|空|未填写|未提供|n\/?a|na|none|null)$/i,
  /^(?:Excel原始文件|PDF原始文件|Word原始文件|图片原始文件|原始文件)[，,]待识别$/,
  /^需人工核对原始文件$/,
  /^Excel 字段导入，如有缺漏请人工补充$/,
];

const FIELD_ALIASES: Record<DocumentMaterialTextField | "quantity", string[]> = {
  material_name: ["material_name", "材料名称", "物料名称", "物资名称", "名称", "品名", "商品名称", "货物名称"],
  specification: ["specification", "规格", "规格型号", "型号", "尺寸", "参数"],
  quantity: ["quantity", "数量", "采购数量", "需求数量"],
  unit: ["unit", "单位", "计量单位"],
  technical_standard: ["technical_standard", "技术标准", "执行标准", "标准"],
  material_requirement: ["material_requirement", "材质要求", "材料要求", "材质"],
  technical_requirement: ["technical_requirement", "技术要求", "技术参数", "参数要求"],
  applicable_conditions: ["applicable_conditions", "适用条件", "使用场景", "适用场景"],
  test_requirement: ["test_requirement", "检验要求", "质检要求", "检测要求"],
  acceptance_note: ["acceptance_note", "验收说明", "验收条件", "验收要求"],
  warranty_period: ["warranty_period", "质保期", "保修期限", "质保期限"],
  deadline: ["deadline", "交货期限", "交付时间", "交货时间", "到货时间"],
  invoice_type: ["invoice_type", "发票种类", "发票类型", "票据类型"],
  brand: ["brand", "品牌", "品牌名称"],
  remarks: ["remarks", "备注", "备注/附件", "说明", "补充说明"],
  delivery_address: ["delivery_address", "收货地址", "交货地址", "送货地址"],
};

export type DocumentMaterialTextField =
  | "material_name"
  | "specification"
  | "unit"
  | "technical_standard"
  | "material_requirement"
  | "technical_requirement"
  | "applicable_conditions"
  | "test_requirement"
  | "acceptance_note"
  | "warranty_period"
  | "deadline"
  | "invoice_type"
  | "brand"
  | "remarks"
  | "delivery_address";

export type DocumentMaterialExtractionItem = Record<DocumentMaterialTextField, string> & {
  quantity: number;
};

export interface DocumentMaterialExtractionPayload {
  items: DocumentMaterialExtractionItem[];
  extraction_summary: string;
  warnings: string[];
}

export interface ProcurementMaterialInsertLike extends DocumentMaterialExtractionItem {
  source_file: string;
  source_file_id?: string;
  batch_id: string;
  created_by?: string;
}

export interface ProcurementMaterialInsertContext {
  batchId: string;
  sourceFile: string;
  sourceFileId?: string;
  createdBy?: string;
}

function isEmptyFieldPlaceholder(value: unknown): boolean {
  if (value == null) {
    return true;
  }

  const text = String(value).trim();
  if (!text) {
    return true;
  }

  return EMPTY_FIELD_PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(text));
}

function normalizeText(value: unknown): string {
  if (value == null) {
    return "";
  }

  const text = String(value).trim();
  return isEmptyFieldPlaceholder(text) ? "" : text;
}

function parseQuantity(value: unknown): number {
  if (typeof value === "number" && Number.isFinite(value)) {
    return value;
  }

  const text = normalizeText(value).replace(/,/g, "");
  const match = text.match(/-?\d+(?:\.\d+)?/);
  if (!match) {
    return 0;
  }

  const parsed = Number(match[0]);
  return Number.isFinite(parsed) ? parsed : 0;
}

function parseJsonPayload(raw: unknown): unknown {
  if (typeof raw !== "string") {
    return raw;
  }

  try {
    return JSON.parse(raw);
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    throw new Error(`文档提取模型返回内容不是合法 JSON: ${message}`);
  }
}

function pickAliasedValue(source: Record<string, unknown>, field: keyof typeof FIELD_ALIASES): unknown {
  for (const alias of FIELD_ALIASES[field]) {
    if (Object.prototype.hasOwnProperty.call(source, alias)) {
      return source[alias];
    }
  }
  return undefined;
}

function normalizeItemAliases(raw: unknown): unknown {
  if (raw == null || typeof raw !== "object" || Array.isArray(raw)) {
    return raw;
  }

  const source = raw as Record<string, unknown>;
  const normalized: Record<string, unknown> = {};
  for (const field of Object.keys(FIELD_ALIASES) as Array<keyof typeof FIELD_ALIASES>) {
    const value = pickAliasedValue(source, field);
    if (value !== undefined) {
      normalized[field] = value;
    }
  }
  return { ...source, ...normalized };
}

const stringishSchema = z.preprocess(normalizeText, z.string());

const documentMaterialExtractionItemSchema = z.preprocess(normalizeItemAliases, z.object({
  material_name: stringishSchema.optional().default(""),
  specification: stringishSchema.optional().default(""),
  quantity: z.preprocess(parseQuantity, z.number()),
  unit: stringishSchema.optional().default(""),
  technical_standard: stringishSchema.optional().default(""),
  material_requirement: stringishSchema.optional().default(""),
  technical_requirement: stringishSchema.optional().default(""),
  applicable_conditions: stringishSchema.optional().default(""),
  test_requirement: stringishSchema.optional().default(""),
  acceptance_note: stringishSchema.optional().default(""),
  warranty_period: stringishSchema.optional().default(""),
  deadline: stringishSchema.optional().default(""),
  invoice_type: stringishSchema.optional().default(""),
  brand: stringishSchema.optional().default(""),
  remarks: stringishSchema.optional().default(""),
  delivery_address: stringishSchema.optional().default(""),
}));

const documentMaterialExtractionPayloadSchema = z.object({
  items: z.array(documentMaterialExtractionItemSchema),
  extraction_summary: stringishSchema.optional().default(""),
  warnings: z.array(z.preprocess(normalizeText, z.string())).optional().default([]),
});

function formatZodIssues(issues: z.ZodIssue[]): string {
  return issues
    .map((issue) => {
      const path = issue.path.length > 0 ? issue.path.join(".") : "root";
      return `${path}: ${issue.message}`;
    })
    .join("; ");
}

export function parseDocumentMaterialExtractionPayload(raw: unknown): DocumentMaterialExtractionPayload {
  const parsedJson = parseJsonPayload(raw);
  const parsed = documentMaterialExtractionPayloadSchema.safeParse(parsedJson);

  if (!parsed.success) {
    throw new Error(`文档提取模型返回结构不符合固定 JSON 合同: ${formatZodIssues(parsed.error.issues)}`);
  }

  const items = parsed.data.items.filter((item) => item.material_name.length > 0);
  if (items.length === 0) {
    throw new Error("文档提取结果为空：items 为空或所有行都缺少 material_name");
  }

  return {
    items,
    extraction_summary: parsed.data.extraction_summary || `提取了 ${items.length} 项物料`,
    warnings: parsed.data.warnings.filter((warning) => warning.length > 0),
  };
}

export function toProcurementMaterialInsert(
  item: DocumentMaterialExtractionItem,
  context: ProcurementMaterialInsertContext,
): ProcurementMaterialInsertLike {
  const material: ProcurementMaterialInsertLike = {
    ...item,
    source_file: context.sourceFile,
    batch_id: context.batchId,
  };

  if (context.sourceFileId) {
    material.source_file_id = context.sourceFileId;
  }
  if (context.createdBy) {
    material.created_by = context.createdBy;
  }

  return material;
}
