import { z } from "zod";

export const PROCUREMENT_LIST_EXTRACTION_SYSTEM_PROMPT = [
  "你是采购清单附件结构化提取助手。只根据用户提供的 PDF 渲染图、图片和 Excel 行数据抽取采购信息，不要编造。",
  "必须输出严格 JSON 对象，不要输出 Markdown，不要输出解释文字。",
  "不把图纸标题直接当采购品名；应结合图纸标注、Excel 名称和图片说明判断推荐采购品名。",
  "区分主采购件、配套材料、施工做法、安装对象、非采购对象；施工做法和安装对象不得当成采购物料。",
  "保留工程符号、单位和比较符号，例如 M30、>=16MPa、0.91~1.2、GB8624-2012 5.2.3。",
  "不确定信息进入待确认，不要凭经验补充附件里没有的字段。",
  "图片中的性能指标必须进入 fields.performance_indicators，不能只写入 remarks。",
  "返回 JSON 必须包含 file_profile、purchase_objects、warnings。",
].join("\n");

const fileTypes = [
  "text_pdf",
  "scanned_pdf",
  "cad_drawing_pdf",
  "image",
  "excel",
  "word",
  "other",
  "unknown",
] as const;

const objectRoles = [
  "primary_item",
  "accessory",
  "construction_method",
  "installation_target",
  "non_procurement",
] as const;

const evidenceSourceTypes = [
  "pdf_render",
  "pdf_text",
  "image",
  "excel",
  "word",
  "manual",
  "unknown",
] as const;

export type ProcurementListExtractionFileType = typeof fileTypes[number];
export type ProcurementListExtractionObjectRole = typeof objectRoles[number];
export type ProcurementListExtractionEvidenceSourceType = typeof evidenceSourceTypes[number];

export interface ProcurementListExtractionFileProfile {
  source_file_name?: string;
  file_type: ProcurementListExtractionFileType;
  document_title: string;
  drawing_no: string;
  page_count: number;
  diagnostics: string[];
}

export interface ProcurementListExtractionFields {
  specification: string;
  material_requirement: string;
  technical_requirement: string;
  test_requirement: string;
  technical_standard: string;
  applicable_conditions: string;
  main_dimensions: string;
  tolerance: string;
  performance_indicators: string;
  accessory_materials: string;
  supply_form: string;
  acceptance_documents: string;
  quantity: number;
  unit: string;
  invoice_type: string;
  delivery_address: string;
  remarks: string;
  pending_confirmations: string[];
}

export interface ProcurementListExtractionEvidence {
  source_file_name: string;
  source_type: ProcurementListExtractionEvidenceSourceType;
  field: string;
  quote: string;
  page_number?: number;
  row_index?: number;
  region_label?: string;
  confidence?: number;
}

export interface ProcurementListPurchaseObject {
  object_role: ProcurementListExtractionObjectRole;
  original_name: string;
  recommended_name: string;
  fields: ProcurementListExtractionFields;
  evidence: ProcurementListExtractionEvidence[];
  warnings: string[];
  confidence?: number;
}

export interface ProcurementListExtractionPayload {
  file_profile: ProcurementListExtractionFileProfile;
  purchase_objects: ProcurementListPurchaseObject[];
  warnings: string[];
}

export interface ProcurementListExtractionImageInput {
  source_file_name: string;
  source_type: "pdf_render" | "image";
  label: string;
  dataUrl: string;
}

export interface ProcurementListExtractionExcelRow {
  source_file_name: string;
  row_index: number;
  values: Record<string, unknown>;
}

export interface BuildProcurementListExtractionRequestInput {
  model: string;
  fileProfiles: ProcurementListExtractionFileProfile[];
  imageInputs: ProcurementListExtractionImageInput[];
  excelRows?: ProcurementListExtractionExcelRow[];
}

export type ProcurementListExtractionContentPart =
  | { type: "image_url"; image_url: { url: string } }
  | { type: "text"; text: string };

export interface ProcurementListExtractionRequest {
  model: string;
  messages: [
    { role: "system"; content: string },
    { role: "user"; content: ProcurementListExtractionContentPart[] },
  ];
  temperature: number;
  response_format: { type: "json_object" };
}

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

function normalizeTextArray(value: unknown): string[] {
  if (!Array.isArray(value)) {
    return [];
  }
  return value.map(normalizeText).filter(Boolean);
}

function parseQuantity(value: unknown): number {
  if (typeof value === "number" && Number.isFinite(value)) {
    return value;
  }
  const match = normalizeText(value).replace(/,/g, "").match(/-?\d+(?:\.\d+)?/);
  if (!match) {
    return 0;
  }
  const parsed = Number(match[0]);
  return Number.isFinite(parsed) ? parsed : 0;
}

function parseOptionalConfidence(value: unknown): number | undefined {
  if (value == null || value === "") {
    return undefined;
  }
  const parsed = typeof value === "number" ? value : Number(value);
  return Number.isFinite(parsed) ? Math.max(0, Math.min(1, parsed)) : undefined;
}

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 formatZodIssues(issues: z.ZodIssue[]): string {
  return issues
    .map((issue) => {
      const path = issue.path.length > 0 ? issue.path.join(".") : "root";
      return `${path}: ${issue.message}`;
    })
    .join("; ");
}

const stringishSchema = z.preprocess(normalizeText, z.string());
const stringArraySchema = z.preprocess(normalizeTextArray, z.array(z.string()));
const confidenceSchema = z.preprocess(parseOptionalConfidence, z.number().min(0).max(1).optional());
const fileTypeSchema = z.preprocess(
  (value) => normalizeText(value) || "unknown",
  z.enum(fileTypes),
);
const objectRoleSchema = z.preprocess(
  (value) => normalizeText(value) || "primary_item",
  z.enum(objectRoles),
);

const fileProfileSchema = z.object({
  source_file_name: stringishSchema.optional(),
  file_type: fileTypeSchema,
  document_title: stringishSchema,
  drawing_no: stringishSchema,
  page_count: z.preprocess(parseQuantity, z.number().int().nonnegative()),
  diagnostics: stringArraySchema,
});

const fieldsSchema = z.object({
  specification: stringishSchema,
  material_requirement: stringishSchema,
  technical_requirement: stringishSchema,
  test_requirement: stringishSchema,
  technical_standard: stringishSchema,
  applicable_conditions: stringishSchema,
  main_dimensions: stringishSchema,
  tolerance: stringishSchema,
  performance_indicators: stringishSchema,
  accessory_materials: stringishSchema,
  supply_form: stringishSchema,
  acceptance_documents: stringishSchema,
  quantity: z.preprocess(parseQuantity, z.number()),
  unit: stringishSchema,
  invoice_type: stringishSchema,
  delivery_address: stringishSchema,
  remarks: stringishSchema,
  pending_confirmations: stringArraySchema,
});

const evidenceSchema = z.object({
  source_file_name: stringishSchema,
  source_type: z.enum(evidenceSourceTypes),
  field: stringishSchema,
  quote: stringishSchema,
  page_number: z.preprocess(parseQuantity, z.number().int().positive().optional()).optional(),
  row_index: z.preprocess(parseQuantity, z.number().int().nonnegative().optional()).optional(),
  region_label: stringishSchema.optional(),
  confidence: confidenceSchema,
});

const evidenceArraySchema = z.preprocess(
  (value) => Array.isArray(value) ? value : [],
  z.array(evidenceSchema),
);

const purchaseObjectSchema = z.object({
  object_role: objectRoleSchema,
  original_name: stringishSchema,
  recommended_name: stringishSchema,
  fields: fieldsSchema,
  evidence: evidenceArraySchema,
  warnings: stringArraySchema,
  confidence: confidenceSchema,
});

const extractionPayloadSchema = z.object({
  file_profile: fileProfileSchema,
  purchase_objects: z.array(purchaseObjectSchema),
  warnings: stringArraySchema,
});

export function parseProcurementListExtractionPayload(raw: unknown): ProcurementListExtractionPayload {
  const parsedJson = parseJsonPayload(raw);
  const parsed = extractionPayloadSchema.safeParse(parsedJson);

  if (!parsed.success) {
    throw new Error(`采购清单多模态模型返回结构不符合固定 JSON 合同: ${formatZodIssues(parsed.error.issues)}`);
  }

  const data = parsed.data as ProcurementListExtractionPayload;
  const purchaseObjects = data.purchase_objects.filter((object) => object.object_role !== "non_procurement");
  if (purchaseObjects.length === 0) {
    throw new Error("采购清单多模态提取结果为空：purchase_objects 为空或全部为 non_procurement");
  }

  return {
    file_profile: data.file_profile,
    purchase_objects: purchaseObjects,
    warnings: data.warnings.filter(Boolean),
  };
}

function buildUserInstruction(input: BuildProcurementListExtractionRequestInput): string {
  const imageMetadata = input.imageInputs.map((image) => ({
    source_file_name: image.source_file_name,
    source_type: image.source_type,
    label: image.label,
  }));

  return [
    "请从这些采购附件中提取文件级结构化采购对象。",
    "输入图片可能包括 PDF 页面渲染图、PDF 高清裁剪图、JPEG/PNG 图片；Excel 行数据在下方 JSON 中。",
    "逐张检查所有图片，并结合 Excel 行补全数量、单位、发票、收货地址等字段。",
    "不要把图纸标题直接当采购品名；区分主采购件、配套材料、施工做法、安装对象、非采购对象。",
    "保留工程符号；不确定信息进入待确认。",
    "输出 JSON schema: { file_profile, purchase_objects, warnings }。",
    "purchase_objects[].fields 必须包含 specification, material_requirement, technical_requirement, test_requirement, technical_standard, applicable_conditions, main_dimensions, tolerance, performance_indicators, accessory_materials, supply_form, acceptance_documents, quantity, unit, invoice_type, delivery_address, remarks, pending_confirmations。",
    "附件图片清单:",
    JSON.stringify(imageMetadata, null, 2),
    "文件诊断:",
    JSON.stringify(input.fileProfiles, null, 2),
    "Excel 行数据:",
    JSON.stringify(input.excelRows ?? [], null, 2),
  ].join("\n");
}

export function buildProcurementListExtractionRequest(
  input: BuildProcurementListExtractionRequestInput,
): ProcurementListExtractionRequest {
  const imageParts = input.imageInputs.map<ProcurementListExtractionContentPart>((image) => ({
    type: "image_url",
    image_url: { url: image.dataUrl },
  }));

  return {
    model: input.model,
    messages: [
      { role: "system", content: PROCUREMENT_LIST_EXTRACTION_SYSTEM_PROMPT },
      {
        role: "user",
        content: [
          ...imageParts,
          { type: "text", text: buildUserInstruction(input) },
        ],
      },
    ],
    temperature: 0.1,
    response_format: { type: "json_object" },
  };
}
