/**
 * ============================================================
 * keyword-generator.ts — 搜索关键词生成 + 预览行构建模块
 * ============================================================
 * 功能：
 *   1. buildKeywordInputRow() — 从 ParsedProduct 构建关键词输入行
 *   2. buildSearchPlanRows()  — 从 ParsedProduct 构建动态搜索计划行
 *   3. buildAllRows()         — 批量处理，返回两类预览行数据
 * 
 * 搜索词算法说明（对照 自动化流程.md）：
 *   - 第1轮：名称 + 核心参数词 + 规格 + 修饰词（总长度控制在 30 字内）
 *   - 参数词来自所有字段，尤其包括品牌列里可拆出的规格/型号/材质/品牌/用途
 *   - 中间轮：依次删除修饰词 → 参数词 → 可搜索品牌简称 → 规格
 *   - 名称轮：只剩商品名称
 *   - 搜索计划首版只生成 progressive 轮次
 *   - AI 替代词不在静态表里预生成，改为运行时按需要再触发
 * ============================================================
 */

import type {
  ParsedProduct,
  KeywordInputRow,
  SearchPlanRow,
  SearchFieldType,
  SearchPipelineConfig,
  StandardizedResultRow,
} from "../core/types.ts";
import { SEARCH_FIELD_LABELS, SEARCH_FIELD_ORDER } from "../core/types.ts";

const MAX_PRIMARY_KEYWORD_LENGTH = 30;
const MAX_PARAMETER_TERM_LENGTH = 12;
const MAX_DETAIL_TERM_LENGTH = 12;
const DETAIL_OR_SERVICE_PATTERN = /含|包含|安装|装卸|运费|运输|施工|检修门|门锁|五金|预埋件|装饰盖|附件|配件|详见|满足|要求|标准/;
const MATERIAL_LIKE_PATTERN = /不锈钢|玻璃|塑料|铝|铜|钢|木|橡胶|夹胶|镀锌|聚乙烯/i;
const SPEC_LIKE_PATTERN = /\d|mm|cm|m|米|Φ|φ|\*|x/i;
const COMPONENT_LIKE_PATTERN = /立柱|玻璃|栏杆|护栏|围栏|波纹管|吊架|挂板|保护盖|卷材|管卡/;
const PLACEHOLDER_OR_NOISE_PATTERN = /^(?:待确认|未知|通用|暂无|无|空|未填写|未提供|其他|其它|n\/a|na|none|null|--|-)$/i;
const IMPORT_NOISE_PATTERN = /Excel|字段导入|人工补充|如有缺漏|请人工|待补充|示例/i;
const PHRASE_SPLIT_PATTERN = /[:：]/;
const PHRASE_STOP_PATTERN = /（|\(|。|；|;|，|,/;
const MATH_UNIT_HTML_ENTITY_PATTERN = /\/?m&#\d+;/g;
const MATH_UNIT_SUPERSCRIPT_PATTERN = /\/?m[⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻ⁿ₀₁₂₃₄₅₆₇₈₉]/g;

function normalizeTerm(value: string) {
  return value
    .replace(MATH_UNIT_HTML_ENTITY_PATTERN, "")
    .replace(MATH_UNIT_SUPERSCRIPT_PATTERN, "")
    .replace(/\s+/g, " ")
    .trim();
}

function compactTerm(value: string) {
  return normalizeTerm(value).replace(/\s+/g, "");
}

function isPlaceholderLikeTerm(value: string) {
  const normalized = normalizeTerm(value);
  if (!normalized) return true;
  return PLACEHOLDER_OR_NOISE_PATTERN.test(normalized) || IMPORT_NOISE_PATTERN.test(normalized);
}

function keywordLength(value: string) {
  return compactTerm(value).length;
}

function uniqueTerms(values: string[]) {
  const result: string[] = [];
  const seen = new Set<string>();

  for (const value of values) {
    const normalized = normalizeTerm(value);
    if (!normalized) continue;
    if (seen.has(normalized)) continue;
    seen.add(normalized);
    result.push(normalized);
  }

  return result;
}

function collapseCoveredTerms(values: string[]) {
  const normalizedValues = uniqueTerms(values);
  const sorted = normalizedValues
    .map((value, index) => ({ value, index, compact: compactTerm(value) }))
    .sort((left, right) => {
      if (right.compact.length !== left.compact.length) {
        return right.compact.length - left.compact.length;
      }

      return left.index - right.index;
    });

  const kept: Array<{ value: string; index: number; compact: string }> = [];

  for (const candidate of sorted) {
    const coveredByExisting = kept.some((item) => item.compact.includes(candidate.compact));
    if (coveredByExisting) {
      continue;
    }

    kept.push(candidate);
  }

  return kept.sort((left, right) => left.index - right.index).map((item) => item.value);
}

function removeOne(list: string[], target: string) {
  const index = list.indexOf(target);
  if (index === -1) return list.slice();
  return [...list.slice(0, index), ...list.slice(index + 1)];
}

function splitMaterialTerms(value: string) {
  return uniqueTerms(
    value
      .split(/[，,、/]/)
      .map((item) => item.trim())
      .filter(Boolean),
  );
}

function stripLeadingSpecPrefix(term: string) {
  return normalizeTerm(term)
    .replace(/^[(（]?[Φφ]?\d+(?:\.\d+)?(?:\s*[xX×*]\s*\d+(?:\.\d+)?)*(?:\s*(?:mm|cm|m|米))?(?:厚|高|宽|长|直径)?/i, "")
    .trim();
}

function normalizeParameterPhrase(term: string) {
  const normalized = normalizeTerm(term);
  if (!normalized) return "";
  if (isPlaceholderLikeTerm(normalized)) return "";

  const stripped = stripLeadingSpecPrefix(normalized);
  if (stripped && stripped !== normalized) {
    if (isPlaceholderLikeTerm(stripped)) return "";
    return stripped;
  }

  return normalized;
}

function isSecondarySpecOnlyTerm(term: string) {
  if (!term) return false;
  if (!SPEC_LIKE_PATTERN.test(term)) return false;
  if (MATERIAL_LIKE_PATTERN.test(term)) return false;
  if (COMPONENT_LIKE_PATTERN.test(term)) return false;

  return true;
}

function extractOriginalParameterPhrases(value: string) {
  const phrases: string[] = [];
  const lines = value
    .split(/\n+/)
    .map((line) => line.replace(/^\s*\d+[、.)）]?\s*/, "").trim())
    .filter(Boolean);

  for (const line of lines) {
    const [beforeColonRaw, afterColonRaw = ""] = line.split(PHRASE_SPLIT_PATTERN, 2);
    const beforeColon = normalizeTerm(beforeColonRaw || "");
    const afterColon = normalizeTerm(afterColonRaw || "")
      .split(PHRASE_STOP_PATTERN)[0]
      ?.split(/含/)[0]
      ?.trim() || "";

    const normalizedBeforeColon = normalizeParameterPhrase(beforeColon);
    if (
      normalizedBeforeColon
      && keywordLength(normalizedBeforeColon) <= MAX_PARAMETER_TERM_LENGTH
      && !DETAIL_OR_SERVICE_PATTERN.test(normalizedBeforeColon)
      && !isSecondarySpecOnlyTerm(normalizedBeforeColon)
    ) {
      phrases.push(normalizedBeforeColon);
    }

    const normalizedAfterColon = normalizeParameterPhrase(afterColon);
    if (
      normalizedAfterColon
      && keywordLength(normalizedAfterColon) <= MAX_PARAMETER_TERM_LENGTH
      && !DETAIL_OR_SERVICE_PATTERN.test(normalizedAfterColon)
      && !isSecondarySpecOnlyTerm(normalizedAfterColon)
    ) {
      phrases.push(normalizedAfterColon);
    }
  }

  return uniqueTerms(phrases);
}

function isSearchableBrandTerm(term: string) {
  if (!term) return false;
  if (isPlaceholderLikeTerm(term)) return false;
  if (keywordLength(term) > 8) return false;
  if (/[，,、；;（）()\n]/.test(term)) return false;
  if (MATERIAL_LIKE_PATTERN.test(term)) return false;
  if (SPEC_LIKE_PATTERN.test(term)) return false;

  return true;
}

function isSearchableDetailTerm(term: string) {
  if (!term) return false;
  if (isPlaceholderLikeTerm(term)) return false;
  if (keywordLength(term) > MAX_DETAIL_TERM_LENGTH) return false;
  if (DETAIL_OR_SERVICE_PATTERN.test(term)) return false;
  if (isSecondarySpecOnlyTerm(term)) return false;

  return true;
}

function isSearchableParameterTerm(term: string) {
  if (!term) return false;
  if (isPlaceholderLikeTerm(term)) return false;
  if (keywordLength(term) > MAX_PARAMETER_TERM_LENGTH) return false;
  if (DETAIL_OR_SERVICE_PATTERN.test(term)) return false;
  if (isSecondarySpecOnlyTerm(term)) return false;

  return true;
}

function scoreParameterTerm(term: string) {
  let score = 0;

  if (MATERIAL_LIKE_PATTERN.test(term)) score += 4;
  if (SPEC_LIKE_PATTERN.test(term)) score += 2;
  if (COMPONENT_LIKE_PATTERN.test(term)) score += 4;
  if (/检修门|门锁|五金/.test(term)) score -= 6;
  if (keywordLength(term) >= 6) score += 1;

  return score;
}

function sortParameterTermsByPriority(terms: string[]) {
  return terms
    .map((term, index) => ({ term, index, score: scoreParameterTerm(term) }))
    .sort((left, right) => {
      if (right.score !== left.score) {
        return right.score - left.score;
      }

      return left.index - right.index;
    })
    .map((item) => item.term);
}

function isSpecExtensionTerm(term: string) {
  if (!term) return false;
  if (!SPEC_LIKE_PATTERN.test(term)) return false;
  if (DETAIL_OR_SERVICE_PATTERN.test(term)) return false;

  return true;
}

function fitTermsWithinLimit(terms: string[]) {
  const fitted: string[] = [];

  for (const term of collapseCoveredTerms(terms)) {
    const candidate = [...fitted, term].join(" ");
    if (keywordLength(candidate) > MAX_PRIMARY_KEYWORD_LENGTH) {
      continue;
    }

    fitted.push(term);
  }

  return fitted;
}

function fitFixedTermsWithinLimit(terms: string[]) {
  const fitted: string[] = [];

  for (const term of uniqueTerms(terms)) {
    const candidate = [...fitted, term].join(" ");
    if (keywordLength(candidate) > MAX_PRIMARY_KEYWORD_LENGTH) {
      continue;
    }

    fitted.push(term);
  }

  return fitted;
}

function buildSearchableParameterTerms(product: ParsedProduct) {
  const { raw, analysis } = product;
  const nameTerm = normalizeTerm(raw.名称);
  const specTerm = normalizeTerm(analysis.simplified_spec);
  const brandTerm = normalizeTerm(analysis.brand_term);
  const materialTerms = splitMaterialTerms(analysis.material);
  const originalPhrases = extractOriginalParameterPhrases(`${raw.品牌}\n${raw["备注/附件"]}`);

  return sortParameterTermsByPriority(
    collapseCoveredTerms([
      ...originalPhrases,
      ...uniqueTerms(analysis.keyword_parameter_terms).map((term) => normalizeParameterPhrase(term)),
      ...materialTerms,
    ]).filter((term) => {
      if ([nameTerm, specTerm, brandTerm].includes(term)) return false;
      return isSearchableParameterTerm(term);
    }),
  );
}

function buildSearchableModifierTerms(product: ParsedProduct, parameterTerms: string[]) {
  const { raw, analysis } = product;
  const nameTerm = normalizeTerm(raw.名称);
  const specTerm = normalizeTerm(analysis.simplified_spec);
  const brandTerm = normalizeTerm(analysis.brand_term);

  return collapseCoveredTerms(uniqueTerms(analysis.modifier_terms)).filter((term) => {
    if ([nameTerm, specTerm, brandTerm].includes(term)) return false;
    if (parameterTerms.includes(term)) return false;
    return isSearchableDetailTerm(term);
  });
}

function buildCompositeSpecTerm(specTerm: string, parameterTerms: string[]) {
  const specExtensions = parameterTerms.filter((term) => isSpecExtensionTerm(term));
  if (!specTerm) {
    return {
      compositeSpecTerm: "",
      remainingParameterTerms: parameterTerms,
    };
  }

  if (specExtensions.length === 0) {
    return {
      compositeSpecTerm: specTerm,
      remainingParameterTerms: parameterTerms,
    };
  }

  const compositeSpecTerm = collapseCoveredTerms([specTerm, ...specExtensions]).join(" ");
  const remainingParameterTerms = parameterTerms.filter((term) => !specExtensions.includes(term));

  return {
    compositeSpecTerm,
    remainingParameterTerms,
  };
}

type SearchRoundSeed = {
  primary_keyword: string;
  search_strategy: string;
  keyword_source: string;
};

type KeywordTermContext = {
  nameTerm: string;
  searchableBrandTerm: string;
  specTerm: string;
  rawParameterTerms: string[];
  parameterTerms: string[];
  rawModifierTerms: string[];
  modifierTerms: string[];
};

type ConfiguredSearchSourceInput = Pick<
  StandardizedResultRow,
  | "primary_search_term"
  | "alt_search_terms"
  | "original_name"
  | "standardized_name"
  | "original_spec"
  | "standardized_spec"
  | "brand"
  | "category"
  | "quantity"
  | "notes"
>;

type ConfiguredSearchSourceRow = Omit<ConfiguredSearchSourceInput, "quantity"> & {
  quantity: string;
};

type ConfiguredSearchFieldContext = Record<SearchFieldType, string[]>;

const CONFIGURED_FIELD_ORDER: SearchFieldType[] = [...SEARCH_FIELD_ORDER];

export type FixedSearchRoundStatus = {
  round: number;
  label: string;
  query: string;
  status: "success" | "failed" | "duplicate_skip";
  failure_reason: string;
  search_strategy: string;
  keyword_source: string;
};

const FIXED_RUNTIME_ROUNDS: Array<{
  label: string;
  fields: SearchFieldType[];
}> = [
  { label: "主搜索词", fields: ["primary_search_term"] },
  { label: "标准化名称", fields: ["standardized_name"] },
  { label: "标准化名称+品牌", fields: ["standardized_name", "brand"] },
  { label: "品牌+标准化名称", fields: ["brand", "standardized_name"] },
  { label: "标准化名称+标准化规格", fields: ["standardized_name", "standardized_spec"] },
  { label: "标准化规格+标准化名称", fields: ["standardized_spec", "standardized_name"] },
  { label: "标准化名称+分类(材质/用途)", fields: ["standardized_name", "category"] },
];

function buildKeywordTermContext(product: ParsedProduct): KeywordTermContext {
  const { raw, analysis } = product;
  const nameTerm = normalizeTerm(raw.名称);
  const baseSpecTerm = normalizeTerm(analysis.simplified_spec);
  const brandTerm = normalizeTerm(analysis.brand_term);
  const searchableBrandTerm = isSearchableBrandTerm(brandTerm) ? brandTerm : "";
  const rawParameterTerms = buildSearchableParameterTerms(product);
  const {
    compositeSpecTerm: specTerm,
    remainingParameterTerms: parameterTerms,
  } = buildCompositeSpecTerm(baseSpecTerm, rawParameterTerms);

  return {
    nameTerm,
    searchableBrandTerm,
    specTerm,
    rawParameterTerms,
    parameterTerms,
    rawModifierTerms: buildSearchableModifierTerms(product, rawParameterTerms),
    modifierTerms: buildSearchableModifierTerms(product, parameterTerms),
  };
}

function toConfiguredSearchTerm(value: unknown) {
  const normalized = normalizeTerm(String(value ?? ""));
  if (!normalized || isPlaceholderLikeTerm(normalized)) {
    return "";
  }

  return normalized;
}

function normalizeConfiguredAltTerms(values: unknown, primarySearchTerm: string) {
  return uniqueTerms(
    (Array.isArray(values) ? values : [])
      .map((value) => toConfiguredSearchTerm(value))
      .filter((term) => term && term !== primarySearchTerm),
  );
}

function buildConfiguredSearchSource(
  product: ParsedProduct,
  aiKeyword?: string,
  standardizedResult?: ConfiguredSearchSourceInput,
): ConfiguredSearchSourceRow {
  const primarySearchTerm = toConfiguredSearchTerm(standardizedResult?.primary_search_term ?? aiKeyword ?? "");

  return {
    primary_search_term: primarySearchTerm,
    alt_search_terms: normalizeConfiguredAltTerms(
      standardizedResult?.alt_search_terms ?? product.analysis.alternative_terms,
      primarySearchTerm,
    ),
    original_name: toConfiguredSearchTerm(standardizedResult?.original_name ?? product.raw.名称),
    standardized_name: toConfiguredSearchTerm(standardizedResult?.standardized_name ?? product.raw.名称),
    original_spec: toConfiguredSearchTerm(standardizedResult?.original_spec ?? product.raw.规格型号),
    standardized_spec: toConfiguredSearchTerm(
      standardizedResult?.standardized_spec ?? product.analysis.simplified_spec ?? product.raw.规格型号,
    ),
    brand: toConfiguredSearchTerm(standardizedResult?.brand ?? product.analysis.brand_term ?? product.raw.品牌),
    category: toConfiguredSearchTerm(standardizedResult?.category ?? product.analysis.material),
    quantity: toConfiguredSearchTerm(standardizedResult?.quantity ?? product.raw.数量),
    notes: toConfiguredSearchTerm(standardizedResult?.notes ?? product.raw["备注/附件"]),
  };
}

function buildConfiguredSearchFieldContext(source: ConfiguredSearchSourceRow): ConfiguredSearchFieldContext {
  return {
    primary_search_term: source.primary_search_term ? [source.primary_search_term] : [],
    alt_search_terms: [...source.alt_search_terms],
    original_name: source.original_name ? [source.original_name] : [],
    standardized_name: source.standardized_name ? [source.standardized_name] : [],
    original_spec: source.original_spec ? [source.original_spec] : [],
    standardized_spec: source.standardized_spec ? [source.standardized_spec] : [],
    brand: source.brand ? [source.brand] : [],
    category: source.category ? [source.category] : [],
    quantity: source.quantity ? [source.quantity] : [],
    notes: source.notes ? [source.notes] : [],
  };
}

function buildSearchPlanRowsFromSeeds(
  materialId: string,
  batchId: string,
  roundSeeds: Array<SearchRoundSeed & { search_round: number }>,
): Omit<SearchPlanRow, "id" | "created_at">[] {
  const keywords = roundSeeds.map((seed) => seed.primary_keyword);

  return roundSeeds.map((seed) => ({
    batch_id: batchId,
    material_id: materialId,
    primary_keyword: seed.primary_keyword,
    alt_keywords: keywords.filter((keyword) => keyword !== seed.primary_keyword).join(", "),
    search_round: seed.search_round,
    search_strategy: seed.search_strategy,
    keyword_source: seed.keyword_source,
    confirmed: null,
    best_keyword: "",
    estimated_price_range: "",
    category: "",
  }));
}

function buildFixedRoundFailureReason(fields: SearchFieldType[]) {
  const labels = fields.map((field) => SEARCH_FIELD_LABELS[field]).join("、");
  return `${labels}为空，组合失败`;
}

function buildFixedRoundQuery(fields: SearchFieldType[], source: ConfiguredSearchSourceRow) {
  const terms = uniqueTerms(fields.map((field) => source[field]).filter(Boolean));
  return fitFixedTermsWithinLimit(terms).join(" ");
}

export function buildFixedSearchPlanRows(
  product: ParsedProduct,
  aiKeyword?: string,
  batchId: string = '',
  standardizedResult?: ConfiguredSearchSourceInput,
): {
  searchPlanRows: Omit<SearchPlanRow, "id" | "created_at">[];
  roundStatuses: FixedSearchRoundStatus[];
} {
  const { material_id } = product;
  const source = buildConfiguredSearchSource(product, aiKeyword, standardizedResult);
  const successfulQueries = new Map<string, number>();
  const roundStatuses = FIXED_RUNTIME_ROUNDS.map((definition, index) => {
    const round = index + 1;
    const missingFields = definition.fields.filter((field) => !source[field]);
    const query = buildFixedRoundQuery(definition.fields, source);
    const searchStrategy = `fixed_round_${round}`;
    const keywordSource = "fixed_runtime";

    if (missingFields.length > 0 || !query) {
      return {
        round,
        label: definition.label,
        query: "",
        status: "failed",
        failure_reason: buildFixedRoundFailureReason(missingFields.length > 0 ? missingFields : definition.fields),
        search_strategy: searchStrategy,
        keyword_source: keywordSource,
      } satisfies FixedSearchRoundStatus;
    }

    const duplicateRound = successfulQueries.get(query);
    if (duplicateRound != null) {
      return {
        round,
        label: definition.label,
        query,
        status: "duplicate_skip",
        failure_reason: `与第 ${duplicateRound} 轮搜索词重复，跳过重复搜索`,
        search_strategy: searchStrategy,
        keyword_source: keywordSource,
      } satisfies FixedSearchRoundStatus;
    }

    successfulQueries.set(query, round);
    return {
      round,
      label: definition.label,
      query,
      status: "success",
      failure_reason: "",
      search_strategy: searchStrategy,
      keyword_source: keywordSource,
    } satisfies FixedSearchRoundStatus;
  });

  const searchPlanRows = buildSearchPlanRowsFromSeeds(
    material_id,
    batchId,
    roundStatuses
      .filter((status) => status.status === "success")
      .map((status) => ({
        primary_keyword: status.query,
        search_round: status.round,
        search_strategy: status.search_strategy,
        keyword_source: status.keyword_source,
      })),
  );

  return { searchPlanRows, roundStatuses };
}

function pushRoundSeed(
  roundSeeds: SearchRoundSeed[],
  terms: string[],
  searchStrategy: string,
  keywordSource: string,
) {
  const keyword = fitTermsWithinLimit(terms).join(" ");
  if (!keyword) return;
  if (roundSeeds.some((item) => item.primary_keyword === keyword)) return;

  roundSeeds.push({
    primary_keyword: keyword,
    search_strategy: searchStrategy,
    keyword_source: keywordSource,
  });
}

function toSearchPlanRows(
  materialId: string,
  batchId: string,
  roundSeeds: SearchRoundSeed[],
): Omit<SearchPlanRow, "id" | "created_at">[] {
  return roundSeeds.map((seed, index) => ({
    batch_id: batchId,
    material_id: materialId,
    primary_keyword: seed.primary_keyword,
    alt_keywords: roundSeeds
      .filter((item) => item.primary_keyword !== seed.primary_keyword)
      .map((item) => item.primary_keyword)
      .join(", "),
    search_round: index + 1,
    search_strategy: seed.search_strategy,
    keyword_source: seed.keyword_source,
    confirmed: null,
    best_keyword: "",
    estimated_price_range: "",
    category: "",
  }));
}

function buildConfiguredTerms(comboFields: SearchFieldType[], fieldContext: ConfiguredSearchFieldContext) {
  const fields = new Set(comboFields);
  const altTerms = fields.has("alt_search_terms") ? fieldContext.alt_search_terms : [""];

  return altTerms
    .map((altTerm) => {
      const terms: string[] = [];

      for (const field of CONFIGURED_FIELD_ORDER) {
        if (!fields.has(field)) {
          continue;
        }

        if (field === "alt_search_terms") {
          if (altTerm) {
            terms.push(altTerm);
          }
          continue;
        }

        terms.push(...fieldContext[field]);
      }

      return uniqueTerms(terms.filter(Boolean));
    })
    .filter((terms) => terms.length > 0);
}

/**
 * 从 ParsedProduct 构建一条关键词输入行
 * @param product - 包含原始 CSV 数据和 LLM 分析结果的中间产品
 * @returns 关键词阶段使用的输入行（不含 id/created_at）
 */
export function buildKeywordInputRow(
  product: ParsedProduct,
  batchId: string = '',
): Omit<KeywordInputRow, "id" | "created_at"> {
  const { raw, analysis, material_id } = product;
  return {
    batch_id: batchId,
    material_id,
    invoice_type: raw.发票种类,
    name: raw.名称,
    spec: raw.规格型号,
    brand: raw.品牌,
    quantity: raw.数量,
    unit: raw.单位,
    notes: raw["备注/附件"],
    delivery_address: raw.收货地址,
    // LLM 提取的字段
    material: analysis.material,
    usage: analysis.usage,
    simplified_spec: analysis.simplified_spec,
    standardized_brand: analysis.brand_term || raw.品牌,
  };
}

/**
 * 从 ParsedProduct 构建动态搜索计划行
 * @param product  - 中间产品
 * @param aiKeyword - AI 语义分析推荐的搜索词（若有，插入第 1 轮）
 * @returns 多条搜索计划行的数组（每轮一条，按执行顺序排列）
 */
export function buildSearchPlanRows(
  product: ParsedProduct,
  aiKeyword?: string,
  batchId: string = '',
): Omit<SearchPlanRow, "id" | "created_at">[] {
  const { material_id } = product;
  const {
    nameTerm,
    searchableBrandTerm,
    specTerm,
    parameterTerms,
    modifierTerms,
  } = buildKeywordTermContext(product);
  const roundSeeds: SearchRoundSeed[] = [];

  // 第0轮（可选）：AI 语义关键词，由 analyzeProductForKeyword 生成
  if (aiKeyword && aiKeyword.trim()) {
    roundSeeds.push({
      primary_keyword: aiKeyword.trim(),
      search_strategy: "ai_semantic",
      keyword_source: "ai_semantic",
    });
  }

  // 第1轮：名称 + 核心参数词 + 规格 + 修饰词。
  // 参数词优先保留原表里的完整短语，例如“304不锈钢立柱”“12mm厚钢化夹胶玻璃”。
  let currentTerms = uniqueTerms([nameTerm, ...parameterTerms, specTerm, ...modifierTerms, searchableBrandTerm]);
  pushRoundSeed(roundSeeds, currentTerms, "initial_full", "progressive");

  // 先整组删除规格，保留名称和核心材质/部件词。
  if (specTerm) {
    currentTerms = removeOne(currentTerms, specTerm);
    pushRoundSeed(roundSeeds, currentTerms, "remove_spec", "progressive");
  }

  // 再逐轮删除修饰词
  for (let index = modifierTerms.length - 1; index >= 0; index -= 1) {
    currentTerms = removeOne(currentTerms, modifierTerms[index]!);
    pushRoundSeed(roundSeeds, currentTerms, "remove_modifier", "progressive");
  }

  // 最后逐轮删除参数词
  for (let index = parameterTerms.length - 1; index >= 0; index -= 1) {
    currentTerms = removeOne(currentTerms, parameterTerms[index]!);
    pushRoundSeed(roundSeeds, currentTerms, "remove_parameter", "progressive");
  }

  // 删除品牌
  if (searchableBrandTerm) {
    currentTerms = removeOne(currentTerms, searchableBrandTerm);
    pushRoundSeed(roundSeeds, currentTerms, "remove_brand", "progressive");
  }

  // 最后一轮：只保留商品名称
  pushRoundSeed(roundSeeds, [nameTerm], "name_only", "progressive");

  return buildSearchPlanRowsFromSeeds(
    material_id,
    batchId,
    roundSeeds.map((seed, index) => ({ ...seed, search_round: index + 1 })),
  );
}

export function buildSearchPlanRowsFromConfig(
  product: ParsedProduct,
  pipelineConfig: SearchPipelineConfig,
  aiKeyword?: string,
  batchId: string = '',
  standardizedResult?: ConfiguredSearchSourceInput,
): Omit<SearchPlanRow, "id" | "created_at">[] {
  if (!pipelineConfig.keywordSearchEnabled) {
    return [];
  }

  if (pipelineConfig.keywordCombinations.length === 0) {
    return buildSearchPlanRows(
      product,
      pipelineConfig.includeAiKeyword ? aiKeyword : undefined,
      batchId,
    );
  }

  const { material_id } = product;
  const fieldContext = buildConfiguredSearchFieldContext(
    buildConfiguredSearchSource(product, aiKeyword, standardizedResult),
  );
  const roundSeeds: SearchRoundSeed[] = [];

  if (pipelineConfig.includeAiKeyword && aiKeyword?.trim()) {
    roundSeeds.push({
      primary_keyword: aiKeyword.trim(),
      search_strategy: "ai_semantic",
      keyword_source: "ai_semantic",
    });
  }

  pipelineConfig.keywordCombinations.forEach((combo, index) => {
    const fields = Array.isArray(combo.fields)
      ? combo.fields.filter((field): field is SearchFieldType => CONFIGURED_FIELD_ORDER.includes(field as SearchFieldType))
      : [];

    if (fields.length === 0) {
      return;
    }

    for (const terms of buildConfiguredTerms(fields, fieldContext)) {
      pushRoundSeed(
        roundSeeds,
        terms,
        `configured_round_${index + 1}`,
        "progressive",
      );
    }
  });

  return toSearchPlanRows(material_id, batchId, roundSeeds);
}

/**
 * 批量处理所有 ParsedProduct，返回两类预览行数据
 * @param products - ParsedProduct 数组
 * @returns { keywordInputRows, searchPlanRows } — 分别对应关键词输入和搜索计划预览
 */
export function buildAllRows(products: ParsedProduct[], batchId: string = ''): {
  keywordInputRows: Omit<KeywordInputRow, "id" | "created_at">[];
  searchPlanRows: Omit<SearchPlanRow, "id" | "created_at">[];
} {
  const keywordInputRows: Omit<KeywordInputRow, "id" | "created_at">[] = [];
  const searchPlanRows: Omit<SearchPlanRow, "id" | "created_at">[] = [];

  for (const product of products) {
    // 构建该商品的关键词输入行
    keywordInputRows.push(buildKeywordInputRow(product, batchId));
    // 构建该商品的动态搜索计划行
    searchPlanRows.push(...buildSearchPlanRows(product, undefined, batchId));
  }

  return { keywordInputRows, searchPlanRows };
}
