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

  const ROUTES = Object.freeze({
    fixed: Object.freeze({ id: "fixed", label: "固定档位", description: "厂家预先确认标准规格与固定档位，超出范围转人工核价。" }),
    gang: Object.freeze({ id: "gang", label: "合版", description: "满足相同材料、色数、交期与质量约束的订单才允许合版。" }),
    outsourced: Object.freeze({ id: "outsourced", label: "外发成品", description: "按供应商同规格、同工艺的实际采购总价核算；不再重复叠加本厂纸张、印工、人工和设备费。" }),
    digital: Object.freeze({ id: "digital", label: "数码专版", description: "无印版费，按点击数或印张计费，通常用于小批量与急单。" }),
    offset: Object.freeze({ id: "offset", label: "胶印专版", description: "按上机纸、拼版、印版、印次、起机损耗和后道工艺分项核算。" })
  });

  const OPERATION_BASES = Object.freeze({
    piece: Object.freeze({ id: "piece", label: "件/个" }),
    sheet: Object.freeze({ id: "sheet", label: "张" }),
    area_m2: Object.freeze({ id: "area_m2", label: "平方米" }),
    meter: Object.freeze({ id: "meter", label: "米" }),
    kg: Object.freeze({ id: "kg", label: "千克" }),
    run: Object.freeze({ id: "run", label: "次" }),
    hour: Object.freeze({ id: "hour", label: "小时" }),
    // The following bases are retained for saved price tables created by older builds.
    local_m2: Object.freeze({ id: "local_m2", label: "局部平方米", legacy: true }),
    hole: Object.freeze({ id: "hole", label: "孔", legacy: true }),
    fold: Object.freeze({ id: "fold", label: "折", legacy: true }),
    page: Object.freeze({ id: "page", label: "页", legacy: true }),
    book: Object.freeze({ id: "book", label: "本", legacy: true }),
    time: Object.freeze({ id: "time", label: "分钟", legacy: true })
  });

  const CHARGE_MODES = Object.freeze({
    linear: Object.freeze({ id: "linear", label: "按实际用量" }),
    minimum: Object.freeze({ id: "minimum", label: "最低收费 + 超出按用量" }),
    package_plus_excess: Object.freeze({ id: "package_plus_excess", label: "套餐包含量 + 超量单价" })
  });

  const LAMINATION_SHEET_FORMATS = Object.freeze({
    full_open: Object.freeze({ id: "full_open", label: "全开", openNumber: 1, priceField: "fullOpenUnitPrice", regular: Object.freeze([787, 1092]), large: Object.freeze([889, 1194]) }),
    two_open: Object.freeze({ id: "two_open", label: "对开", openNumber: 2, priceField: "twoOpenUnitPrice", regular: Object.freeze([546, 787]), large: Object.freeze([597, 889]) }),
    four_open: Object.freeze({ id: "four_open", label: "四开", openNumber: 4, priceField: "fourOpenUnitPrice", regular: Object.freeze([393, 546]), large: Object.freeze([444, 597]) }),
    eight_open: Object.freeze({ id: "eight_open", label: "八开", openNumber: 8, priceField: "eightOpenUnitPrice", regular: Object.freeze([273, 393]), large: Object.freeze([298, 444]) }),
    sixteen_open: Object.freeze({ id: "sixteen_open", label: "十六开", openNumber: 16, priceField: "sixteenOpenUnitPrice", regular: Object.freeze([196, 273]), large: Object.freeze([222, 298]) }),
    thirty_two_open: Object.freeze({ id: "thirty_two_open", label: "三十二开", openNumber: 32, priceField: "thirtyTwoOpenUnitPrice", regular: Object.freeze([136, 196]), large: Object.freeze([149, 222]) })
  });

  // The current factory price table confirms four press-sheet openings.
  // Unconfirmed six/sixteen/thirty-two-open rows must not be auto-selected.
  const PRINT_SHEET_FORMATS = Object.freeze({
    full_open: LAMINATION_SHEET_FORMATS.full_open,
    two_open: LAMINATION_SHEET_FORMATS.two_open,
    four_open: LAMINATION_SHEET_FORMATS.four_open,
    eight_open: LAMINATION_SHEET_FORMATS.eight_open
  });

  const LAMINATION_FORMAT_ALIASES = Object.freeze({
    full_open: "full_open", "1_open": "full_open", "1k": "full_open", "1开": "full_open", "全开": "full_open", "全张": "full_open",
    two_open: "two_open", "2_open": "two_open", "2k": "two_open", "2开": "two_open", "对开": "two_open", "二开": "two_open",
    four_open: "four_open", "4_open": "four_open", "4k": "four_open", "4开": "four_open", "四开": "four_open",
    eight_open: "eight_open", "8_open": "eight_open", "8k": "eight_open", "8开": "eight_open", "八开": "eight_open",
    sixteen_open: "sixteen_open", "16_open": "sixteen_open", "16k": "sixteen_open", "16开": "sixteen_open", "十六开": "sixteen_open",
    thirty_two_open: "thirty_two_open", "32_open": "thirty_two_open", "32k": "thirty_two_open", "32开": "thirty_two_open", "三十二开": "thirty_two_open"
  });

  const PRINT_FORMAT_ALIASES = Object.freeze({
    full_open: "full_open", "1_open": "full_open", "1k": "full_open", "1开": "full_open", "全开": "full_open", "全张": "full_open",
    two_open: "two_open", "2_open": "two_open", "2k": "two_open", "2开": "two_open", "对开": "two_open", "二开": "two_open",
    four_open: "four_open", "4_open": "four_open", "4k": "four_open", "4开": "four_open", "四开": "four_open",
    eight_open: "eight_open", "8_open": "eight_open", "8k": "eight_open", "8开": "eight_open", "八开": "eight_open"
  });

  const STRUCTURE_TEMPLATES = Object.freeze({
    paper_box_tuck_end: Object.freeze({
      id: "paper_box_tuck_end",
      profileId: "paper_box",
      label: "折叠纸盒（插口/粘口）",
      dimensions: Object.freeze(["length", "depth", "height"])
    }),
    paper_box_lid_base: Object.freeze({
      id: "paper_box_lid_base",
      profileId: "paper_box",
      label: "天地盖（上盖+下盒双片联排）",
      dimensions: Object.freeze(["length", "width", "height"])
    }),
    paper_box_drawer: Object.freeze({
      id: "paper_box_drawer",
      profileId: "paper_box",
      label: "抽屉盒（外套+内抽双片联排）",
      dimensions: Object.freeze(["length", "width", "height"])
    }),
    paper_box_book_style: Object.freeze({
      id: "paper_box_book_style",
      profileId: "paper_box",
      label: "书型盒（封面+内盒双片联排）",
      dimensions: Object.freeze(["length", "width", "height"])
    }),
    paper_box_mailer: Object.freeze({
      id: "paper_box_mailer",
      profileId: "paper_box",
      label: "飞机盒一片式展开",
      dimensions: Object.freeze(["length", "width", "height"])
    }),
    paper_bag_side_gusset: Object.freeze({
      id: "paper_bag_side_gusset",
      profileId: "paper_bag",
      label: "侧风琴纸袋",
      dimensions: Object.freeze(["width", "depth", "height"])
    }),
    corrugated_rsc: Object.freeze({
      id: "corrugated_rsc",
      profileId: "corrugated",
      label: "0201/RSC 开槽纸箱",
      dimensions: Object.freeze(["length", "width", "height"])
    }),
    office_folder_pocket: Object.freeze({
      id: "office_folder_pocket",
      profileId: "office_folder",
      label: "双页口袋文件夹",
      dimensions: Object.freeze(["width", "height", "spine"])
    })
  });

  const PROFILE_RULES = Object.freeze({
    paper_flat: rule("sheet", ["outsourced", "gang", "digital", "offset"], ["material", "size", "print_side", "print_color", "ink_coverage"]),
    card: rule("sheet", ["outsourced", "gang", "digital", "offset"], ["material", "size", "print_side", "print_color"]),
    book: rule("book", ["digital", "offset"], ["cover_material", "inner_material", "size", "page_count", "binding"]),
    instruction: rule("book", ["digital", "offset"], ["material", "size", "page_count", "binding", "print_color"]),
    calendar: rule("book", ["digital", "offset"], ["material", "size", "page_count", "binding"]),
    label: rule("roll", ["fixed", "digital", "offset"], ["material", "size", "delivery_form", "print_color", "core_direction"]),
    paper_box: rule("dieline", ["outsourced", "digital", "offset"], ["material", "size", "box_structure", "ink_coverage", "required_process"]),
    paper_bag: rule("dieline", ["outsourced", "digital", "offset"], ["material", "size", "ink_coverage", "required_process", "handle", "hole_process"]),
    corrugated: rule("dieline", ["fixed", "digital", "offset"], ["material", "size", "box_structure", "print_color", "box_process"]),
    office: rule("multipart", ["fixed", "digital", "offset"], ["material", "size", "copy_count", "office_process"]),
    office_folder: rule("dieline", ["outsourced", "digital", "offset"], ["material", "size", "structure", "print_color", "folder_process"]),
    hang_tag: rule("sheet", ["outsourced", "gang", "digital", "offset"], ["material", "size", "shape", "print_side", "print_color"]),
    display: rule("area", ["fixed", "digital"], ["material", "size", "print_mode", "display_process"]),
    textile: rule("yield", ["fixed"], ["material", "size", "print_mode", "sewing_process"]),
    plastic: rule("weight", ["fixed"], ["material", "size", "bag_structure", "thickness", "print_color"]),
    gift: rule("unit", ["fixed"], ["material", "size", "logo_process", "gift_process"]),
    industrial: rule("unit", ["fixed"], ["material", "size", "drawing", "forming_process", "surface_process", "tolerance"]),
    wood_box: rule("unit", ["fixed"], ["material", "size", "box_structure", "surface_process", "logo_process"]),
    custom_generic: rule("unit", ["fixed"], ["material", "size", "specification", "required_process"]),
    electronic: rule("bom", ["fixed"], ["material", "size", "drawing", "assembly_process", "test_process"])
  });

  function rule(family, routes, requiredGroups) {
    return Object.freeze({ family, routes: Object.freeze(routes.slice()), requiredGroups: Object.freeze(requiredGroups.slice()) });
  }

  function number(value, label, options) {
    const settings = options || {};
    const parsed = Number(value);
    const allowZero = Boolean(settings.allowZero);
    if (!Number.isFinite(parsed) || (allowZero ? parsed < 0 : parsed <= 0)) {
      throw new Error(`${label}必须是${allowZero ? "非负" : "大于0的"}数字`);
    }
    return parsed;
  }

  function rate(value, label, options) {
    const parsed = number(value, label, { allowZero: Boolean(options && options.allowZero) });
    if (parsed > 1) throw new Error(`${label}不能超过100%`);
    return parsed;
  }

  function integer(value, label, options) {
    const parsed = number(value, label, options);
    if (!Number.isInteger(parsed)) throw new Error(`${label}必须是整数`);
    return parsed;
  }

  function parseDimensions(value, expected) {
    const count = Number(expected || 2);
    const rawParts = String(value || "").trim().split(/[xX×*]/).map(item => item.trim()).filter(Boolean);
    if (rawParts.length !== count) throw new Error(`尺寸必须填写${count}个数值，例如 200×150mm`);
    const globalUnit = rawParts.map(item => item.match(/(mm|毫米|cm|厘米|m|米)$/i)?.[1] || "").filter(Boolean).pop() || "mm";
    const unitScale = unit => /^(cm|厘米)$/i.test(unit) ? 10 : /^(m|米)$/i.test(unit) ? 1000 : 1;
    const parts = rawParts.map(item => {
      const match = item.match(/^\s*(\d+(?:\.\d+)?)\s*(mm|毫米|cm|厘米|m|米)?\s*$/i);
      if (!match) return NaN;
      return Number(match[1]) * unitScale(match[2] || globalUnit);
    });
    if (parts.some(item => !Number.isFinite(item) || item <= 0)) throw new Error(`尺寸必须填写${count}个大于0的数值，并标明 mm/cm`);
    return parts;
  }

  function parseFlexibleDimensions(value) {
    const raw = String(value || "").trim();
    if (!raw) throw new Error("尺寸不能为空");
    const rawParts = raw.split(/[xX×*]/).map(item => item.trim()).filter(Boolean);
    if (rawParts.length < 2 || rawParts.length > 3) throw new Error("尺寸必须填写2个或3个数值");
    return parseDimensions(raw, rawParts.length);
  }

  function flatSizeResult(input) {
    const widthMm = Number.isFinite(Number(input.widthMm)) ? Number(input.widthMm) : null;
    const heightMm = Number.isFinite(Number(input.heightMm)) ? Number(input.heightMm) : null;
    return Object.freeze({
      status: String(input.status || "ok"),
      widthMm,
      heightMm,
      areaM2: widthMm && heightMm ? widthMm / 1000 * (heightMm / 1000) : null,
      estimated: Boolean(input.estimated),
      source: String(input.source || "manual_required"),
      templateId: String(input.templateId || ""),
      note: String(input.note || "")
    });
  }

  function resolveStructureTemplate(profileId, structure) {
    const profile = String(profileId || "").trim();
    const raw = String(structure || "").trim().toLowerCase();
    if (Object.prototype.hasOwnProperty.call(STRUCTURE_TEMPLATES, raw)) return STRUCTURE_TEMPLATES[raw];
    const aliases = [
      { id: "paper_box_tuck_end", profileId: "paper_box", patterns: [/纸盒/, /插口/, /扣底/, /粘口/, /tuck/] },
      { id: "paper_box_lid_base", profileId: "paper_box", patterns: [/^天地盖(?:盒|礼盒)?$/] },
      { id: "paper_box_drawer", profileId: "paper_box", patterns: [/^抽屉(?:盒|礼盒)?$/] },
      { id: "paper_box_book_style", profileId: "paper_box", patterns: [/^书型(?:盒|礼盒)?$/] },
      { id: "paper_box_mailer", profileId: "paper_box", patterns: [/^飞机(?:盒|纸盒)?$/] },
      { id: "paper_bag_side_gusset", profileId: "paper_bag", patterns: [/纸袋/, /风琴/, /gusset/] },
      { id: "corrugated_rsc", profileId: "corrugated", patterns: [/0201/, /rsc/, /开槽/, /瓦楞/] },
      { id: "office_folder_pocket", profileId: "office_folder", patterns: [/文件夹/, /口袋/, /folder/] }
    ];
    const matched = aliases.find(item => item.profileId === profile && raw && item.patterns.some(pattern => pattern.test(raw)));
    if (matched) return STRUCTURE_TEMPLATES[matched.id];
    if (raw) return null;
    const defaultTemplate = aliases.find(item => item.profileId === profile);
    return defaultTemplate ? STRUCTURE_TEMPLATES[defaultTemplate.id] : null;
  }

  function estimateFlatSize(input) {
    const settings = input || {};
    const profileId = String(settings.profileId || settings.profile || "").trim();
    const ruleInfo = profileRule(profileId);
    let dimensions;
    try {
      dimensions = parseFlexibleDimensions(settings.finishedSize || settings.size || settings.dimensions);
    } catch (error) {
      return flatSizeResult({
        status: "manual_required",
        source: "manual_required",
        note: `无法自动计算展开尺寸：${String(error && error.message || error)}`
      });
    }

    if (dimensions.length === 2 && ruleInfo.family !== "dieline") {
      return flatSizeResult({
        widthMm: dimensions[0],
        heightMm: dimensions[1],
        estimated: false,
        source: "finished_size",
        note: "二维产品按成品宽×高作为展开尺寸。"
      });
    }

    const template = resolveStructureTemplate(profileId, settings.templateId || settings.structure);
    if (!template) {
      return flatSizeResult({
        status: "manual_required",
        source: "manual_required",
        note: dimensions.length === 3
          ? "当前三维产品没有匹配的结构模板，请选择结构或按刀线图填写展开尺寸。"
          : "当前产品需要结构模板才能计算展开尺寸。"
      });
    }

    const first = dimensions[0];
    const second = dimensions[1];
    const third = dimensions.length === 3 ? dimensions[2] : 0;
    const allowance = settings.allowances || {};
    let widthMm;
    let heightMm;
    let note;
    if (template.id === "paper_box_tuck_end") {
      if (!third) return flatSizeResult({ status: "manual_required", source: "manual_required", templateId: template.id, note: "纸盒展开尺寸需要长×宽（深）×高三个成品尺寸。" });
      const glueFlapMm = number(allowance.glueFlapMm === undefined ? Math.min(30, Math.max(15, second * 0.25)) : allowance.glueFlapMm, "粘口尺寸", { allowZero: true });
      widthMm = 2 * (first + second) + glueFlapMm;
      heightMm = third + 2 * second;
      note = "按折叠纸盒盒身四面、上下插翼和粘口估算；生产前仍需以刀线图核对。";
    } else if (template.id === "paper_box_lid_base") {
      if (!third) return flatSizeResult({ status: "manual_required", source: "manual_required", templateId: template.id, note: "天地盖展开尺寸需要长×宽×高三个成品尺寸。" });
      const gapMm = number(allowance.componentGapMm === undefined ? 10 : allowance.componentGapMm, "双片联排间隙", { allowZero: true });
      const lidClearanceMm = number(allowance.lidClearanceMm === undefined ? 2 : allowance.lidClearanceMm, "天地盖配合余量", { allowZero: true });
      widthMm = (first + 2 * third) + gapMm + (first + 2 * third + 2 * lidClearanceMm);
      heightMm = second + 2 * third + 2 * lidClearanceMm;
      note = "按上盖和下盒两片刀版并排、含默认配合余量估算材料包络；生产前需按实际包边、灰板厚度和刀线图修正。";
    } else if (template.id === "paper_box_drawer") {
      if (!third) return flatSizeResult({ status: "manual_required", source: "manual_required", templateId: template.id, note: "抽屉盒展开尺寸需要长×宽×高三个成品尺寸。" });
      const gapMm = number(allowance.componentGapMm === undefined ? 10 : allowance.componentGapMm, "双片联排间隙", { allowZero: true });
      const glueFlapMm = number(allowance.glueFlapMm === undefined ? 20 : allowance.glueFlapMm, "外套粘口尺寸", { allowZero: true });
      widthMm = (first + 2 * third) + gapMm + (2 * (second + third) + glueFlapMm);
      heightMm = Math.max(second + 2 * third, first);
      note = "按内抽托盘与外套两片刀版并排估算材料包络；生产前需按抽拉间隙、包边和刀线图修正。";
    } else if (template.id === "paper_box_book_style") {
      if (!third) return flatSizeResult({ status: "manual_required", source: "manual_required", templateId: template.id, note: "书型盒展开尺寸需要长×宽×高三个成品尺寸。" });
      const gapMm = number(allowance.componentGapMm === undefined ? 10 : allowance.componentGapMm, "双片联排间隙", { allowZero: true });
      widthMm = (first + 2 * third) + gapMm + (2 * first + 3 * third);
      heightMm = second + 2 * third;
      note = "按书型封面与内盒两片刀版并排估算材料包络；生产前需按书脊、磁吸、包边、灰板厚度和刀线图修正。";
    } else if (template.id === "paper_box_mailer") {
      if (!third) return flatSizeResult({ status: "manual_required", source: "manual_required", templateId: template.id, note: "飞机盒展开尺寸需要长×宽×高三个成品尺寸。" });
      widthMm = first + 2 * second + 2 * third;
      heightMm = second + 2 * third;
      note = "按常规一片式飞机盒主板、侧翼和插舌包络估算；生产前需按锁扣、插舌和刀线图修正。";
    } else if (template.id === "paper_bag_side_gusset") {
      if (!third) return flatSizeResult({ status: "manual_required", source: "manual_required", templateId: template.id, note: "纸袋展开尺寸需要宽×侧宽×高三个成品尺寸。" });
      const glueFlapMm = number(allowance.glueFlapMm === undefined ? 20 : allowance.glueFlapMm, "粘口尺寸", { allowZero: true });
      const topFoldMm = number(allowance.topFoldMm === undefined ? 40 : allowance.topFoldMm, "袋口折边", { allowZero: true });
      const bottomFoldMm = number(allowance.bottomFoldMm === undefined ? second : allowance.bottomFoldMm, "袋底折边", { allowZero: true });
      widthMm = 2 * (first + second) + glueFlapMm;
      heightMm = third + topFoldMm + bottomFoldMm;
      note = "按侧风琴纸袋四面、粘口、袋口折边和袋底估算；生产前仍需以刀线图核对。";
    } else if (template.id === "corrugated_rsc") {
      if (!third) return flatSizeResult({ status: "manual_required", source: "manual_required", templateId: template.id, note: "开槽纸箱展开尺寸需要长×宽×高三个成品尺寸。" });
      const jointMm = number(allowance.jointMm === undefined ? 35 : allowance.jointMm, "钉粘口尺寸", { allowZero: true });
      widthMm = 2 * (first + second) + jointMm;
      heightMm = third + second;
      note = "按0201/RSC开槽纸箱四面、上下摇盖和钉粘口估算；生产前仍需以刀线图核对。";
    } else if (template.id === "office_folder_pocket") {
      const spineMm = third || number(allowance.spineMm || 0, "文件夹书脊", { allowZero: true });
      const pocketDepthMm = number(allowance.pocketDepthMm === undefined ? second * 0.35 : allowance.pocketDepthMm, "口袋深度", { allowZero: true });
      widthMm = 2 * first + spineMm;
      heightMm = second + pocketDepthMm;
      note = "按双页口袋文件夹面板、书脊和底部口袋估算；生产前仍需以刀线图核对。";
    }

    return flatSizeResult({
      widthMm,
      heightMm,
      estimated: true,
      source: "structure_template",
      templateId: template.id,
      note
    });
  }

  function bestSheetLayout(input) {
    const pieceWidth = number(input.pieceWidthMm, "展开宽");
    const pieceHeight = number(input.pieceHeightMm, "展开高");
    const sheetWidth = number(input.sheetWidthMm, "上机纸宽");
    const sheetHeight = number(input.sheetHeightMm, "上机纸高");
    const gripper = number(input.gripperMm || 0, "咬口", { allowZero: true });
    const sideMargin = number(input.sideMarginMm || 0, "侧规/安全边", { allowZero: true });
    const gap = number(input.gapMm || 0, "拼版间隙", { allowZero: true });
    const usableWidth = sheetWidth - sideMargin * 2;
    const usableHeight = sheetHeight - gripper - sideMargin;
    if (usableWidth <= 0 || usableHeight <= 0) throw new Error("咬口和安全边超过上机纸尺寸");

    function layout(width, height, rotated) {
      const across = Math.floor((usableWidth + gap) / (width + gap));
      const down = Math.floor((usableHeight + gap) / (height + gap));
      return { rotated, across: Math.max(0, across), down: Math.max(0, down), count: Math.max(0, across) * Math.max(0, down) };
    }

    const candidates = [layout(pieceWidth, pieceHeight, false)];
    if (input.allowRotate !== false && pieceWidth !== pieceHeight) candidates.push(layout(pieceHeight, pieceWidth, true));
    const best = candidates.sort((left, right) => right.count - left.count || Number(left.rotated) - Number(right.rotated))[0];
    if (!best || best.count < 1) throw new Error("当前展开尺寸无法排入所选上机纸");
    return Object.freeze({ ...best, usableWidthMm: usableWidth, usableHeightMm: usableHeight });
  }

  function productionSheets(input) {
    const quantity = integer(input.quantity, "成品数量");
    const piecesPerProduct = integer(input.piecesPerProduct || 1, "每件用量");
    const piecesPerSheet = integer(input.piecesPerSheet, "每张拼版数");
    const setupWasteSheets = integer(input.setupWasteSheets || 0, "起机损耗张数", { allowZero: true });
    const printWasteRate = rate(input.printWasteRate || 0, "印刷损耗率", { allowZero: true });
    const finishingWasteRate = rate(input.finishingWasteRate || 0, "后工损耗率", { allowZero: true });
    if (printWasteRate >= 1 || finishingWasteRate >= 1) throw new Error("印刷和后工损耗率必须小于100%");
    const netSheets = Math.ceil(quantity * piecesPerProduct / piecesPerSheet);
    const variableSheets = Math.ceil(netSheets / ((1 - printWasteRate) * (1 - finishingWasteRate)));
    const automaticProductionSheets = variableSheets + setupWasteSheets;
    const overrideText = String(input.productionSheetsOverride ?? "").trim();
    let finalProductionSheets = automaticProductionSheets;
    let manuallyAdjusted = false;
    if (overrideText) {
      finalProductionSheets = integer(overrideText, "人工修正生产印张");
      if (finalProductionSheets < netSheets) {
        throw new Error(`人工修正生产印张不能少于净印张 ${netSheets}`);
      }
      manuallyAdjusted = finalProductionSheets !== automaticProductionSheets;
    }
    return Object.freeze({
      netSheets,
      setupWasteSheets,
      automaticProductionSheets,
      productionSheets: finalProductionSheets,
      manuallyAdjusted
    });
  }

  function paperWeightKg(input) {
    const sheetWidth = number(input.sheetWidthMm, "上机纸宽");
    const sheetHeight = number(input.sheetHeightMm, "上机纸高");
    const gsm = number(input.gsm, "材料克重");
    const sheets = integer(input.sheets, "生产印张");
    const areaM2 = sheetWidth / 1000 * sheetHeight / 1000 * sheets;
    return Object.freeze({ areaM2, weightKg: areaM2 * gsm / 1000 });
  }

  function normalizeOperationBasis(value) {
    const raw = String(value || "piece").trim().toLowerCase();
    const aliases = {
      unit: "piece",
      per_piece: "piece",
      item: "piece",
      pieces: "piece",
      sheets: "sheet",
      sqm: "area_m2",
      m2: "area_m2",
      square_meter: "area_m2",
      metres: "meter",
      meters: "meter",
      kilogram: "kg",
      kilograms: "kg",
      times: "run",
      hours: "hour"
    };
    const normalized = aliases[raw] || raw;
    if (!Object.prototype.hasOwnProperty.call(OPERATION_BASES, normalized)) throw new Error(`不支持的工艺计价基数：${value}`);
    return normalized;
  }

  function normalizeChargeMode(value) {
    const raw = String(value || "minimum").trim().toLowerCase();
    const aliases = {
      floor: "minimum",
      minimum_charge: "minimum",
      package: "package_plus_excess",
      tiered: "package_plus_excess"
    };
    const normalized = aliases[raw] || raw;
    if (!Object.prototype.hasOwnProperty.call(CHARGE_MODES, normalized)) throw new Error(`不支持的工艺计价模式：${value}`);
    return normalized;
  }

  function normalizeOperationPricing(input) {
    const settings = input || {};
    const optionalRate = settings.unitRate === undefined || settings.unitRate === null || settings.unitRate === ""
      ? null
      : number(settings.unitRate, "工艺单价", { allowZero: true });
    const minimumCharge = number(settings.minimumCharge || 0, "工艺最低收费", { allowZero: true });
    const packageFee = settings.packageFee === undefined || settings.packageFee === null || settings.packageFee === ""
      ? minimumCharge
      : number(settings.packageFee, "套餐保底费", { allowZero: true });
    return Object.freeze({
      basis: normalizeOperationBasis(settings.basis || "piece"),
      chargeMode: normalizeChargeMode(settings.chargeMode || "minimum"),
      unitRate: optionalRate,
      setupFee: number(settings.setupFee || 0, "工艺开机费", { allowZero: true }),
      minimumCharge,
      packageQuantity: number(settings.packageQuantity ?? settings.includedQuantity ?? 0, "套餐包含量", { allowZero: true }),
      packageFee
    });
  }

  function operationCharge(input) {
    const settings = input || {};
    const setupFee = number(settings.setupFee || 0, "工艺开机费", { allowZero: true });
    if (settings.unitRate === undefined || settings.unitRate === null || settings.unitRate === "") {
      throw new Error("工艺单价 unitRate 必须由厂家明确填写；没有单价时可显式填写0");
    }
    const unitRate = number(settings.unitRate, "工艺单价", { allowZero: true });
    const basisQuantity = number(settings.basisQuantity || 0, "工艺计价基数", { allowZero: true });
    const minimumCharge = number(settings.minimumCharge || 0, "工艺最低收费", { allowZero: true });
    const chargeMode = normalizeChargeMode(settings.chargeMode || "minimum");
    if (chargeMode === "linear") return setupFee + unitRate * basisQuantity;
    if (chargeMode === "package_plus_excess") {
      const packageQuantity = number(settings.packageQuantity ?? settings.includedQuantity ?? 0, "套餐包含量", { allowZero: true });
      const packageFee = settings.packageFee === undefined || settings.packageFee === null || settings.packageFee === ""
        ? minimumCharge
        : number(settings.packageFee, "套餐保底费", { allowZero: true });
      const excessQuantity = Math.max(0, basisQuantity - packageQuantity);
      return Math.max(minimumCharge, setupFee + packageFee + unitRate * excessQuantity);
    }
    // Backward compatibility: saved operations without chargeMode use the historic floor formula.
    return Math.max(minimumCharge, setupFee + unitRate * basisQuantity);
  }

  function resolveLaminationSheetFormat(input) {
    const settings = input || {};
    const forced = String(settings.sheetFormat || settings.format || "auto").trim().toLowerCase();
    const rawStandard = String(settings.sheetStandard || settings.standard || "auto").trim().toLowerCase();
    let sheetStandard = ["large", "大度", "大度纸"].includes(rawStandard) ? "large"
      : ["regular", "正度", "正度纸"].includes(rawStandard) ? "regular" : "auto";
    if (sheetStandard === "auto") {
      const parentWidth = Number(settings.sheetWidthMm || 0);
      const parentHeight = Number(settings.sheetHeightMm || 0);
      const parentShort = Math.min(parentWidth, parentHeight);
      const parentLong = Math.max(parentWidth, parentHeight);
      sheetStandard = parentShort > 787 || parentLong > 1092 ? "large" : "regular";
    }
    const forcedId = LAMINATION_FORMAT_ALIASES[forced];
    const standardLabel = sheetStandard === "large" ? "大度" : "正度";
    if (forcedId) {
      const definition = LAMINATION_SHEET_FORMATS[forcedId];
      return Object.freeze({
        id: definition.id,
        label: `${standardLabel}${definition.label}刀板`,
        openNumber: definition.openNumber,
        sheetStandard,
        standardLabel,
        automatic: false
      });
    }
    const widthMm = number(settings.widthMm ?? settings.laminationWidthMm ?? settings.pieceWidthMm, "覆膜实际宽");
    const heightMm = number(settings.heightMm ?? settings.laminationHeightMm ?? settings.pieceHeightMm, "覆膜实际高");
    const shortEdge = Math.min(widthMm, heightMm);
    const longEdge = Math.max(widthMm, heightMm);
    const areaM2 = widthMm / 1000 * (heightMm / 1000);
    const candidates = Object.values(LAMINATION_SHEET_FORMATS).slice().sort((left, right) => right.openNumber - left.openNumber);
    const matched = candidates.find(definition => {
      const dimensions = definition[sheetStandard];
      const standardShort = Math.min(...dimensions);
      const standardLong = Math.max(...dimensions);
      return shortEdge <= standardShort + 2 && longEdge <= standardLong + 2;
    });
    if (!matched) {
      return Object.freeze({
        id: "custom",
        label: `${standardLabel}超全开/非标尺寸`,
        openNumber: null,
        sheetStandard,
        standardLabel,
        automatic: true,
        widthMm,
        heightMm,
        areaM2
      });
    }
    return Object.freeze({
      id: matched.id,
      label: `${standardLabel}${matched.label}刀板`,
      openNumber: matched.openNumber,
      sheetStandard,
      standardLabel,
      automatic: true,
      widthMm,
      heightMm,
      areaM2
    });
  }

  function laminationTierRate(operation, context) {
    const settings = operation || {};
    const tier = settings.laminationTierPricing;
    if (!tier || tier.enabled === false) return null;
    const sheetFormat = resolveLaminationSheetFormat({
      sheetFormat: context?.laminationSheetFormat || context?.sheetFormat || "auto",
      sheetStandard: context?.laminationSheetStandard || context?.sheetStandard || "auto",
      sheetWidthMm: context?.sheetWidthMm,
      sheetHeightMm: context?.sheetHeightMm,
      laminationWidthMm: context?.laminationWidthMm ?? context?.pieceWidthMm,
      laminationHeightMm: context?.laminationHeightMm ?? context?.pieceHeightMm
    });
    if (sheetFormat.id === "custom") throw new Error("覆膜实际尺寸超过全开或属于非标准开数，请人工核价");
    const definition = LAMINATION_SHEET_FORMATS[sheetFormat.id];
    const unitRate = number(tier[definition.priceField] ?? 0, `${sheetFormat.label}覆膜工价`, { allowZero: true });
    if (!(unitRate > 0)) throw new Error(`${sheetFormat.label}工价尚未填写`);
    const sideCount = integer(context?.laminationSideCount || 1, "覆膜面数");
    if (![1, 2].includes(sideCount)) throw new Error("覆膜面数只能是单面1或双面2");
    const sheetQuantity = integer(context?.laminationQuantity || context?.productionSheets || context?.quantity || 0, "覆膜数量");
    return Object.freeze({
      unitRate,
      sheetFormat,
      sheetQuantity,
      sideCount,
      basisQuantity: sheetQuantity * sideCount,
      filmType: String(context?.laminationFilmType || "").trim()
    });
  }

  function resolvePrintSheetFormat(input) {
    const settings = input || {};
    const forced = String(settings.printSheetFormat || settings.sheetFormat || settings.format || "auto").trim().toLowerCase();
    const rawStandard = String(settings.printSheetStandard || settings.sheetStandard || settings.standard || "auto").trim().toLowerCase();
    const forcedId = PRINT_FORMAT_ALIASES[forced];
    const requestedStandard = ["large", "大度", "大度纸"].includes(rawStandard) ? "large"
      : ["regular", "正度", "正度纸"].includes(rawStandard) ? "regular" : "auto";
    const widthRaw = settings.printSheetWidthMm ?? settings.widthMm ?? settings.sheetWidthMm;
    const heightRaw = settings.printSheetHeightMm ?? settings.heightMm ?? settings.sheetHeightMm;
    const hasDimensions = widthRaw !== undefined && widthRaw !== null && widthRaw !== ""
      && heightRaw !== undefined && heightRaw !== null && heightRaw !== "";
    const widthMm = hasDimensions ? number(widthRaw, "上机纸实际宽") : 0;
    const heightMm = hasDimensions ? number(heightRaw, "上机纸实际高") : 0;
    const shortEdge = Math.min(widthMm, heightMm);
    const longEdge = Math.max(widthMm, heightMm);

    const exactMatch = hasDimensions
      ? Object.values(PRINT_SHEET_FORMATS).flatMap(definition => {
        const standards = requestedStandard === "auto" ? ["regular", "large"] : [requestedStandard];
        return standards.map(sheetStandard => ({ definition, sheetStandard }));
      }).find(candidate => {
        const dimensions = candidate.definition[candidate.sheetStandard];
        const standardShort = Math.min(...dimensions);
        const standardLong = Math.max(...dimensions);
        // A small trim tolerance is allowed, but a merely "nearby" or
        // containable size must not silently inherit another opening's rate.
        return Math.abs(shortEdge - standardShort) <= 3 && Math.abs(longEdge - standardLong) <= 3;
      })
      : null;

    if (forcedId && PRINT_SHEET_FORMATS[forcedId]) {
      const definition = PRINT_SHEET_FORMATS[forcedId];
      const sheetStandard = requestedStandard !== "auto"
        ? requestedStandard
        : exactMatch?.sheetStandard || "regular";
      const standardLabel = sheetStandard === "large" ? "大度" : "正度";
      return Object.freeze({
        id: definition.id,
        label: `${standardLabel}${definition.label}`,
        openNumber: definition.openNumber,
        sheetStandard,
        standardLabel,
        automatic: false,
        ...(hasDimensions ? { widthMm, heightMm, areaM2: widthMm / 1000 * (heightMm / 1000) } : {})
      });
    }

    if (!exactMatch) {
      const sheetStandard = requestedStandard === "large" ? "large" : "regular";
      const standardLabel = sheetStandard === "large" ? "大度" : "正度";
      return Object.freeze({
        id: "custom",
        label: "非标上机纸尺寸",
        openNumber: null,
        sheetStandard,
        standardLabel,
        automatic: true,
        widthMm,
        heightMm,
        areaM2: widthMm / 1000 * (heightMm / 1000)
      });
    }

    const { definition, sheetStandard } = exactMatch;
    const standardLabel = sheetStandard === "large" ? "大度" : "正度";
    return Object.freeze({
      id: definition.id,
      label: `${standardLabel}${definition.label}`,
      openNumber: definition.openNumber,
      sheetStandard,
      standardLabel,
      automatic: true,
      widthMm,
      heightMm,
      areaM2: widthMm / 1000 * (heightMm / 1000)
    });
  }

  function printTierLegacyField(definition, suffix) {
    return String(definition.priceField || "").replace(/UnitPrice$/, suffix);
  }

  function printSheetTierRate(operation, context) {
    const settings = operation || {};
    const tier = settings.printSheetTierPricing;
    if (!tier || tier.enabled === false) return null;
    const sheetFormat = resolvePrintSheetFormat({
      printSheetFormat: context?.printSheetFormat || context?.sheetFormat || "auto",
      printSheetStandard: context?.printSheetStandard || context?.sheetStandard || "auto",
      printSheetWidthMm: context?.printSheetWidthMm ?? context?.sheetWidthMm,
      printSheetHeightMm: context?.printSheetHeightMm ?? context?.sheetHeightMm
    });
    if (sheetFormat.id === "custom") throw new Error("上机纸尺寸超过全开或属于非标准开数，请人工核价");
    const definition = PRINT_SHEET_FORMATS[sheetFormat.id];
    const formatTier = tier[sheetFormat.id] && typeof tier[sheetFormat.id] === "object"
      ? tier[sheetFormat.id]
      : {};
    const unitPrice = number(
      formatTier.unitPrice
        ?? tier[definition.priceField]
        ?? settings.unitRate
        ?? settings.unitPrice
        ?? 0,
      `${sheetFormat.label}印刷单价`,
      { allowZero: true }
    );
    const minimumCharge = number(
      formatTier.minimumCharge
        ?? tier[printTierLegacyField(definition, "MinimumCharge")]
        ?? settings.minimumCharge
        ?? 0,
      `${sheetFormat.label}最低收费`,
      { allowZero: true }
    );
    const includedQuantity = number(
      formatTier.includedQuantity
        ?? tier[printTierLegacyField(definition, "IncludedQuantity")]
        ?? tier.includedQuantity
        ?? settings.includedQuantity
        ?? settings.packageQuantity
        ?? 0,
      `${sheetFormat.label}包含印张`,
      { allowZero: true }
    );
    const excessUnitPrice = number(
      formatTier.excessUnitPrice
        ?? tier[printTierLegacyField(definition, "ExcessUnitPrice")]
        ?? tier.excessUnitPrice
        ?? settings.excessUnitPrice
        ?? unitPrice,
      `${sheetFormat.label}超量单价`,
      { allowZero: true }
    );
    if (!(unitPrice > 0) && !(includedQuantity > 0 && minimumCharge > 0 && excessUnitPrice > 0)) {
      throw new Error(`${sheetFormat.label}印刷价格尚未填写`);
    }
    const sideCount = integer(context?.printSideCount || context?.sideCount || 1, "印刷面数");
    if (![1, 2].includes(sideCount)) throw new Error("印刷面数只能是单面1或双面2");
    const sheetQuantity = integer(
      context?.printSheetQuantity || context?.productionSheets || context?.sheets || 0,
      "印刷张数"
    );
    const packageMode = includedQuantity > 0;
    return Object.freeze({
      unitRate: packageMode ? excessUnitPrice : unitPrice,
      unitPrice,
      minimumCharge,
      includedQuantity,
      excessUnitPrice,
      thresholdQuantity: includedQuantity,
      withinPrice: minimumCharge,
      aboveUnitPrice: excessUnitPrice,
      chargeMode: packageMode ? "package_plus_excess" : "minimum",
      packageFee: minimumCharge,
      sheetFormat,
      sheetQuantity,
      sideCount,
      basisQuantity: sheetQuantity * sideCount
    });
  }

  function sellingPrice(input) {
    const totalCost = number(input.totalCost, "总成本", { allowZero: true });
    const targetGrossMargin = rate(input.targetGrossMargin || 0, "目标毛利率", { allowZero: true });
    if (targetGrossMargin >= 1) throw new Error("目标毛利率必须小于100%");
    const preTax = totalCost / (1 - targetGrossMargin);
    const taxRate = rate(input.taxRate || 0, "税率", { allowZero: true });
    const tax = input.taxIncluded ? preTax * taxRate : 0;
    return Object.freeze({ preTax, tax, total: preTax + tax, grossProfit: preTax - totalCost });
  }

  function operationBasisQuantity(operation, context) {
    const settings = operation || {};
    const valuesContext = context || {};
    const basis = normalizeOperationBasis(settings.basis || "piece");
    const quantity = number(valuesContext.quantity || 0, "成品数量", { allowZero: true });
    const usagePerPiece = settings.usagePerPiece === undefined || settings.usagePerPiece === null || settings.usagePerPiece === ""
      ? null
      : number(settings.usagePerPiece, "每件用量", { allowZero: true });
    const perPieceOr = (explicitValue, contextValue, fallback = 0) => {
      if (explicitValue !== undefined && explicitValue !== null && explicitValue !== "") {
        return number(explicitValue, "工艺实际用量", { allowZero: true });
      }
      if (usagePerPiece !== null) return quantity * usagePerPiece;
      return number(contextValue ?? fallback, "工艺实际用量", { allowZero: true });
    };
    // Only validate the selected basis. Building every basis eagerly used to make
    // unrelated zero-valued fields (for example a book operation carrying the
    // normalized default `usagePerPiece: 0`) abort an otherwise valid quote.
    switch (basis) {
      case "piece":
      case "book":
        return quantity;
      case "sheet":
        return number(valuesContext.productionSheets ?? valuesContext.sheets ?? 0, "生产张数", { allowZero: true });
      case "area_m2":
        return number(settings.areaM2 ?? valuesContext.printAreaM2 ?? valuesContext.areaM2 ?? 0, "工艺面积", { allowZero: true });
      case "local_m2":
        return perPieceOr(settings.localAreaM2, valuesContext.localAreaM2);
      case "meter":
        return perPieceOr(settings.meters, valuesContext.meters);
      case "kg":
        return perPieceOr(settings.kg ?? settings.kilograms, valuesContext.weightKg ?? valuesContext.kg);
      case "hole":
        return quantity * number(settings.countPerPiece ?? usagePerPiece ?? 1, "每件孔数");
      case "fold":
        return quantity * number(settings.countPerPiece ?? usagePerPiece ?? 1, "每件折数");
      case "page":
        return quantity * number(settings.pagesPerPiece ?? usagePerPiece ?? 1, "每件页数");
      case "run":
        return number(settings.runs ?? usagePerPiece ?? valuesContext.runs ?? 1, "工艺开机次数", { allowZero: true });
      case "hour":
        return settings.minutes !== undefined && settings.minutes !== null && settings.minutes !== ""
          ? number(Number(settings.minutes) / 60, "工艺小时", { allowZero: true })
          : perPieceOr(settings.hours, valuesContext.hours);
      case "time":
        return number(settings.minutes || 0, "工艺分钟", { allowZero: true });
      default:
        return undefined;
    }
  }

  function calculateSheetQuote(input) {
    const quantity = integer(input.quantity, "成品数量");
    const route = String(input.route || "offset");
    if (!Object.prototype.hasOwnProperty.call(ROUTES, route)) throw new Error(`不支持的生产路线：${route}`);
    if (route === "outsourced") {
      const supplierTotalCost = number(input.supplierTotalCost, "外发采购总成本");
      const overheadRate = rate(input.overheadRate || 0, "制造间接费率", { allowZero: true });
      const overheadCost = supplierTotalCost * overheadRate;
      const totalCost = supplierTotalCost + overheadCost;
      const sell = sellingPrice({
        totalCost,
        targetGrossMargin: input.targetGrossMargin || 0,
        taxRate: input.taxRate || 0,
        taxIncluded: Boolean(input.taxIncluded)
      });
      return Object.freeze({
        route,
        outsourced: true,
        printingEnabled: true,
        quantity,
        layout: Object.freeze({ count: 0, across: 0, down: 0, rotated: false }),
        sheets: Object.freeze({ netSheets: 0, productionSheets: 0 }),
        paper: Object.freeze({ weightKg: 0 }),
        impressions: 0,
        plateCount: 0,
        printSheetFormat: null,
        printTier: null,
        productionHours: 0,
        costs: Object.freeze({ material: 0, printing: 0, labor: 0, machine: 0, operations: 0, outsource: supplierTotalCost, overhead: overheadCost, total: totalCost }),
        operationItems: Object.freeze([]),
        supplier: Object.freeze({
          name: String(input.supplierName || "").trim(),
          quotedAt: String(input.supplierQuotedAt || "").trim(),
          scope: "同规格成品采购价（不含运）"
        }),
        sell,
        unitPrice: sell.total / quantity
      });
    }
    const printingEnabled = input.printingEnabled !== false;
    const layout = bestSheetLayout(input);
    const sheets = productionSheets({
      quantity,
      piecesPerProduct: input.piecesPerProduct || 1,
      piecesPerSheet: layout.count,
      setupWasteSheets: input.setupWasteSheets || 0,
      printWasteRate: input.printWasteRate || 0,
      finishingWasteRate: input.finishingWasteRate || 0,
      productionSheetsOverride: input.productionSheetsOverride
    });
    const paper = paperWeightKg({
      sheetWidthMm: input.sheetWidthMm,
      sheetHeightMm: input.sheetHeightMm,
      gsm: input.gsm,
      sheets: sheets.productionSheets
    });
    const materialPricePerKg = number(input.materialPricePerKg, "材料采购价");
    const materialCost = paper.weightKg * materialPricePerKg;
    const sideCount = integer(input.sideCount || 1, "印刷面数");
    const colorCount = integer(input.colorCount || 1, "印刷色数");
    const hasPlateCountOverride = input.plateCount !== undefined && input.plateCount !== null && String(input.plateCount).trim() !== "";
    const plateCount = !printingEnabled || route === "digital"
      ? 0
      : integer(hasPlateCountOverride ? input.plateCount : sideCount * colorCount, "印版数");
    const impressions = sheets.productionSheets * sideCount;
    const operations = Array.isArray(input.operations) ? input.operations : [];
    const printTierOperation = input.printTierOperation && typeof input.printTierOperation === "object"
      ? input.printTierOperation
      : null;
    const hasPrintSheetTier = printingEnabled && (Boolean(printTierOperation?.printSheetTierPricing && printTierOperation.printSheetTierPricing.enabled !== false)
      || operations.some(operation => operation?.printSheetTierPricing && operation.printSheetTierPricing.enabled !== false));
    const basePrintingCost = !printingEnabled || hasPrintSheetTier ? 0 : operationCharge({
      setupFee: input.printSetupFee || 0,
      unitRate: input.printRatePerImpression,
      basisQuantity: impressions,
      minimumCharge: input.printMinimumCharge || 0
    });
    const plateCost = plateCount * number(!printingEnabled || route === "digital" ? 0 : input.plateFee || 0, "印版单价", { allowZero: true });

    const pressSpeed = printingEnabled ? number(input.pressSheetsPerHour, "设备速度") : 1;
    const setupMinutes = printingEnabled ? number(input.setupMinutes || 0, "开机准备分钟", { allowZero: true }) : 0;
    const productionHours = printingEnabled ? sheets.productionSheets / pressSpeed : 0;
    const setupHours = printingEnabled ? setupMinutes / 60 : 0;
    // A factory print tier is already an all-in press charge. Adding generic
    // operator and machine-hour costs again would count the same press stage
    // twice. Hourly cost engineering is used only when no all-in tier exists.
    const laborHourly = hasPrintSheetTier ? 0 : number(input.laborHourly || 0, "人工小时成本", { allowZero: true });
    const machineHourly = hasPrintSheetTier ? 0 : number(input.machineHourly || 0, "设备小时成本", { allowZero: true });
    const operatorCount = integer(input.operatorCount || 1, "操作人数");
    const laborCost = (setupHours + productionHours) * operatorCount * laborHourly;
    const machineCost = (setupHours + productionHours) * machineHourly;
    // 整版后道应按实际进入设备的生产大张面积计费，不能用成品净面积；
    // 否则拼版空隙、咬口和损耗张都会被漏算。
    const printAreaM2 = Number(input.sheetWidthMm) / 1000
      * Number(input.sheetHeightMm) / 1000
      * sheets.productionSheets;
    const context = {
      quantity,
      productionSheets: sheets.productionSheets,
      printAreaM2,
      weightKg: paper.weightKg,
      hours: setupHours + productionHours,
      pieceWidthMm: Number(input.pieceWidthMm),
      pieceHeightMm: Number(input.pieceHeightMm),
      sheetWidthMm: Number(input.sheetWidthMm),
      sheetHeightMm: Number(input.sheetHeightMm),
      printSheetWidthMm: input.printSheetWidthMm ?? Number(input.sheetWidthMm),
      printSheetHeightMm: input.printSheetHeightMm ?? Number(input.sheetHeightMm),
      printSheetFormat: input.printSheetFormat || "auto",
      printSheetStandard: input.printSheetStandard || "auto",
      printSideCount: sideCount,
      printSheetQuantity: input.printSheetQuantity || sheets.productionSheets,
      laminationWidthMm: input.laminationWidthMm ?? Number(input.pieceWidthMm),
      laminationHeightMm: input.laminationHeightMm ?? Number(input.pieceHeightMm),
      laminationSheetFormat: input.laminationSheetFormat || "auto",
      laminationSheetStandard: input.laminationSheetStandard || "auto",
      laminationSideCount: input.laminationSideCount || 1,
      laminationQuantity: input.laminationQuantity || sheets.productionSheets,
      laminationFilmType: input.laminationFilmType || ""
    };
    const directPrintTier = printingEnabled ? printSheetTierRate(printTierOperation, context) : null;
    const directPrintOperation = directPrintTier
      ? {
          ...printTierOperation,
          basis: "sheet",
          unitRate: directPrintTier.unitRate,
          minimumCharge: directPrintTier.minimumCharge,
          chargeMode: directPrintTier.chargeMode,
          packageQuantity: directPrintTier.includedQuantity,
          packageFee: directPrintTier.packageFee
        }
      : null;
    const directPrintTierCost = directPrintTier
      ? operationCharge({ ...directPrintOperation, basisQuantity: directPrintTier.basisQuantity })
      : 0;
    const operationItems = operations
      .filter(operation => printingEnabled || !operation?.printSheetTierPricing)
      .filter(operation => !(directPrintTier && operation?.printSheetTierPricing))
      .map(operation => {
      const printTier = printingEnabled ? printSheetTierRate(operation, context) : null;
      const laminationTier = printTier ? null : laminationTierRate(operation, context);
      const tierRate = printTier || laminationTier;
      const effectiveOperation = tierRate
        ? {
            ...operation,
            basis: "sheet",
            unitRate: tierRate.unitRate,
            minimumCharge: tierRate.minimumCharge ?? operation.minimumCharge,
            chargeMode: tierRate.chargeMode ?? operation.chargeMode,
            packageQuantity: tierRate.includedQuantity ?? operation.packageQuantity,
            packageFee: tierRate.packageFee ?? operation.packageFee
          }
        : operation;
      const basisQuantity = tierRate?.basisQuantity ?? operationBasisQuantity(effectiveOperation, context);
      const total = operationCharge({ ...effectiveOperation, basisQuantity });
      return Object.freeze({
        id: String(operation.id || "operation"),
        name: String(operation.name || "后道工艺"),
        basis: normalizeOperationBasis(effectiveOperation.basis || "piece"),
        basisQuantity,
        unitRate: tierRate?.unitRate ?? Number(effectiveOperation.unitRate || 0),
        sheetFormat: tierRate?.sheetFormat || null,
        pricingModel: printTier ? "print_sheet_tier" : laminationTier ? "lamination_sheet_tier" : "standard",
        minimumCharge: printTier?.minimumCharge ?? Number(effectiveOperation.minimumCharge || 0),
        includedQuantity: printTier?.includedQuantity ?? null,
        excessUnitPrice: printTier?.excessUnitPrice ?? null,
        printSideCount: printTier?.sideCount || null,
        laminationSideCount: tierRate?.sideCount || null,
        laminationFilmType: tierRate?.filmType || "",
        total
      });
    });
    const printTierCost = operationItems
      .filter(item => item.pricingModel === "print_sheet_tier")
      .reduce((sum, item) => sum + item.total, 0);
    const printingCost = basePrintingCost + plateCost + directPrintTierCost + printTierCost;
    const operationsCost = operationItems
      .filter(item => item.pricingModel !== "print_sheet_tier")
      .reduce((sum, item) => sum + item.total, 0);
    const directCost = materialCost + printingCost + laborCost + machineCost + operationsCost;
    const overheadRate = rate(input.overheadRate || 0, "制造间接费率", { allowZero: true });
    const overheadCost = directCost * overheadRate;
    const totalCost = directCost + overheadCost;
    const sell = sellingPrice({
      totalCost,
      targetGrossMargin: input.targetGrossMargin || 0,
      taxRate: input.taxRate || 0,
      taxIncluded: Boolean(input.taxIncluded)
    });
    return Object.freeze({
      route,
      printingEnabled,
      quantity,
      layout,
      sheets,
      paper,
      impressions,
      plateCount,
      printSheetFormat: directPrintTier?.sheetFormat
        || operationItems.find(item => item.pricingModel === "print_sheet_tier")?.sheetFormat
        || null,
      printTier: directPrintTier ? Object.freeze({
        basisQuantity: directPrintTier.basisQuantity,
        unitPrice: directPrintTier.unitPrice,
        minimumCharge: directPrintTier.minimumCharge,
        includedQuantity: directPrintTier.includedQuantity,
        excessUnitPrice: directPrintTier.excessUnitPrice,
        thresholdQuantity: directPrintTier.thresholdQuantity,
        withinPrice: directPrintTier.withinPrice,
        aboveUnitPrice: directPrintTier.aboveUnitPrice,
        sideCount: directPrintTier.sideCount,
        total: directPrintTierCost
      }) : null,
      productionHours,
      costs: Object.freeze({ material: materialCost, printing: printingCost, labor: laborCost, machine: machineCost, operations: operationsCost, overhead: overheadCost, total: totalCost }),
      operationItems: Object.freeze(operationItems),
      sell,
      unitPrice: sell.total / quantity
    });
  }

  function calculateMaterialPurchaseCost(input) {
    const kind = String(input.kind || "piece");
    const basis = String(input.basis || (["weight", "area"].includes(kind) ? "dimensions" : "usage"));
    const purchaseUnit = String(input.purchaseUnit || "元/个");
    const purchasePrice = number(input.purchasePrice, "材料采购价");
    const quantity = number(input.quantity || 1, "数量");
    const utilizationRate = rate(input.utilizationRate, "材料利用率");
    const wasteRate = rate(input.wasteRate || 0, "材料损耗率", { allowZero: true });
    if (wasteRate >= 1) throw new Error("材料损耗率必须小于100%");
    let netUsagePerPiece;
    let usageUnit;
    if ((kind === "weight" || kind === "area") && basis === "dimensions") {
      const widthMm = number(input.widthMm, "展开宽");
      const heightMm = number(input.heightMm, "展开高");
      const areaM2 = widthMm / 1000 * (heightMm / 1000);
      if (kind === "weight") {
        const gsm = number(input.gsm, "材料克重");
        netUsagePerPiece = areaM2 * gsm / 1000;
        usageUnit = "kg";
      } else {
        netUsagePerPiece = areaM2;
        usageUnit = "㎡";
      }
    } else {
      netUsagePerPiece = number(input.usagePerPiece, "单件材料用量");
      usageUnit = kind === "weight" ? "kg" : kind === "area" ? "㎡" : kind === "meter" ? "米" : kind === "sheet" ? "张" : "个";
    }
    const grossUsagePerPiece = netUsagePerPiece / utilizationRate / (1 - wasteRate);
    const pricePerUsageUnit = purchaseUnit.includes("吨") ? purchasePrice / 1000 : purchasePrice;
    const materialUnitCost = grossUsagePerPiece * pricePerUsageUnit;
    return Object.freeze({
      kind,
      basis,
      purchaseUnit,
      purchasePrice,
      pricePerUsageUnit,
      quantity,
      utilizationRate,
      wasteRate,
      usageUnit,
      netUsagePerPiece,
      grossUsagePerPiece,
      totalUsage: grossUsagePerPiece * quantity,
      materialUnitCost,
      materialCost: materialUnitCost * quantity
    });
  }

  function parseBookPageCount(value) {
    const match = String(value ?? "").trim().match(/^(\d+)\s*(?:P|页)?$/i);
    if (!match) throw new Error("内页页数必须填写数字，例如 24P");
    const pages = integer(match[1], "内页页数");
    if (pages % 4 !== 0) throw new Error("书刊内页页数必须是4的倍数");
    return pages;
  }

  function bookPaperComponent(input) {
    const quantity = integer(input.quantity, "成品数量");
    const piecesPerProduct = number(input.piecesPerProduct, `${input.label}每本用纸`);
    const layout = bestSheetLayout({
      sheetWidthMm: input.sheetWidthMm,
      sheetHeightMm: input.sheetHeightMm,
      pieceWidthMm: input.pieceWidthMm,
      pieceHeightMm: input.pieceHeightMm,
      gripperMm: input.gripperMm || 0,
      sideMarginMm: input.sideMarginMm || 0,
      gapMm: input.gapMm || 0,
      allowRotate: input.allowRotate !== false
    });
    const forms = Math.max(1, Math.ceil(piecesPerProduct / layout.count));
    const perForm = productionSheets({
      quantity,
      piecesPerSheet: 1,
      setupWasteSheets: input.setupWasteSheets || 0,
      printWasteRate: input.printWasteRate || 0,
      finishingWasteRate: input.finishingWasteRate || 0
    });
    const netSheets = quantity * forms;
    const productionSheetCount = perForm.productionSheets * forms;
    const paper = paperWeightKg({
      sheetWidthMm: input.sheetWidthMm,
      sheetHeightMm: input.sheetHeightMm,
      gsm: input.gsm,
      sheets: productionSheetCount
    });
    const pricePerKg = number(input.materialPricePerKg, `${input.label}材料公斤价`);
    return Object.freeze({
      id: input.id,
      label: input.label,
      materialName: String(input.materialName || input.label),
      gsm: number(input.gsm, `${input.label}克重`),
      materialPricePerKg: pricePerKg,
      sheetWidthMm: number(input.sheetWidthMm, `${input.label}上机纸宽`),
      sheetHeightMm: number(input.sheetHeightMm, `${input.label}上机纸高`),
      layout,
      forms,
      piecesPerProduct,
      sheetsPerForm: perForm,
      netSheets,
      productionSheets: productionSheetCount,
      paper,
      materialCost: paper.weightKg * pricePerKg
    });
  }

  function bookPrintComponent(component, operation, input) {
    if (!operation || !operation.printSheetTierPricing) {
      throw new Error(`${component.label}印刷开数价尚未填写`);
    }
    const sideCount = integer(input.sideCount || 1, `${component.label}印刷面数`);
    const perFormTier = printSheetTierRate(operation, {
      printSheetWidthMm: component.sheetWidthMm,
      printSheetHeightMm: component.sheetHeightMm,
      printSheetFormat: input.printSheetFormat || "auto",
      printSheetStandard: input.printSheetStandard || "auto",
      printSheetQuantity: component.sheetsPerForm.productionSheets,
      printSideCount: sideCount
    });
    const perFormTotal = operationCharge({
      basisQuantity: perFormTier.basisQuantity,
      unitRate: perFormTier.unitRate,
      minimumCharge: perFormTier.minimumCharge,
      chargeMode: perFormTier.chargeMode,
      packageQuantity: perFormTier.includedQuantity,
      packageFee: perFormTier.packageFee
    });
    return Object.freeze({
      id: `${component.id}-printing`,
      name: `${component.label}${String(operation.name || "印刷")}`,
      basis: "sheet",
      basisQuantity: perFormTier.basisQuantity * component.forms,
      unitRate: perFormTier.unitRate,
      sheetFormat: perFormTier.sheetFormat,
      sideCount,
      forms: component.forms,
      perFormTotal,
      total: perFormTotal * component.forms
    });
  }

  function calculateBookQuote(input) {
    const quantity = integer(input.quantity, "成品数量");
    const pageCount = parseBookPageCount(input.pageCount);
    const finishedWidthMm = number(input.finishedWidthMm, "成品宽");
    const finishedHeightMm = number(input.finishedHeightMm, "成品高");
    const sheetWidthMm = number(input.sheetWidthMm, "上机纸宽");
    const sheetHeightMm = number(input.sheetHeightMm, "上机纸高");
    const shared = {
      quantity,
      gripperMm: input.gripperMm || 0,
      sideMarginMm: input.sideMarginMm || 0,
      gapMm: input.gapMm || 0,
      allowRotate: input.allowRotate !== false,
      setupWasteSheets: input.setupWasteSheets || 0,
      printWasteRate: input.printWasteRate || 0,
      finishingWasteRate: input.finishingWasteRate || 0
    };
    const cover = bookPaperComponent({
      ...shared, sheetWidthMm: input.coverSheetWidthMm || sheetWidthMm,
      sheetHeightMm: input.coverSheetHeightMm || sheetHeightMm,
      id: "cover", label: "封面", materialName: input.coverMaterialName,
      pieceWidthMm: finishedWidthMm * 2, pieceHeightMm: finishedHeightMm,
      piecesPerProduct: 1, gsm: input.coverGsm,
      materialPricePerKg: input.coverMaterialPricePerKg
    });
    const inner = bookPaperComponent({
      ...shared, sheetWidthMm: input.innerSheetWidthMm || sheetWidthMm,
      sheetHeightMm: input.innerSheetHeightMm || sheetHeightMm,
      id: "inner", label: `内页${pageCount}P`, materialName: input.innerMaterialName,
      pieceWidthMm: finishedWidthMm, pieceHeightMm: finishedHeightMm,
      piecesPerProduct: pageCount / 2, gsm: input.innerGsm,
      materialPricePerKg: input.innerMaterialPricePerKg
    });
    const coverPrint = bookPrintComponent(cover, input.printOperation, {
      ...shared, sideCount: input.coverPrintSideCount || 1,
      printSheetFormat: input.printSheetFormat,
      printSheetStandard: input.printSheetStandard
    });
    const innerPrint = bookPrintComponent(inner, input.printOperation, {
      ...shared, sideCount: input.innerPrintSideCount || 2,
      printSheetFormat: input.printSheetFormat,
      printSheetStandard: input.printSheetStandard
    });
    const bindingOperation = input.bindingOperation;
    if (!bindingOperation || !(Number(bindingOperation.unitRate) >= 0)) {
      throw new Error(`${String(input.bindingName || "装订")}价格尚未填写`);
    }
    const bindingBasisQuantity = operationBasisQuantity(
      { ...bindingOperation, basis: bindingOperation.basis || "book", usagePerPiece: bindingOperation.usagePerPiece ?? 1 },
      { quantity }
    );
    const bindingCost = operationCharge({ ...bindingOperation, basisQuantity: bindingBasisQuantity });
    const binding = Object.freeze({
      id: String(bindingOperation.id || "book-binding"),
      name: String(input.bindingName || bindingOperation.name || "装订"),
      basis: normalizeOperationBasis(bindingOperation.basis || "book"),
      basisQuantity: bindingBasisQuantity,
      unitRate: Number(bindingOperation.unitRate || 0),
      minimumCharge: Number(bindingOperation.minimumCharge || 0),
      total: bindingCost
    });
    const materialCost = cover.materialCost + inner.materialCost;
    const printingCost = coverPrint.total + innerPrint.total;
    const totalCost = materialCost + printingCost + bindingCost;
    return Object.freeze({
      family: "book",
      quantity,
      pageCount,
      finishedSize: Object.freeze({ widthMm: finishedWidthMm, heightMm: finishedHeightMm }),
      sheetSize: Object.freeze({ widthMm: sheetWidthMm, heightMm: sheetHeightMm }),
      components: Object.freeze({ cover, inner }),
      printItems: Object.freeze([coverPrint, innerPrint]),
      operationItems: Object.freeze([binding]),
      sheets: Object.freeze({
        netSheets: cover.netSheets + inner.netSheets,
        productionSheets: cover.productionSheets + inner.productionSheets
      }),
      costs: Object.freeze({
        material: materialCost,
        printing: printingCost,
        operations: bindingCost,
        labor: 0,
        machine: 0,
        total: totalCost
      }),
      unitCost: totalCost / quantity
    });
  }

  function calculateQuantityLadder(quantities, input) {
    if (!Array.isArray(quantities) || !quantities.length) throw new Error("数量阶梯不能为空");
    return quantities.map(quantity => calculateSheetQuote({ ...input, quantity }));
  }

  function profileRule(profileId) {
    return PROFILE_RULES[String(profileId || "")] || Object.freeze({ family: "manual", routes: Object.freeze(["fixed"]), requiredGroups: Object.freeze([]) });
  }

  return Object.freeze({
    routes: ROUTES,
    profileRules: PROFILE_RULES,
    operationBases: OPERATION_BASES,
    chargeModes: CHARGE_MODES,
    laminationSheetFormats: LAMINATION_SHEET_FORMATS,
    printSheetFormats: PRINT_SHEET_FORMATS,
    StructureTemplate: STRUCTURE_TEMPLATES,
    structureTemplates: STRUCTURE_TEMPLATES,
    profileRule,
    parseDimensions,
    estimateFlatSize,
    bestSheetLayout,
    productionSheets,
    paperWeightKg,
    normalizeOperationBasis,
    normalizeOperationPricing,
    operationBasisQuantity,
    operationCharge,
    resolveLaminationSheetFormat,
    laminationTierRate,
    resolvePrintSheetFormat,
    printSheetTierRate,
    sellingPrice,
    calculateMaterialPurchaseCost,
    parseBookPageCount,
    calculateBookQuote,
    calculateSheetQuote,
    calculateQuantityLadder
  });
});
