(function (root, factory) {
  const api = factory();
  if (typeof module === "object" && module.exports) module.exports = api;
  if (root) root.JIANGLAI_QUOTE_COST_DEFAULTS = api;
})(typeof window !== "undefined" ? window : globalThis, function () {
  "use strict";

  function text(value) {
    return String(value == null ? "" : value).trim().toLowerCase().replace(/[\s·•（）()_\-/]+/g, "");
  }

  function positive(value) {
    const number = Number(value);
    return Number.isFinite(number) && number > 0 ? number : 0;
  }

  function hourlyItems(items, type) {
    return (Array.isArray(items) ? items : []).filter(item => (
      item
      && item.active !== false
      && !item.incomplete
      && item.type === type
      && positive(item.price) > 0
      && /元\s*\/\s*(?:小时|时|h)/i.test(String(item.unit || ""))
    ));
  }

  function namesOverlap(left, right) {
    const a = text(left);
    const b = text(right);
    return Boolean(a && b && (a === b || a.includes(b) || b.includes(a)));
  }

  function median(values) {
    const sorted = values.map(positive).filter(Boolean).sort((a, b) => a - b);
    if (!sorted.length) return 0;
    const middle = Math.floor(sorted.length / 2);
    return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
  }

  function resolved(item, source, reason) {
    return Object.freeze({
      rate: positive(item?.price),
      source,
      reason,
      item: item || null,
      label: item?.name || (source === "factory_setting" ? "本厂报价参数" : "未设置")
    });
  }

  function resolveHourlyRate(options = {}) {
    const explicitRate = positive(options.explicitRate);
    if (explicitRate) {
      return resolved({ name: options.explicitLabel || "本厂报价参数", price: explicitRate }, "factory_setting", "客户或厂家已保存的报价参数优先");
    }
    const candidates = hourlyItems(options.items, options.type);
    for (const preferredName of options.preferredNames || []) {
      const match = candidates.find(item => namesOverlap(item.name, preferredName));
      if (match) return resolved(match, "cost_library", `成本库匹配：${match.name}`);
    }
    if (options.allowMedian && candidates.length) {
      const rate = median(candidates.map(item => item.price));
      return resolved(
        { name: `${options.type === "labor" ? "人工" : "设备"}成本库中位值`, price: rate },
        "cost_library_median",
        `没有指定岗位或设备，采用${candidates.length}条启用成本的中位值`
      );
    }
    return resolved(null, "missing", "成本库没有匹配的有效小时成本");
  }

  function offsetMachineNames(sheetWidthMm, sheetHeightMm) {
    const longest = Math.max(positive(sheetWidthMm), positive(sheetHeightMm));
    return longest > 700
      ? ["对开胶印机", "四开胶印机"]
      : ["四开胶印机", "对开胶印机"];
  }

  function resolvePrintingRates(options = {}) {
    if (options.allInPrintPrice) {
      const included = Object.freeze({
        rate: 0,
        source: "included_in_print_price",
        reason: "所选本厂印工价已包含该生产环节的人工和设备，不重复计费",
        item: null,
        label: "已含在印工价"
      });
      return Object.freeze({ labor: included, machine: included, allInPrintPrice: true });
    }
    const route = String(options.route || "offset");
    const labor = resolveHourlyRate({
      explicitRate: options.explicitLaborRate,
      explicitLabel: "本厂完全人工成本",
      items: options.items,
      type: "labor",
      preferredNames: route === "digital"
        ? ["数码印刷操作员", "印刷机长"]
        : ["印刷机长", "印刷副手"],
      allowMedian: true
    });
    const machine = resolveHourlyRate({
      explicitRate: options.explicitMachineRate,
      explicitLabel: "本厂设备机时费",
      items: options.items,
      type: "equipment",
      preferredNames: route === "digital"
        ? ["数码印刷机"]
        : offsetMachineNames(options.sheetWidthMm, options.sheetHeightMm),
      allowMedian: false
    });
    return Object.freeze({ labor, machine, allInPrintPrice: false });
  }

  const PUBLIC_PRINT_REFERENCES = Object.freeze({
    gripperMm: 10,
    sideMarginMm: 5,
    bleedMm: 3,
    gapMm: 6,
    pressSheetsPerHour: 15000,
    setupWasteSheets: 100,
    printWasteRate: 5,
    finishingWasteRate: 2,
    sources: Object.freeze([
      Object.freeze({ label: "Heidelberg Speedmaster XL 75 技术资料", url: "https://www.heidelberg.com/global/media/en/global_media/products___sheetfed_offset/archive_pictures_products___sheetfed_offset/2016_42/50x70/pdf_37/TDB_XL75.pdf", facts: "叼口 8–10mm、最大印刷幅面 510×740mm、标准速度 15,000张/小时" }),
      Object.freeze({ label: "KOMORI LITHRONE G29 官方规格", url: "https://www.komori.com/en/global/product/press/offset/lith-g/g29/", facts: "最大纸张 530×750mm、最高速度 16,500张/小时" }),
      Object.freeze({ label: "Adobe InDesign 印刷出血说明", url: "https://helpx.adobe.com/indesign/desktop/print/page-set-up-and-printer-marks/print-bleed-and-slug-areas.html", facts: "常用印刷出血 3mm，仍需按承印厂要求核对" }),
      Object.freeze({ label: "公开印刷排版损耗参考", url: "https://www.printimposition.com/tips/how-much-paper-waste-is-normal", facts: "小型胶印常见损耗 5%–8%、起机纸 50–100张、裁切损耗 1%–3%；仅作校准起点" })
    ])
  });

  const PRESS_SHEET_CANDIDATES = Object.freeze([
    Object.freeze({ format: "eight_open", standard: "regular", widthMm: 273, heightMm: 393 }),
    Object.freeze({ format: "eight_open", standard: "large", widthMm: 298, heightMm: 444 }),
    Object.freeze({ format: "four_open", standard: "regular", widthMm: 393, heightMm: 546 }),
    Object.freeze({ format: "four_open", standard: "large", widthMm: 444, heightMm: 597 }),
    Object.freeze({ format: "two_open", standard: "regular", widthMm: 546, heightMm: 787 }),
    Object.freeze({ format: "two_open", standard: "large", widthMm: 597, heightMm: 889 }),
    Object.freeze({ format: "full_open", standard: "regular", widthMm: 787, heightMm: 1092 }),
    Object.freeze({ format: "full_open", standard: "large", widthMm: 889, heightMm: 1194 })
  ]);

  function dimensions(value) {
    const values = String(value || "").match(/\d+(?:\.\d+)?/g)?.map(Number) || [];
    return values.length >= 2 && values[0] > 0 && values[1] > 0 ? values.slice(0, 2) : [];
  }

  function layoutCount(pieceWidthMm, pieceHeightMm, sheet, options = {}) {
    const side = positive(options.sideMarginMm ?? PUBLIC_PRINT_REFERENCES.sideMarginMm);
    const gripper = positive(options.gripperMm ?? PUBLIC_PRINT_REFERENCES.gripperMm);
    const gap = positive(options.gapMm ?? PUBLIC_PRINT_REFERENCES.gapMm);
    const usableWidth = sheet.widthMm - side * 2;
    const usableHeight = sheet.heightMm - gripper - side;
    const count = (width, height) => Math.max(0, Math.floor((usableWidth + gap) / (width + gap)))
      * Math.max(0, Math.floor((usableHeight + gap) / (height + gap)));
    return Math.max(count(pieceWidthMm, pieceHeightMm), options.allowRotate === false ? 0 : count(pieceHeightMm, pieceWidthMm));
  }

  function tierCharge(tierPricing, candidate, sheetQuantity, sideCount) {
    const rule = tierPricing?.[candidate.format];
    if (!rule || typeof rule !== "object") return null;
    const quantity = Math.max(0, sheetQuantity * sideCount);
    const included = positive(rule.includedQuantity);
    const minimum = positive(rule.minimumCharge);
    const unit = positive(rule.excessUnitPrice ?? rule.unitPrice);
    if (included && minimum && unit) return minimum + Math.max(0, quantity - included) * unit;
    if (unit) return Math.max(minimum, quantity * unit);
    return null;
  }

  function recommendPressSheet(options = {}) {
    const [pieceWidthMm, pieceHeightMm] = dimensions(options.flatSize);
    if (!(pieceWidthMm > 0 && pieceHeightMm > 0)) return null;
    const quantity = Math.max(1, Math.ceil(positive(options.quantity) || 1));
    const sideCount = Math.max(1, Math.ceil(positive(options.sideCount) || 1));
    const setupWaste = Math.max(0, Math.ceil(Number(options.setupWasteSheets ?? PUBLIC_PRINT_REFERENCES.setupWasteSheets) || 0));
    const printWaste = Math.max(0, Number(options.printWasteRate ?? PUBLIC_PRINT_REFERENCES.printWasteRate) || 0) / 100;
    const finishingWaste = Math.max(0, Number(options.finishingWasteRate ?? PUBLIC_PRINT_REFERENCES.finishingWasteRate) || 0) / 100;
    const tierPricing = options.tierPricing && typeof options.tierPricing === "object" ? options.tierPricing : null;
    const configuredFormats = tierPricing
      ? new Set(Object.keys(tierPricing).filter(key => tierPricing[key] && typeof tierPricing[key] === "object"))
      : null;
    const candidates = PRESS_SHEET_CANDIDATES.map(candidate => {
      if (configuredFormats?.size && !configuredFormats.has(candidate.format)) return null;
      const piecesPerSheet = layoutCount(pieceWidthMm, pieceHeightMm, candidate, options);
      if (!piecesPerSheet) return null;
      const netSheets = Math.ceil(quantity / piecesPerSheet);
      if (printWaste >= 1 || finishingWaste >= 1) return null;
      // Keep the recommendation and the final quote engine on the same loss
      // model: variable waste reduces the number of good sheets, while setup
      // sheets are a fixed addition. Applying a percentage to setup sheets made
      // the price preview disagree with the generated quote.
      const productionSheets = Math.ceil(netSheets / ((1 - printWaste) * (1 - finishingWaste))) + setupWaste;
      const printCost = tierPricing ? tierCharge(tierPricing, candidate, productionSheets, sideCount) : 0;
      if (tierPricing && printCost === null) return null;
      const paperWeightKg = candidate.widthMm / 1000 * candidate.heightMm / 1000
        * productionSheets * positive(options.gsm) / 1000;
      const paperCost = paperWeightKg * positive(options.materialPricePerKg);
      const utilizationScore = candidate.widthMm * candidate.heightMm * productionSheets;
      return Object.freeze({ ...candidate, piecesPerSheet, netSheets, productionSheets, printCost:Number(printCost || 0), paperCost, estimatedDirectCost:Number(printCost || 0) + paperCost, utilizationScore });
    }).filter(Boolean);
    if (!candidates.length) return null;
    candidates.sort((left, right) => (
      tierPricing && positive(options.materialPricePerKg)
        ? left.estimatedDirectCost - right.estimatedDirectCost
        : tierPricing
        ? left.printCost - right.printCost || left.utilizationScore - right.utilizationScore
        : left.utilizationScore - right.utilizationScore
    ));
    return candidates[0];
  }

  function recommendPrintingInputs(options = {}) {
    const reference = PUBLIC_PRINT_REFERENCES;
    const sheet = recommendPressSheet({
      ...options,
      gripperMm: reference.gripperMm,
      sideMarginMm: reference.sideMarginMm,
      gapMm: reference.gapMm,
      setupWasteSheets: reference.setupWasteSheets,
      printWasteRate: reference.printWasteRate,
      finishingWasteRate: reference.finishingWasteRate
    });
    const allInPrintPrice = Boolean(options.tierPricing);
    return Object.freeze({
      sheetSize: sheet ? `${sheet.widthMm}×${sheet.heightMm}` : "",
      printSheetFormat: sheet?.format || "auto",
      printSheetStandard: sheet?.standard || "auto",
      gripperMm: String(reference.gripperMm),
      sideMarginMm: String(reference.sideMarginMm),
      gapMm: String(reference.gapMm),
      setupWasteSheets: String(reference.setupWasteSheets),
      printWasteRate: String(reference.printWasteRate),
      finishingWasteRate: String(reference.finishingWasteRate),
      printSetupFee: allInPrintPrice ? "0" : "",
      plateFee: allInPrintPrice ? "0" : "",
      printRatePerImpression: allInPrintPrice ? "0" : "",
      printMinimumCharge: allInPrintPrice ? "0" : "",
      setupMinutes: allInPrintPrice ? "0" : "",
      pressSheetsPerHour: String(reference.pressSheetsPerHour),
      operatorCount: "1",
      allowRotate: "yes",
      sheet,
      allInPrintPrice,
      sources: reference.sources
    });
  }

  const INDUSTRY_LABOR_NAMES = Object.freeze({
    packaging_printing: Object.freeze(["后道操作员", "包装工", "印刷机长"]),
    acrylic: Object.freeze(["后道操作员", "包装工"]),
    metal: Object.freeze(["后道操作员", "质检员"]),
    textile_bag: Object.freeze(["包装工", "后道操作员"]),
    electronics_bom: Object.freeze(["质检员", "后道操作员"]),
    generic_weight: Object.freeze(["后道操作员", "包装工"])
  });

  function resolveIndustryRates(options = {}) {
    const strategyId = String(options.strategyId || "generic_weight");
    return Object.freeze({
      labor: resolveHourlyRate({
        explicitRate: options.explicitLaborRate,
        explicitLabel: "本厂完全人工成本",
        items: options.items,
        type: "labor",
        preferredNames: INDUSTRY_LABOR_NAMES[strategyId] || INDUSTRY_LABOR_NAMES.generic_weight,
        allowMedian: true
      }),
      machine: resolveHourlyRate({
        explicitRate: options.explicitMachineRate,
        explicitLabel: "本厂设备机时费",
        items: options.items,
        type: "equipment",
        preferredNames: options.preferredMachineNames || [],
        allowMedian: true
      })
    });
  }

  return Object.freeze({
    hourlyItems,
    median,
    publicPrintReferences: PUBLIC_PRINT_REFERENCES,
    pressSheetCandidates: PRESS_SHEET_CANDIDATES,
    recommendPressSheet,
    recommendPrintingInputs,
    resolveHourlyRate,
    resolvePrintingRates,
    resolveIndustryRates
  });
});
