/**
 * ============================================================
 * standardization-llm.ts — 标准化 LLM 客户端（OpenAI 兼容接口）
 * ============================================================
 * 功能：
 *   1. 惰性初始化 OpenAI 客户端（按阶段解析 Provider 配置）
 *   2. analyzeProduct() — 对一行 CSV 数据调用 LLM，提取材质、用途、
 *      简化规格、品牌简称、关键词参数词、修饰词、AI 替代词
 * 
 * 配置来源（按优先级）：
 *   1. 阶段级 Provider 路由（env 或 Supabase llm_stage_routes）
 *   2. Provider 凭证（env 或 Supabase llm_provider_configs）
 *   3. legacy LLM_BASE_URL / LLM_API_KEY / LLM_MODEL fallback
 * ============================================================
 */

import OpenAI from "openai";
import type {
  RawCsvRow,
  LlmAnalysisResult,
  KeywordInputRow,
  AiKeywordAnalysis,
  OfflineCombinedStandardizationResult,
  StandardizationSourceSnapshot,
} from "../core/types.ts";
import {
  createLlmClient,
  resolveStageLlmModel,
  resolveLlmRuntimeConfig as resolveLlmRuntimeConfigImpl,
  type ResolvedLlmRuntimeConfig,
} from "../core/llm-client.ts";
import { runWithLlmDebugLog } from "../core/llm-debug-log.js";
import { resolveTextPrompt } from "../internal/prompt-runtime.js";
import {
  COMBINED_STANDARDIZATION_PROMPT_KEY,
  buildCombinedStandardizationBatchUserMessage,
  buildCombinedStandardizationUserMessageFromRaw,
} from "./combined-standardization-contract.ts";
import { applyModelNumberOverride } from "./model-number-override.ts";
import {
  parseOfflineCombinedResult,
  parseParameterInterpretations,
  toLlmAnalysisResult,
} from "./offline-batch-result.ts";
import {
  buildCategorizedHumanFixHint,
  normalizeHumanFixHint,
} from "./human-fix-hint.ts";

const KEYWORD_SEMANTIC_PROMPT_KEY = "automation.standardization.keyword_semantic.system";
export const ONLINE_STANDARDIZATION_MODEL = "glm-5.1";
const DEFAULT_STANDARDIZATION_LLM_MAX_TOKENS = 8192;

export type { ResolvedLlmRuntimeConfig };

// ── 惰性初始化 OpenAI 兼容客户端 ─────────────────────────────
const _clientsByStage = new Map<
  StandardizationLlmStage,
  { client: OpenAI; provider: string; runtimeFallbackModel: string }
>();
let llmQueueTail: Promise<void> = Promise.resolve();
let llmQueuePendingCount = 0;
let lastLlmRequestFinishedAt = 0;

type StandardizationLlmStage = "standardization" | "keyword_semantic";
type ChatCompletionResponse = Awaited<ReturnType<OpenAI["chat"]["completions"]["create"]>>;

function parsePositiveInteger(value: string | undefined, fallback: number): number {
  const parsed = Number.parseInt(value ?? "", 10);
  return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
}

function normalizeEnvString(value: string | undefined): string {
  return String(value ?? "").trim();
}

function getStandardizationLlmMaxTokens(): number {
  return parsePositiveInteger(
    process.env.STANDARDIZATION_LLM_MAX_TOKENS,
    DEFAULT_STANDARDIZATION_LLM_MAX_TOKENS,
  );
}

function getStandardizationLlmThinkingMode(): "enabled" | "disabled" {
  return normalizeEnvString(process.env.STANDARDIZATION_LLM_THINKING).toLowerCase() === "enabled"
    ? "enabled"
    : "disabled";
}

function getStandardizationLlmReasoningEffort(): string {
  return normalizeEnvString(process.env.STANDARDIZATION_LLM_REASONING_EFFORT) || "medium";
}

function isDeepSeekProvider(provider: string): boolean {
  return provider.trim().toLowerCase() === "deepseek";
}

function isLegacyDeepSeekAlias(model: string): boolean {
  return model === "deepseek-chat" || model === "deepseek-reasoner";
}

function warnLegacyDeepSeekAlias(provider: string, model: string): void {
  if (!isDeepSeekProvider(provider) || !isLegacyDeepSeekAlias(model)) {
    return;
  }
  console.warn(
    `[LLM] ${model} 是 DeepSeek 兼容旧别名，官方文档提示将在 2026-07-24 弃用；建议改用 deepseek-v4-pro 或 deepseek-v4-flash。`,
  );
}

function ensureJsonOutputPrompt(prompt: string): string {
  if (/json/i.test(prompt)) {
    return prompt;
  }

  return [
    prompt.trim(),
    "",
    "必须只输出合法 JSON 对象，不要 Markdown，不要解释。",
    "JSON 输出样例：{\"outcome\":\"success\",\"suggested_keyword\":\"示例搜索词\"}",
  ].filter(Boolean).join("\n");
}

class RetryableLlmContentError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "RetryableLlmContentError";
  }
}

function createAbortError(message: string): Error {
  const error = new Error(message);
  error.name = "AbortError";
  return error;
}

export function isAbortError(error: unknown): boolean {
  if (!error || typeof error !== "object") {
    return false;
  }

  const candidate = error as { name?: unknown; message?: unknown };
  return candidate.name === "AbortError"
    || candidate.message === "This operation was aborted";
}

function sleep(ms: number, signal?: AbortSignal): Promise<void> {
  if (signal?.aborted) {
    return Promise.reject(createAbortError("LLM 请求已取消"));
  }

  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => {
      signal?.removeEventListener("abort", handleAbort);
      resolve();
    }, ms);

    const handleAbort = () => {
      clearTimeout(timeout);
      signal?.removeEventListener("abort", handleAbort);
      reject(createAbortError("LLM 请求已取消"));
    };

    signal?.addEventListener("abort", handleAbort, { once: true });
  });
}

function getLlmQueueMinIntervalMs(): number {
  return parsePositiveInteger(process.env.LLM_QUEUE_MIN_INTERVAL_MS, 3000);
}

async function enqueueLlmRequest<T>(
  label: string,
  operation: () => Promise<T>,
  options: { signal?: AbortSignal } = {},
): Promise<T> {
  if (options.signal?.aborted) {
    throw createAbortError("LLM 请求已取消");
  }

  llmQueuePendingCount += 1;
  const queuedCount = llmQueuePendingCount;
  const scheduled = llmQueueTail.then(async () => {
    if (options.signal?.aborted) {
      throw createAbortError("LLM 请求已取消");
    }

    const minIntervalMs = getLlmQueueMinIntervalMs();
    const elapsedSinceLastFinish = Date.now() - lastLlmRequestFinishedAt;
    const waitMs = lastLlmRequestFinishedAt > 0
      ? Math.max(0, minIntervalMs - elapsedSinceLastFinish)
      : 0;

    if (waitMs > 0) {
      console.log(`[LLM queue] ${label} 前方排队，等待 ${waitMs}ms 后发送`);
      await sleep(waitMs, options.signal);
    }

    console.log(`[LLM queue] 开始执行 ${label}，当前队列剩余 ${Math.max(0, queuedCount - 1)} 项`);

    try {
      return await operation();
    } finally {
      lastLlmRequestFinishedAt = Date.now();
      llmQueuePendingCount = Math.max(0, llmQueuePendingCount - 1);
    }
  });

  llmQueueTail = scheduled.then(
    () => undefined,
    () => undefined,
  );

  return scheduled;
}

function getErrorStatus(error: unknown): number | null {
  if (!error || typeof error !== "object") {
    return null;
  }

  const candidate = error as {
    status?: unknown;
    response?: { status?: unknown };
  };

  if (typeof candidate.status === "number") {
    return candidate.status;
  }

  if (typeof candidate.response?.status === "number") {
    return candidate.response.status;
  }

  return null;
}

function isRateLimitError(error: unknown): boolean {
  if (!error || typeof error !== "object") {
    return false;
  }

  const candidate = error as {
    code?: unknown;
    type?: unknown;
    message?: unknown;
  };
  const status = getErrorStatus(error);
  const code = typeof candidate.code === "string" ? candidate.code.toLowerCase() : "";
  const errorType = typeof candidate.type === "string" ? candidate.type.toLowerCase() : "";
  const message = typeof candidate.message === "string" ? candidate.message.toLowerCase() : "";

  return status === 429
    || code === "429"
    || code === "rate_limit_exceeded"
    || errorType.includes("rate_limit")
    || message.includes("rate limit")
    || message.includes("速率限制")
    || message.includes("请求频率")
    || message.includes("429");
}

function isRetryableLlmError(error: unknown): boolean {
  return error instanceof RetryableLlmContentError || isRateLimitError(error);
}

async function withLlmRetryInternal<T>(
  label: string,
  operation: () => Promise<T>,
  options: { signal?: AbortSignal } = {},
): Promise<T> {
  const maxAttempts = parsePositiveInteger(process.env.LLM_RETRY_MAX_ATTEMPTS, 4);
  const baseDelayMs = parsePositiveInteger(process.env.LLM_RETRY_BASE_DELAY_MS, 1500);

  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    if (options.signal?.aborted) {
      throw createAbortError("LLM 请求已取消");
    }

    try {
      return await operation();
    } catch (error) {
      if (isAbortError(error)) {
        throw error;
      }

      if (!isRetryableLlmError(error) || attempt >= maxAttempts) {
        throw error;
      }

      const delayMs = baseDelayMs * 2 ** (attempt - 1) + Math.floor(Math.random() * 300);
      const message = error instanceof Error ? error.message : String(error);
      console.warn(`[LLM] ${label} 可重试错误，${delayMs}ms 后进行第 ${attempt + 1}/${maxAttempts} 次重试: ${message}`);
      await sleep(delayMs, options.signal);
    }
  }

  throw new Error(`[LLM] ${label} 重试耗尽`);
}

function resolvePromptScope(config: { provider?: string; model?: string } = {}) {
  return {
    provider: String(config.provider ?? "").trim() || process.env.PROMPT_PROVIDER || process.env.LLM_PROVIDER || "*",
    model: String(config.model ?? "").trim() || process.env.LLM_MODEL || "*",
  };
}

async function resolveTextPromptContent(
  promptKey: string,
  config: { provider?: string; model?: string } = {},
): Promise<string> {
  const prompt = await resolveTextPrompt(promptKey, {
    ...resolvePromptScope(config),
  });

  return ensureJsonOutputPrompt(prompt.text);
}

/**
 * @deprecated 直接从 `../core/llm-client.ts` import 同名 API。
 * 此处保留是为了不破坏既有 import（bigmodel-batch-client / document-extraction-llm / server）。
 */
export const resolveLlmRuntimeConfig = resolveLlmRuntimeConfigImpl;

/**
 * 从 DB 加载 LLM 配置，若无则回退到环境变量，初始化 OpenAI 客户端。
 * 客户端在模块内单例缓存，可通过 refreshLlmClient() 强制刷新。
 */
async function ensureClient(stage: StandardizationLlmStage): Promise<{ client: OpenAI; provider: string; runtimeFallbackModel: string }> {
  const cached = _clientsByStage.get(stage);
  if (cached) return cached;

  const { client, config } = await createLlmClient({ maxRetries: 0, stage });
  const next = {
    client,
    runtimeFallbackModel: config.model,
    provider: config.provider,
  };
  _clientsByStage.set(stage, next);
  return next;
}

export function resolveOnlineStandardizationModel(fallbackModel?: string | null): string {
  return resolveStageLlmModel("standardization", {
    fallbackModel: fallbackModel ?? ONLINE_STANDARDIZATION_MODEL,
  });
}

export function resolveKeywordSemanticModel(fallbackModel?: string | null): string {
  return resolveStageLlmModel("keyword_semantic", {
    fallbackModel: fallbackModel ?? ONLINE_STANDARDIZATION_MODEL,
  });
}

async function ensureClientForStage(
  stage: StandardizationLlmStage,
): Promise<{ client: OpenAI; model: string; provider: string }> {
  const { client, provider, runtimeFallbackModel } = await ensureClient(stage);
  const model = stage === "keyword_semantic"
    ? resolveKeywordSemanticModel(runtimeFallbackModel)
    : resolveOnlineStandardizationModel(runtimeFallbackModel);
  warnLegacyDeepSeekAlias(provider, model);
  return { client, model, provider };
}

/**
 * 强制刷新 LLM 客户端（下次调用时重新从 DB 读取配置）
 */
export function refreshLlmClient(): void {
  _clientsByStage.clear();
}

function buildChatCompletionRequest(params: {
  provider: string;
  model: string;
  messages: Array<{ role: "system" | "user"; content: string }>;
  temperature: number;
}): Record<string, unknown> {
  const request: Record<string, unknown> = {
    model: params.model,
    messages: params.messages,
    temperature: params.temperature,
    response_format: { type: "json_object" },
  };

  if (isDeepSeekProvider(params.provider)) {
    request.max_tokens = getStandardizationLlmMaxTokens();
    const thinkingMode = getStandardizationLlmThinkingMode();
    request.thinking = { type: thinkingMode };
    if (thinkingMode === "enabled") {
      request.reasoning_effort = getStandardizationLlmReasoningEffort();
    }
  }

  return request;
}

function buildLlmDebugContext(params: {
  stage: StandardizationLlmStage;
  label: string;
  provider: string;
  model: string;
}) {
  return {
    scope: "automation",
    stage: params.stage,
    label: params.label,
    provider: params.provider,
    model: params.model,
  };
}

function extractChatCompletionContent(
  response: ChatCompletionResponse,
  label: string,
): string {
  const choice = response.choices[0];
  if (choice?.finish_reason === "length") {
    throw new Error(
      `[LLM] ${label} 响应被截断。请调大 STANDARDIZATION_LLM_MAX_TOKENS 或缩短输入。`,
    );
  }

  const content = choice?.message?.content ?? "";
  if (!content.trim()) {
    throw new RetryableLlmContentError(`[LLM] ${label} 返回空 content`);
  }

  return content;
}

function buildLine(label: string, value: unknown) {
  const text = String(value ?? "").trim();
  return text ? `${label}：${text}` : "";
}

function buildLlmAnalysisFallback(row: RawCsvRow): LlmAnalysisResult {
  return {
    material: "",
    usage: "",
    simplified_spec: row.规格型号,
    brand_term: "",
    keyword_parameter_terms: [],
    modifier_terms: [],
    alternative_terms: [],
  };
}

async function requestCombinedStandardizationContent(
  row: RawCsvRow,
  options: { signal?: AbortSignal } = {},
): Promise<string> {
  const { client, model, provider } = await ensureClientForStage("standardization");
  const userMessage = buildCombinedStandardizationUserMessageFromRaw(row);
  const systemPrompt = await resolveTextPromptContent(
    COMBINED_STANDARDIZATION_PROMPT_KEY,
    { provider, model },
  );

  const response = await enqueueLlmRequest(
    `analyzeProductCombined:${row.名称 || "未命名物料"}`,
    () => withLlmRetryInternal("analyzeProductCombined", async () => {
      const request = buildChatCompletionRequest({
        provider,
        model,
        messages: [
          { role: "system", content: systemPrompt },
          { role: "user", content: userMessage },
        ],
        temperature: 0.1,
      });
      const completion = await runWithLlmDebugLog(
        buildLlmDebugContext({
          stage: "standardization",
          label: "analyzeProductCombined",
          provider,
          model,
        }),
        request,
        () => client.chat.completions.create(request as never, {
          signal: options.signal,
        }),
      );
      return extractChatCompletionContent(completion, "analyzeProductCombined");
    }, { signal: options.signal }),
    options,
  );

  return response;
}

function cleanJsonText(value: string) {
  return value.replace(/```json\s*/g, "").replace(/```\s*/g, "").trim();
}

function parseCombinedBatchItems(content: string): Array<Record<string, unknown>> {
  const parsed = JSON.parse(cleanJsonText(content)) as unknown;

  if (Array.isArray(parsed)) {
    return parsed.filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object" && !Array.isArray(item));
  }

  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
    const items = (parsed as { items?: unknown }).items;
    if (Array.isArray(items)) {
      return items.filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object" && !Array.isArray(item));
    }
  }

  throw new Error("批量标准化结果必须是 JSON 对象，且包含 items 数组");
}

async function requestCombinedStandardizationBatchContent(
  rows: Array<{ materialId: string; row: RawCsvRow }>,
  options: { signal?: AbortSignal } = {},
): Promise<string> {
  const { client, model, provider } = await ensureClientForStage("standardization");
  const userMessage = buildCombinedStandardizationBatchUserMessage(rows);
  const systemPrompt = await resolveTextPromptContent(
    COMBINED_STANDARDIZATION_PROMPT_KEY,
    { provider, model },
  );

  const response = await enqueueLlmRequest(
    `analyzeProductsCombined:${rows.length} rows`,
    () => withLlmRetryInternal("analyzeProductsCombined", async () => {
      const request = buildChatCompletionRequest({
        provider,
        model,
        messages: [
          { role: "system", content: systemPrompt },
          { role: "user", content: userMessage },
        ],
        temperature: 0.1,
      });
      const completion = await runWithLlmDebugLog(
        buildLlmDebugContext({
          stage: "standardization",
          label: "analyzeProductsCombined",
          provider,
          model,
        }),
        request,
        () => client.chat.completions.create(request as never, {
          signal: options.signal,
        }),
      );
      return extractChatCompletionContent(completion, "analyzeProductsCombined");
    }, { signal: options.signal }),
    options,
  );

  return response;
}

export async function analyzeProductCombined(
  row: RawCsvRow,
  options: { signal?: AbortSignal } = {},
): Promise<OfflineCombinedStandardizationResult> {
  const content = await requestCombinedStandardizationContent(row, options);
  return parseOfflineCombinedResult(content, row);
}

export async function analyzeProductsCombined(
  rows: Array<{ materialId: string; row: RawCsvRow }>,
  options: { signal?: AbortSignal } = {},
): Promise<Map<string, OfflineCombinedStandardizationResult>> {
  if (rows.length === 0) {
    return new Map();
  }

  if (rows.length === 1) {
    const single = rows[0]!;
    return new Map([[single.materialId, await analyzeProductCombined(single.row, options)]]);
  }

  const content = await requestCombinedStandardizationBatchContent(rows, options);
  const items = parseCombinedBatchItems(content);
  const rowsByMaterialId = new Map(rows.map((item) => [item.materialId, item.row]));
  const results = new Map<string, OfflineCombinedStandardizationResult>();

  for (const item of items) {
    const materialId = typeof item.material_id === "string" ? item.material_id.trim() : "";
    const row = rowsByMaterialId.get(materialId);
    if (!materialId || !row || results.has(materialId)) {
      continue;
    }

    const { material_id: _materialId, ...resultPayload } = item;
    results.set(materialId, parseOfflineCombinedResult(JSON.stringify(resultPayload), row));
  }

  const missingIds = rows
    .map((row) => row.materialId)
    .filter((materialId) => !results.has(materialId));
  if (missingIds.length > 0) {
    throw new Error(`批量标准化结果缺少 ${missingIds.length} 条物料: ${missingIds.join(", ")}`);
  }

  return results;
}

/**
 * 调用 LLM 分析一行 CSV 数据，提取结构化信息
 * @param row - 一行 CSV 原始数据
 * @returns LLM 解析出的材质、用途、简化规格、品牌简称、关键词参数词、修饰词、AI 替代词
 */
export async function analyzeProduct(
  row: RawCsvRow,
  options: { signal?: AbortSignal } = {},
): Promise<LlmAnalysisResult> {
  const content = await requestCombinedStandardizationContent(row, options);

  try {
    return toLlmAnalysisResult(parseOfflineCombinedResult(content, row));
  } catch {
    // JSON 解析失败时，使用基于规则的回退方案
    console.warn(`[LLM] JSON 解析失败，使用回退方案。原始响应: ${content}`);
    return buildLlmAnalysisFallback(row);
  }
}

/**
 * 调用 LLM 对关键词输入行进行深度语义分析，生成最佳 1688 搜索词
 * @param row - 关键词输入行（含 LLM 已提取的材质、用途、简化规格等字段）
 * @returns AI 分析的 6 个结构字段
 */
export async function analyzeProductForKeyword(
  row: KeywordInputRow,
  source?: Partial<StandardizationSourceSnapshot>,
  options: { signal?: AbortSignal } = {},
): Promise<AiKeywordAnalysis> {
  const { client, model, provider } = await ensureClientForStage("keyword_semantic");
  const systemPrompt = await resolveTextPromptContent(
    KEYWORD_SEMANTIC_PROMPT_KEY,
    { provider, model },
  );

  const userMessage = [
    buildLine("名称", row.name),
    buildLine("规格型号", row.spec),
    buildLine("品牌/技术要求", row.brand),
    buildLine("备注", row.notes),
    buildLine("材质（已提取）", row.material),
    buildLine("用途（已提取）", row.usage),
    buildLine("简化规格（已提取）", row.simplified_spec),
    buildLine("材质要求", source?.material_requirement),
    buildLine("技术标准", source?.technical_standard),
    buildLine("技术要求", source?.technical_requirement),
    buildLine("适用条件", source?.applicable_conditions),
    buildLine("检验要求", source?.test_requirement),
    buildLine("验收说明", source?.acceptance_note),
    buildLine("质保期", source?.warranty_period),
    buildLine("交货期限", source?.deadline),
  ]
    .filter(Boolean)
    .join("\n");

  const response = await enqueueLlmRequest(
    `analyzeProductForKeyword:${row.name || "未命名物料"}`,
    () => withLlmRetryInternal("analyzeProductForKeyword", async () => {
      const request = buildChatCompletionRequest({
        provider,
        model,
        messages: [
          { role: "system", content: systemPrompt },
          { role: "user", content: userMessage },
        ],
        temperature: 0.3,
      });
      const completion = await runWithLlmDebugLog(
        buildLlmDebugContext({
          stage: "keyword_semantic",
          label: "analyzeProductForKeyword",
          provider,
          model,
        }),
        request,
        () => client.chat.completions.create(request as never, {
          signal: options.signal,
        }),
      );
      return extractChatCompletionContent(completion, "analyzeProductForKeyword");
    }, { signal: options.signal }),
    options,
  );

  const content = response;
  const cleaned = content.replace(/```json\s*/g, "").replace(/```\s*/g, "").trim();

  try {
    const parsed = JSON.parse(cleaned) as AiKeywordAnalysis;
    const suggestedKeyword = (parsed.suggested_keyword || "").trim();
    const missingRequiredFields = Array.isArray(parsed.missing_required_fields)
      ? parsed.missing_required_fields
          .filter((item): item is string => typeof item === "string")
          .map((item) => item.trim())
          .filter(Boolean)
      : [];
    const canSearch1688 = Boolean(parsed.can_search_1688);
    const normalizedOutcome: "success" | "failed" = parsed.outcome === "success"
      && canSearch1688
      && suggestedKeyword.length > 0
      && missingRequiredFields.length === 0
      ? "success"
      : "failed";
    const failureReason = normalizedOutcome === "failed"
      ? (parsed.failure_reason || (missingRequiredFields.length > 0
          ? `缺少关键采购信息：${missingRequiredFields.join("、")}`
          : suggestedKeyword
            ? "当前信息不足以确认是否可直接在 1688 采购"
            : "AI 无法生成可靠的 1688 搜索词"))
      : "";
    const humanFixHint = normalizedOutcome === "failed"
      ? normalizeHumanFixHint(parsed.human_fix_hint, missingRequiredFields)
      : "";

    // 型号关联字典后处理：对已知型号强制 success
    const matchText = `${row.name ?? ""} ${row.spec ?? ""}`;
    return applyModelNumberOverride(matchText, {
      reasoning: parsed.reasoning || "",
      suggested_keyword: normalizedOutcome === "success" ? suggestedKeyword : "",
      product_type: parsed.product_type || "",
      structure_summary: parsed.structure_summary || "",
      component_roles: parsed.component_roles || "",
      purchase_type: parsed.purchase_type || "",
      outcome: normalizedOutcome,
      can_search_1688: normalizedOutcome === "success",
      missing_required_fields: missingRequiredFields,
      failure_reason: failureReason,
      human_fix_hint: humanFixHint,
      required_attributes_summary: parsed.required_attributes_summary || "",
      parameter_interpretations: parseParameterInterpretations(parsed.parameter_interpretations),
      identified_unique_model: normalizedOutcome === "success"
        && typeof parsed.identified_unique_model === "string"
        && parsed.identified_unique_model.trim()
        ? parsed.identified_unique_model.trim()
        : "",
    });
  } catch {
    console.warn(`[LLM] analyzeProductForKeyword JSON 解析失败。原始响应: ${content}`);
    return {
      reasoning: "",
      suggested_keyword: "",
      product_type: "",
      structure_summary: "",
      component_roles: "",
      purchase_type: "",
      outcome: "failed",
      can_search_1688: false,
      missing_required_fields: [],
      failure_reason: "模型返回格式异常，无法确认具体采购对象",
      human_fix_hint: buildCategorizedHumanFixHint([]),
      required_attributes_summary: "",
      parameter_interpretations: [],
    };
  }
}
