(function (root) {
  "use strict";

  const INSTANCE_KEY = "__jianglaiImpositionWorkbench";
  const DEFAULT_SHEET = Object.freeze({
    widthMm: 787,
    heightMm: 1092,
    marginMm: 10,
    gripperMm: 12,
    gapXmm: 3,
    gapYmm: 3
  });
  const SKU_COLORS = Object.freeze([
    "#2563eb",
    "#0f9f6e",
    "#f59e0b",
    "#dc5a4f",
    "#8b5cf6",
    "#0891b2",
    "#db2777",
    "#65a30d"
  ]);

  function element(id, scope) {
    const owner = scope && typeof scope.getElementById === "function" ? scope : document;
    return owner.getElementById(id);
  }

  function firstElement(ids, scope) {
    for (const id of ids) {
      const found = element(id, scope);
      if (found) return found;
    }
    return null;
  }

  function text(value) {
    return String(value == null ? "" : value)
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#39;");
  }

  function xmlText(value) {
    return text(value).replace(/\r?\n/g, " ");
  }

  function finiteNumber(value, fallback) {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : fallback;
  }

  function positive(value, label, options) {
    const settings = options || {};
    if (value === null || value === undefined || String(value).trim() === "") {
      if (settings.optional) return null;
      throw new Error(`${label}还没有填写`);
    }
    const parsed = Number(value);
    const minimum = settings.allowZero ? 0 : 0.001;
    if (!Number.isFinite(parsed) || parsed < minimum) {
      throw new Error(`${label}必须是${settings.allowZero ? "不小于0" : "大于0"}的数字`);
    }
    if (settings.integer && !Number.isInteger(parsed)) {
      throw new Error(`${label}必须是整数`);
    }
    return parsed;
  }

  function percent(value) {
    return `${(finiteNumber(value, 0) * 100).toFixed(1)}%`;
  }

  function formatNumber(value, digits) {
    const number = finiteNumber(value, 0);
    return number.toLocaleString("zh-CN", {
      maximumFractionDigits: digits == null ? 2 : digits
    });
  }

  function hashColor(value) {
    const raw = String(value || "");
    let hash = 0;
    for (let index = 0; index < raw.length; index += 1) {
      hash = ((hash << 5) - hash + raw.charCodeAt(index)) | 0;
    }
    return SKU_COLORS[Math.abs(hash) % SKU_COLORS.length];
  }

  function button(label, className, id) {
    const node = document.createElement("button");
    node.type = "button";
    node.textContent = label;
    if (className) node.className = className;
    if (id) node.id = id;
    return node;
  }

  function downloadBlob(content, type, filename) {
    const blob = new Blob([content], { type });
    const url = URL.createObjectURL(blob);
    const anchor = document.createElement("a");
    anchor.href = url;
    anchor.download = filename;
    anchor.style.display = "none";
    document.body.appendChild(anchor);
    anchor.click();
    window.setTimeout(function () {
      anchor.remove();
      URL.revokeObjectURL(url);
    }, 0);
  }

  function safeFilename(value) {
    return String(value || "多订单混拼")
      .replace(/[\\/:*?"<>|\u0000-\u001f]/g, "-")
      .replace(/\s+/g, "-")
      .slice(0, 80) || "多订单混拼";
  }

  function csvCell(value) {
    let raw = String(value == null ? "" : value);
    if (/^[=+\-@\t\r]/.test(raw)) raw = `'${raw}`;
    return `"${raw.replace(/"/g, '""')}"`;
  }

  function createSkuRow(instance, values) {
    const settings = values || {};
    instance.rowSequence += 1;
    const row = document.createElement("article");
    row.className = "die-batch-sku-row";
    row.dataset.skuRow = "true";
    row.dataset.skuId = settings.id || `SKU-${String(instance.rowSequence).padStart(2, "0")}`;
    row.innerHTML = `
      <div class="die-batch-sku-row__number" aria-hidden="true">${instance.rowSequence}</div>
      <label class="die-batch-field die-batch-field--name">
        <span>产品 / 订单</span>
        <input data-field="name" type="text" maxlength="40" placeholder="例如：手提袋A款"
          value="${text(settings.name || "")}" autocomplete="off">
      </label>
      <label class="die-batch-field">
        <span>展开宽（mm）</span>
        <input data-field="widthMm" type="number" min="0.1" step="0.1" inputmode="decimal"
          placeholder="例如 210" value="${text(settings.widthMm || "")}">
      </label>
      <label class="die-batch-field">
        <span>展开高（mm）</span>
        <input data-field="heightMm" type="number" min="0.1" step="0.1" inputmode="decimal"
          placeholder="例如 148" value="${text(settings.heightMm || "")}">
      </label>
      <label class="die-batch-field">
        <span>需要数量</span>
        <input data-field="quantity" type="number" min="1" step="1" inputmode="numeric"
          placeholder="例如 3000" value="${text(settings.quantity || "")}">
      </label>
      <label class="die-batch-field">
        <span>纹向</span>
        <select data-field="grainDirection">
          <option value="free">不限纹向</option>
          <option value="width">沿展开宽</option>
          <option value="height">沿展开高</option>
          <option value="fixed">方向锁定</option>
        </select>
      </label>
      <label class="die-batch-field die-batch-field--priority">
        <span>生产优先级</span>
        <select data-field="priority">
          <option value="1">普通</option>
          <option value="2">优先</option>
          <option value="3">加急</option>
        </select>
      </label>
      <label class="die-batch-check">
        <input data-field="allowRotate" type="checkbox" ${settings.allowRotate === false ? "" : "checked"}>
        <span>允许旋转90°</span>
      </label>
      <button class="die-batch-row-remove" type="button" aria-label="删除这条SKU">删除</button>
    `;
    const grain = row.querySelector('[data-field="grainDirection"]');
    const priority = row.querySelector('[data-field="priority"]');
    grain.value = settings.grainDirection || "free";
    priority.value = String(settings.priority || 1);
    const rotation = row.querySelector('[data-field="allowRotate"]');
    function syncRotation() {
      const fixed = grain.value === "fixed";
      rotation.disabled = fixed;
      if (fixed) rotation.checked = false;
    }
    grain.addEventListener("change", syncRotation);
    syncRotation();
    row.querySelector(".die-batch-row-remove").addEventListener("click", function () {
      row.remove();
      renumberRows(instance);
      clearResults(instance, "已修改产品清单，请重新计算。");
      if (!instance.rows.querySelector("[data-sku-row]")) addSkuRow(instance);
    });
    row.querySelectorAll("input, select").forEach(function (control) {
      control.addEventListener("input", function () {
        clearResults(instance, "参数已修改，请重新计算最新方案。");
      });
      control.addEventListener("change", function () {
        clearResults(instance, "参数已修改，请重新计算最新方案。");
      });
    });
    return row;
  }

  function renumberRows(instance) {
    instance.rows.querySelectorAll("[data-sku-row]").forEach(function (row, index) {
      const number = row.querySelector(".die-batch-sku-row__number");
      if (number) number.textContent = String(index + 1);
    });
  }

  function addSkuRow(instance, values) {
    if (!instance || !instance.rows) return null;
    const row = createSkuRow(instance, values);
    instance.rows.appendChild(row);
    renumberRows(instance);
    return row;
  }

  function clearRows(instance) {
    while (instance.rows.firstChild) instance.rows.removeChild(instance.rows.firstChild);
    instance.rowSequence = 0;
  }

  function loadExample(instance) {
    clearRows(instance);
    addSkuRow(instance, {
      id: "ORDER-A",
      name: "订单A · 手提袋正面",
      widthMm: 210,
      heightMm: 148,
      quantity: 3000,
      allowRotate: true,
      grainDirection: "free",
      priority: 2
    });
    addSkuRow(instance, {
      id: "ORDER-B",
      name: "订单B · 礼盒围边",
      widthMm: 185,
      heightMm: 96,
      quantity: 1800,
      allowRotate: true,
      grainDirection: "width",
      priority: 1
    });
    addSkuRow(instance, {
      id: "ORDER-C",
      name: "订单C · 吊牌",
      widthMm: 90,
      heightMm: 54,
      quantity: 5000,
      allowRotate: true,
      grainDirection: "free",
      priority: 1
    });
    clearResults(instance, "示例已载入，点击“计算混拼方案”即可比较多种排版。");
  }

  function valueOf(ids, fallback) {
    const node = firstElement(ids);
    if (!node || String(node.value || "").trim() === "") return fallback;
    return node.value;
  }

  function readInput(instance) {
    const rows = Array.from(instance.rows.querySelectorAll("[data-sku-row]"));
    if (!rows.length) throw new Error("请先增加至少一个产品或订单");
    const usedIds = new Set();
    const skus = rows.map(function (row, index) {
      const read = function (name) {
        const control = row.querySelector(`[data-field="${name}"]`);
        return control ? control.value : "";
      };
      const name = read("name").trim() || `SKU ${index + 1}`;
      let id = String(row.dataset.skuId || `SKU-${index + 1}`).trim();
      if (usedIds.has(id)) id = `${id}-${index + 1}`;
      usedIds.add(id);
      return {
        id,
        name,
        widthMm: positive(read("widthMm"), `第${index + 1}项展开宽`),
        heightMm: positive(read("heightMm"), `第${index + 1}项展开高`),
        quantity: positive(read("quantity"), `第${index + 1}项数量`, { integer: true }),
        grainDirection: read("grainDirection") || "free",
        priority: positive(read("priority") || 1, `第${index + 1}项优先级`),
        allowRotate: Boolean(row.querySelector('[data-field="allowRotate"]')?.checked)
      };
    });
    const maxSheetsRaw = valueOf(["dieBatchMaxSheets"], "");
    return {
      sheet: {
        widthMm: positive(
          valueOf(["dieBatchBoardWidth", "dieBatchSheetWidth"], DEFAULT_SHEET.widthMm),
          "上机纸宽度"
        ),
        heightMm: positive(
          valueOf(["dieBatchBoardHeight", "dieBatchSheetHeight"], DEFAULT_SHEET.heightMm),
          "上机纸高度"
        ),
        grainDirection: valueOf(["dieBatchSheetGrain"], "free")
      },
      marginsMm: positive(
        valueOf(["dieBatchMargin", "dieBatchMargins"], DEFAULT_SHEET.marginMm),
        "安全边距",
        { allowZero: true }
      ),
      gripperMm: positive(
        valueOf(["dieBatchGripper"], DEFAULT_SHEET.gripperMm),
        "咬口",
        { allowZero: true }
      ),
      gripperEdge: valueOf(["dieBatchGripperEdge"], "top"),
      gapMm: {
        x: positive(
          valueOf(["dieBatchGapX"], DEFAULT_SHEET.gapXmm),
          "横向间隙",
          { allowZero: true }
        ),
        y: positive(
          valueOf(["dieBatchGapY"], DEFAULT_SHEET.gapYmm),
          "纵向间隙",
          { allowZero: true }
        )
      },
      maxSheets: maxSheetsRaw === ""
        ? null
        : positive(maxSheetsRaw, "最大生产张数", { integer: true }),
      skus
    };
  }

  function setVerification(instance, kind, title, details) {
    if (!instance.verification) return;
    instance.verification.dataset.state = kind;
    const detailList = Array.isArray(details) ? details.filter(Boolean) : [];
    instance.verification.innerHTML = `
      <div class="die-batch-verification__icon" aria-hidden="true">${
        kind === "success" ? "✓" : kind === "error" ? "!" : "i"
      }</div>
      <div>
        <strong>${text(title)}</strong>
        ${detailList.length
          ? `<ul>${detailList.map(item => `<li>${text(item)}</li>`).join("")}</ul>`
          : ""}
      </div>
    `;
  }

  function clearResults(instance, message) {
    instance.result = null;
    instance.candidate = null;
    instance.patternIndex = 0;
    instance.currentSvg = "";
    if (instance.candidates) instance.candidates.innerHTML = "";
    if (instance.metrics) {
      instance.metrics.innerHTML = '<div class="die-batch-empty">计算后会在这里显示张数、利用率、浪费和每个SKU的交付情况。</div>';
    }
    if (instance.preview) {
      instance.preview.innerHTML = '<div class="die-batch-preview-empty">版面预览将在计算完成后显示。</div>';
    }
    setVerification(instance, "info", message || "填写参数后开始计算。");
    syncExportButtons(instance);
  }

  function renderCandidates(instance) {
    if (!instance.candidates || !instance.result) return;
    instance.candidates.innerHTML = "";
    instance.result.candidates.forEach(function (candidate) {
      const node = button("", "die-batch-candidate");
      node.dataset.candidateId = candidate.candidateId;
      node.setAttribute("aria-pressed", candidate === instance.candidate ? "true" : "false");
      node.innerHTML = `
        <span class="die-batch-candidate__rank">${candidate.recommended ? "推荐" : `方案 ${candidate.rank}`}</span>
        <strong>${text(candidate.heuristicLabel)}</strong>
        <span>${formatNumber(candidate.productionSheets, 0)}张 · 利用率${percent(candidate.utilizationRate)}</span>
      `;
      node.addEventListener("click", function () {
        instance.candidate = candidate;
        instance.patternIndex = 0;
        renderAllResults(instance);
      });
      instance.candidates.appendChild(node);
    });
  }

  function renderMetrics(instance) {
    if (!instance.metrics || !instance.candidate || !instance.result) return;
    const candidate = instance.candidate;
    const baseline = instance.result.separateBaseline || {};
    const overrun = candidate.perSku.reduce(function (sum, item) {
      return sum + finiteNumber(item.overrun, 0);
    }, 0);
    instance.metrics.innerHTML = `
      <div class="die-batch-metric">
        <span>生产张数</span>
        <strong>${formatNumber(candidate.productionSheets, 0)}<small> 张</small></strong>
      </div>
      <div class="die-batch-metric die-batch-metric--good">
        <span>综合利用率</span>
        <strong>${percent(candidate.utilizationRate)}</strong>
      </div>
      <div class="die-batch-metric">
        <span>空白浪费</span>
        <strong>${percent(candidate.wasteRate)}</strong>
      </div>
      <div class="die-batch-metric">
        <span>跨SKU混拼</span>
        <strong>${formatNumber(candidate.mixedSheetCount, 0)}<small> 张</small></strong>
      </div>
      <div class="die-batch-metric ${candidate.shortageTotal ? "die-batch-metric--danger" : ""}">
        <span>欠产数量</span>
        <strong>${formatNumber(candidate.shortageTotal, 0)}</strong>
      </div>
      <div class="die-batch-metric ${overrun ? "die-batch-metric--warn" : ""}">
        <span>超产数量</span>
        <strong>${formatNumber(overrun, 0)}</strong>
      </div>
      <div class="die-batch-metric die-batch-metric--saving">
        <span>比各自单排节省</span>
        <strong>${formatNumber(baseline.sheetSavings, 0)}<small> 张</small></strong>
      </div>
      <div class="die-batch-sku-results">
        <div class="die-batch-sku-results__head">
          <span>产品 / 订单</span><span>需求</span><span>计划</span><span>短缺</span><span>完成率</span>
        </div>
        ${candidate.perSku.map(function (item) {
          return `
            <div class="die-batch-sku-result">
              <strong title="${text(item.skuName)}">${text(item.skuName)}</strong>
              <span>${formatNumber(item.targetQuantity, 0)}</span>
              <span>${formatNumber(item.plannedOutput, 0)}</span>
              <span class="${item.shortage ? "is-shortage" : ""}">${formatNumber(item.shortage, 0)}</span>
              <span>${percent(item.completionRate)}</span>
            </div>
          `;
        }).join("")}
      </div>
    `;
  }

  function buildPatternSvg(instance, group) {
    const input = instance.result.input;
    const sheet = input.sheet;
    const width = finiteNumber(sheet.widthMm, 1);
    const height = finiteNumber(sheet.heightMm, 1);
    const printableX = finiteNumber(sheet.printableXmm, 0);
    const printableY = finiteNumber(sheet.printableYmm, 0);
    const printableWidth = finiteNumber(sheet.printableWidthMm, width);
    const printableHeight = finiteNumber(sheet.printableHeightMm, height);
    const fontSize = Math.max(6, Math.min(width, height) / 38);
    const strokeWidth = Math.max(0.4, Math.min(width, height) / 800);
    const placements = Array.isArray(group.placements) ? group.placements : [];
    const pieces = placements.map(function (placement, index) {
      const fill = hashColor(placement.skuId);
      const x = finiteNumber(placement.xMm, 0);
      const y = finiteNumber(placement.yMm, 0);
      const pieceWidth = finiteNumber(placement.widthMm, 0);
      const pieceHeight = finiteNumber(placement.heightMm, 0);
      const label = String(placement.skuName || placement.skuId || index + 1);
      const compactLabel = label.length > 12 ? `${label.slice(0, 11)}…` : label;
      const canLabel = pieceWidth >= fontSize * 2.2 && pieceHeight >= fontSize * 1.35;
      return `
        <g class="die-batch-svg-piece" data-sku-id="${text(placement.skuId)}">
          <title>${xmlText(label)} · ${pieceWidth}×${pieceHeight}mm · 旋转${placement.rotation || 0}°</title>
          <rect x="${x}" y="${y}" width="${pieceWidth}" height="${pieceHeight}"
            rx="${Math.min(1.8, pieceWidth / 10, pieceHeight / 10)}"
            fill="${fill}" fill-opacity="0.78" stroke="${fill}" stroke-width="${strokeWidth}" />
          ${canLabel ? `
            <text x="${x + pieceWidth / 2}" y="${y + pieceHeight / 2}"
              text-anchor="middle" dominant-baseline="middle" font-size="${fontSize}"
              fill="#ffffff" font-weight="700">${xmlText(compactLabel)}</text>
          ` : ""}
        </g>
      `;
    }).join("");
    const gripperEdge = sheet.gripperEdge || "top";
    const gripperMm = finiteNumber(sheet.gripperMm, 0);
    let gripper = "";
    if (gripperMm > 0) {
      if (gripperEdge === "bottom") {
        gripper = `<rect x="0" y="${height - gripperMm}" width="${width}" height="${gripperMm}" class="die-batch-svg-gripper"/>`;
      } else if (gripperEdge === "left") {
        gripper = `<rect x="0" y="0" width="${gripperMm}" height="${height}" class="die-batch-svg-gripper"/>`;
      } else if (gripperEdge === "right") {
        gripper = `<rect x="${width - gripperMm}" y="0" width="${gripperMm}" height="${height}" class="die-batch-svg-gripper"/>`;
      } else {
        gripper = `<rect x="0" y="0" width="${width}" height="${gripperMm}" class="die-batch-svg-gripper"/>`;
      }
    }
    return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}mm" height="${height}mm"
  role="img" aria-label="${xmlText(group.patternId)}拼版图">
  <style>
    .die-batch-svg-sheet{fill:#fff;stroke:#94a3b8;stroke-width:${strokeWidth * 1.3}}
    .die-batch-svg-printable{fill:#f8fafc;stroke:#2563eb;stroke-width:${strokeWidth};stroke-dasharray:${strokeWidth * 6} ${strokeWidth * 4}}
    .die-batch-svg-gripper{fill:#f59e0b;fill-opacity:.18}
  </style>
  <rect x="0" y="0" width="${width}" height="${height}" class="die-batch-svg-sheet"/>
  ${gripper}
  <rect x="${printableX}" y="${printableY}" width="${printableWidth}" height="${printableHeight}"
    class="die-batch-svg-printable"/>
  ${pieces}
</svg>`;
  }

  function renderPreview(instance) {
    if (!instance.preview || !instance.candidate || !instance.result) return;
    const groups = instance.candidate.sheetGroups || [];
    if (!groups.length) {
      instance.currentSvg = "";
      instance.preview.innerHTML = '<div class="die-batch-preview-empty">当前候选方案没有可预览的版式。</div>';
      return;
    }
    instance.patternIndex = Math.max(0, Math.min(instance.patternIndex, groups.length - 1));
    const group = groups[instance.patternIndex];
    instance.currentSvg = buildPatternSvg(instance, group);
    const svgWithoutDeclaration = instance.currentSvg.replace(/^<\?xml[^>]*>\s*/i, "");
    const skuIds = Object.keys(group.countsBySku || {});
    instance.preview.innerHTML = `
      <div class="die-batch-pattern-toolbar">
        <div class="die-batch-pattern-tabs" role="tablist" aria-label="版式">
          ${groups.map(function (item, index) {
            return `
              <button type="button" role="tab" data-pattern-index="${index}"
                aria-selected="${index === instance.patternIndex ? "true" : "false"}">
                ${text(item.patternId)} <small>×${formatNumber(item.repeat, 0)}张</small>
              </button>
            `;
          }).join("")}
        </div>
        <div class="die-batch-pattern-summary">
          每张 ${formatNumber(group.piecesPerSheet, 0)} 件 · 此版生产 ${formatNumber(group.repeat, 0)} 张 ·
          利用率 ${percent(group.utilizationRate)}
        </div>
      </div>
      <div class="die-batch-svg-stage">${svgWithoutDeclaration}</div>
      <div class="die-batch-preview-legend">
        ${skuIds.map(function (id) {
          const sku = instance.result.input.skus.find(item => item.id === id);
          return `
            <span><i style="--sku-color:${hashColor(id)}"></i>${text(sku ? sku.name : id)}
              <small>${formatNumber(group.countsBySku[id], 0)}件/张</small></span>
          `;
        }).join("")}
      </div>
      <p class="die-batch-pattern-explanation">${text(group.explanation)}</p>
    `;
    instance.preview.querySelectorAll("[data-pattern-index]").forEach(function (node) {
      node.addEventListener("click", function () {
        instance.patternIndex = Number(node.dataset.patternIndex) || 0;
        renderPreview(instance);
        syncExportButtons(instance);
      });
    });
  }

  function verifyCandidate(instance) {
    const engine = root.JianglaiImpositionEngine;
    if (!engine || typeof engine.verifyPlan !== "function" || !instance.result || !instance.candidate) {
      setVerification(instance, "error", "无法校验当前方案", ["拼版校验引擎尚未就绪。"]);
      return;
    }
    const verification = engine.verifyPlan({
      input: instance.result.input,
      recommended: instance.candidate
    });
    const shortages = instance.candidate.perSku
      .filter(item => item.shortage > 0)
      .map(item => `${item.skuName}还短缺${formatNumber(item.shortage, 0)}件`);
    if (!verification.valid) {
      setVerification(instance, "error", "版面校验未通过，请勿下单", verification.errors);
    } else if (shortages.length) {
      setVerification(instance, "warning", "版面位置校验通过，但存在欠产", shortages);
    } else {
      setVerification(instance, "success", "版面边界、间隙和订单数量校验通过", [
        `${instance.candidate.patternCount}种版式均位于可印区域内，没有重叠。`,
        `全部${formatNumber(instance.candidate.targetTotal, 0)}件已纳入生产计划。`
      ]);
    }
  }

  function renderAllResults(instance) {
    renderCandidates(instance);
    renderMetrics(instance);
    renderPreview(instance);
    verifyCandidate(instance);
    syncExportButtons(instance);
  }

  function calculate(instance) {
    const engine = root.JianglaiImpositionEngine;
    if (!engine || typeof engine.optimize !== "function") {
      setVerification(instance, "error", "多订单混拼引擎没有加载", [
        "请刷新页面；如果仍然出现，请检查 imposition_engine.js 是否已随应用发布。"
      ]);
      return;
    }
    let input;
    try {
      input = readInput(instance);
    } catch (error) {
      setVerification(instance, "error", "请先补全拼版参数", [error.message || String(error)]);
      return;
    }
    const calculateButton = instance.calculate;
    if (calculateButton) {
      calculateButton.disabled = true;
      calculateButton.dataset.originalText = calculateButton.textContent;
      calculateButton.textContent = "正在比较多种方案…";
    }
    setVerification(instance, "info", "正在计算多种混拼策略，请稍候。");
    window.setTimeout(function () {
      try {
        instance.result = engine.optimize(input);
        instance.candidate = instance.result.recommended || instance.result.candidates?.[0] || null;
        instance.patternIndex = 0;
        renderAllResults(instance);
        if (instance.metrics && typeof instance.metrics.scrollIntoView === "function") {
          instance.metrics.scrollIntoView({ behavior: "smooth", block: "nearest" });
        }
      } catch (error) {
        clearResults(instance);
        setVerification(instance, "error", "暂时无法生成混拼方案", [
          error && error.message ? error.message : String(error),
          "已填写的数据仍然保留，可修改尺寸、纹向、纸张或数量后直接重算。"
        ]);
      } finally {
        if (calculateButton) {
          calculateButton.disabled = false;
          calculateButton.textContent = calculateButton.dataset.originalText || "计算混拼方案";
        }
      }
    }, 24);
  }

  function exportSvg(instance) {
    if (!instance.currentSvg || !instance.candidate) return;
    const group = instance.candidate.sheetGroups[instance.patternIndex];
    const filename = `${safeFilename(group.patternId)}-${safeFilename(instance.candidate.heuristicLabel)}.svg`;
    downloadBlob(instance.currentSvg, "image/svg+xml;charset=utf-8", filename);
  }

  function exportCsv(instance) {
    if (!instance.candidate || !instance.result) return;
    const rows = [[
      "候选方案", "策略", "版式", "生产张数", "SKU编号", "产品/订单",
      "每张数量", "位置序号", "X(mm)", "Y(mm)", "宽(mm)", "高(mm)", "旋转角度"
    ]];
    instance.candidate.sheetGroups.forEach(function (group) {
      group.placements.forEach(function (placement, index) {
        rows.push([
          instance.candidate.candidateId,
          instance.candidate.heuristicLabel,
          group.patternId,
          group.repeat,
          placement.skuId,
          placement.skuName,
          group.countsBySku[placement.skuId] || 0,
          index + 1,
          placement.xMm,
          placement.yMm,
          placement.widthMm,
          placement.heightMm,
          placement.rotation || 0
        ]);
      });
    });
    rows.push([]);
    rows.push(["SKU汇总", "", "", "", "SKU编号", "产品/订单", "需求", "计划", "短缺", "超产", "完成率"]);
    instance.candidate.perSku.forEach(function (item) {
      rows.push([
        "", "", "", "", item.skuId, item.skuName, item.targetQuantity,
        item.plannedOutput, item.shortage, item.overrun, percent(item.completionRate)
      ]);
    });
    const csv = `\uFEFF${rows.map(row => row.map(csvCell).join(",")).join("\r\n")}`;
    downloadBlob(
      csv,
      "text/csv;charset=utf-8",
      `${safeFilename(instance.candidate.heuristicLabel)}-混拼生产清单.csv`
    );
  }

  function ensureOptionalControls(instance) {
    let example = firstElement(["dieBatchLoadExample", "dieBatchExample", "dieBatchDemo"]);
    if (!example && instance.add && instance.add.parentElement) {
      example = button("载入双订单示例", "die-batch-secondary-action", "dieBatchLoadExample");
      instance.add.parentElement.insertBefore(example, instance.calculate || null);
    }
    instance.example = example;

    let svg = element("dieBatchDownloadSvg");
    let csv = element("dieBatchDownloadCsv");
    if ((!svg || !csv) && instance.workspace) {
      let actions = element("dieBatchExportActions");
      if (!actions) {
        actions = document.createElement("div");
        actions.id = "dieBatchExportActions";
        actions.className = "die-batch-export-actions";
        const title = document.createElement("span");
        title.textContent = "生产文件";
        actions.appendChild(title);
        (instance.verification?.parentElement || instance.workspace).appendChild(actions);
      }
      if (!svg) {
        svg = button("下载当前版式 SVG", "die-batch-secondary-action", "dieBatchDownloadSvg");
        actions.appendChild(svg);
      }
      if (!csv) {
        csv = button("下载生产清单 CSV", "die-batch-secondary-action", "dieBatchDownloadCsv");
        actions.appendChild(csv);
      }
    }
    instance.downloadSvg = svg;
    instance.downloadCsv = csv;
  }

  function syncExportButtons(instance) {
    if (instance.downloadSvg) instance.downloadSvg.disabled = !instance.currentSvg;
    if (instance.downloadCsv) instance.downloadCsv.disabled = !instance.candidate;
  }

  function bindSheetFields(instance) {
    [
      "dieBatchBoardWidth", "dieBatchSheetWidth", "dieBatchBoardHeight", "dieBatchSheetHeight",
      "dieBatchMargin", "dieBatchMargins", "dieBatchGripper", "dieBatchGripperEdge",
      "dieBatchGapX", "dieBatchGapY", "dieBatchSheetGrain", "dieBatchMaxSheets"
    ].forEach(function (id) {
      const control = element(id);
      if (!control || control.dataset.impositionBound === "true") return;
      control.dataset.impositionBound = "true";
      const clear = function () {
        clearResults(instance, "纸张参数已修改，请重新计算。");
      };
      control.addEventListener("input", clear);
      control.addEventListener("change", clear);
    });
  }

  function initMultiOrderImpositionWorkbench(scope) {
    if (typeof document === "undefined") return null;
    const owner = scope && typeof scope.querySelector === "function" ? scope : document;
    const workspace = owner.querySelector("#dieBatchWorkspace") || element("dieBatchWorkspace");
    if (!workspace) return null;
    if (workspace[INSTANCE_KEY]) return workspace[INSTANCE_KEY];
    const rows = owner.querySelector("#dieBatchSkuRows") || element("dieBatchSkuRows");
    if (!rows) return null;
    const instance = {
      workspace,
      rows,
      add: owner.querySelector("#dieBatchAddSku") || element("dieBatchAddSku"),
      calculate: owner.querySelector("#dieBatchCalculate") || element("dieBatchCalculate"),
      candidates: owner.querySelector("#dieBatchCandidateList") || element("dieBatchCandidateList"),
      metrics: owner.querySelector("#dieBatchMetrics") || element("dieBatchMetrics"),
      preview: owner.querySelector("#dieBatchPreview") || element("dieBatchPreview"),
      verification: owner.querySelector("#dieBatchVerification") || element("dieBatchVerification"),
      result: null,
      candidate: null,
      patternIndex: 0,
      currentSvg: "",
      rowSequence: 0
    };
    workspace[INSTANCE_KEY] = instance;
    ensureOptionalControls(instance);
    if (instance.add) {
      instance.add.addEventListener("click", function () {
        addSkuRow(instance);
        clearResults(instance, "已增加一项，请填写展开尺寸和数量。");
        const last = instance.rows.querySelector("[data-sku-row]:last-child input");
        if (last) last.focus();
      });
    }
    if (instance.example) instance.example.addEventListener("click", () => loadExample(instance));
    if (instance.calculate) instance.calculate.addEventListener("click", () => calculate(instance));
    if (instance.downloadSvg) instance.downloadSvg.addEventListener("click", () => exportSvg(instance));
    if (instance.downloadCsv) instance.downloadCsv.addEventListener("click", () => exportCsv(instance));
    bindSheetFields(instance);
    if (!rows.querySelector("[data-sku-row]")) addSkuRow(instance);
    clearResults(instance, "可同时加入多个订单或SKU，系统会比较多种混拼方案。");
    return instance;
  }

  root.initMultiOrderImpositionWorkbench = initMultiOrderImpositionWorkbench;
  root.JianglaiImpositionWorkbench = Object.freeze({
    init: initMultiOrderImpositionWorkbench
  });

  if (typeof document !== "undefined") {
    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", function () {
        initMultiOrderImpositionWorkbench();
      }, { once: true });
    } else {
      initMultiOrderImpositionWorkbench();
    }
  }
})(typeof globalThis !== "undefined" ? globalThis : this);
