/* Gang-sheet builder. Given a set of selected artworks, pick a print size + qty
   for each, set horizontal/vertical margins and auto-rotate, see a live layout
   preview of the 57 cm roll, then generate one print-ready PNG (added back to the
   artwork list). Works for both order and draft artwork via an { type, id } owner.
   Talks to DTF.gangSheetPlan / DTF.gangSheetCreate (server does the packing). */

const GS_MM_PER_PX = 25.4 / 300;                 // px @300 DPI → mm
const gsRound = (n) => Math.round(n * 10) / 10;

// Quote band for a gang sheet is driven by AREA, not the outer rectangle: the roll
// is always 57 cm wide, so a bounding-box band just reports the width (a tiny 57×10cm
// sheet would read "A2"). Instead we measure film area and express it in A4-equivalents
// — an A4 is 0.0625 m², so 0.057 m² ≈ 0.9× A4. `gsAreaBand` also gives the nearest ISO
// band for reference. Used for pricing.
const GS_A4_M2 = 0.210 * 0.297;                                  // 0.06237 m²
const GS_A_AREAS = [                                             // [label, area m²]
  ["A0", 1.0], ["A1", 0.5], ["A2", 0.25], ["A3", 0.125], ["A4", GS_A4_M2],
  ["A5", GS_A4_M2 / 2], ["A6", GS_A4_M2 / 4], ["A7", GS_A4_M2 / 8],
];
function gsAreaBand(m2) {
  if (!(m2 > 0)) return null;
  if (m2 > 1.0 * Math.SQRT2) return ">A0";
  let best = GS_A_AREAS[0][0], bd = Infinity;
  for (const [label, a] of GS_A_AREAS) {                         // nearest in area (log scale)
    const d = Math.abs(Math.log(m2 / a));
    if (d < bd) { bd = d; best = label; }
  }
  return best;
}

// Website builder base — a CRM gang sheet also lands there as history (?gs=<hash>).
const GS_BUILDER_URL = "https://dtf.uk/products/build-a-dtf-gang-sheet-online";
function GangSheetBuilder({ owner, files, onDone, onCancel, customerId, initialRows }) {
  // Only raster images can be placed on a sheet, and they need pixel dims.
  const usable = (files || []).filter(f => /^image\//.test(f.contentType || "") && f.width && f.height);

  // Default sizing for an artwork: natural print size at 300 DPI, driven by width.
  const defRow = (f) => ({ dim: "w", mm: Math.max(10, gsRound((f.width || 0) * GS_MM_PER_PX)), qty: 1 });

  // Rows are a LIST (not a map): one artwork can appear on several rows so it can
  // be placed at multiple sizes. Each row = { rid, fid, dim:'w'|'h', mm, qty }.
  // When editing an existing gang sheet, `initialRows` pre-fills the exact sizes/qty.
  const ridRef = React.useRef(1);
  const seededRef = React.useRef(new Set());  // file ids that already got their initial row
  const [rows, setRows] = useState(() => {
    const arr = [];
    if (initialRows && initialRows.length) {
      for (const r of initialRows) {
        if (!usable.some(u => u.id === r.fid)) continue;      // source file missing from the order now
        arr.push({ rid: ridRef.current++, fid: r.fid, dim: r.dim || "w", mm: r.mm, qty: Number(r.qty) || 1 });
        seededRef.current.add(r.fid);
      }
      // Any remaining usable file (not in the saved layout) still gets a default row.
      for (const f of usable) if (!seededRef.current.has(f.id)) { arr.push({ rid: ridRef.current++, fid: f.id, ...defRow(f) }); seededRef.current.add(f.id); }
      return arr;
    }
    for (const f of usable) { arr.push({ rid: ridRef.current++, fid: f.id, ...defRow(f) }); seededRef.current.add(f.id); }
    return arr;
  });

  // Keep rows in sync as the usable set changes after mount: seed a row for any
  // newly-usable file (e.g. a freshly upscaled file only gains width/height once a
  // background refresh lands) and drop rows whose file left the selection. A file
  // seeded once is never auto-re-added, so removing all its rows sticks.
  useEffect(() => {
    const ids = new Set(usable.map(f => f.id));
    for (const id of [...seededRef.current]) if (!ids.has(id)) seededRef.current.delete(id);
    setRows(prev => {
      let next = prev.filter(r => ids.has(r.fid));
      let changed = next.length !== prev.length;
      for (const f of usable) if (!seededRef.current.has(f.id)) { next = [...next, { rid: ridRef.current++, fid: f.id, ...defRow(f) }]; seededRef.current.add(f.id); changed = true; }
      return changed ? next : prev;
    });
  }, [usable.map(f => f.id).join(",")]);
  const [marginH, setMarginH] = useState(10);
  const [marginV, setMarginV] = useState(10);
  const [rotate, setRotate] = useState(true);
  const [cutMode, setCutMode] = useState(false); // "prepare for cutting" grid layout
  const [burnLines, setBurnLines] = useState(false); // print the cut lines onto the sheet (UV/rigid)
  const [plan, setPlan] = useState(null);
  const [planning, setPlanning] = useState(false);
  const [err, setErr] = useState("");
  const [creating, setCreating] = useState(false);
  const [step, setStep] = useState("");
  const [result, setResult] = useState(null);  // { hash, previewUrl, designId } after a successful render
  const [zoom, setZoom] = useState(1);   // width multiplier: 1 = fill the column, >1 = wider (scrolls)
  const previewRef = React.useRef(null);

  const setRow = (rid, patch) => setRows(rs => rs.map(r => r.rid === rid ? { ...r, ...patch } : r));
  const duplicateRow = (rid) => setRows(rs => { const i = rs.findIndex(r => r.rid === rid); if (i < 0) return rs; const copy = { ...rs[i], rid: ridRef.current++ }; const next = rs.slice(); next.splice(i + 1, 0, copy); return next; });
  const removeRow = (rid) => setRows(rs => rs.filter(r => r.rid !== rid));
  const byId = useMemo(() => new Map(usable.map(f => [f.id, f])), [files]);
  const fidCount = useMemo(() => { const m = {}; for (const r of rows) m[r.fid] = (m[r.fid] || 0) + 1; return m; }, [rows]);

  // Build the items payload the server packs from — one entry per row (server
  // handles repeats of the same id and downloads each source only once).
  const items = useMemo(() => rows.map(r => ({ id: r.fid, dim: r.dim, mm: Number(r.mm) || 0, qty: Number(r.qty) || 1 })), [rows]);

  // Auto-repack (debounced) whenever sizes / margins / rotate change, so the
  // preview always reflects the current settings.
  useEffect(() => {
    let live = true;
    const t = setTimeout(async () => {
      if (!items.length || items.some(i => !(i.mm > 0))) { setPlan(null); return; }
      setPlanning(true); setErr("");
      try {
        const p = await DTF.gangSheetPlan(owner, items, { marginHmm: Number(marginH) || 0, marginVmm: Number(marginV) || 0, rotate, layout: cutMode ? "cut" : undefined });
        if (live) setPlan(p);
      } catch (e) { if (live) { setErr(e.message || "Couldn't lay out the sheet"); setPlan(null); } }
      finally { if (live) setPlanning(false); }
    }, 400);
    return () => { live = false; clearTimeout(t); };
  }, [items, marginH, marginV, rotate, cutMode]);

  async function proceed() {
    setCreating(true); setErr(""); setStep("queued…"); setResult(null);
    try {
      const file = await DTF.gangSheetCreate(owner, items, { marginHmm: Number(marginH) || 0, marginVmm: Number(marginV) || 0, rotate, layout: cutMode ? "cut" : undefined, burnCutLines: cutMode && burnLines, customerId }, setStep);
      if (file && file.hash) setResult({ hash: file.hash, previewUrl: file.previewUrl, designId: file.designId });
      onDone && onDone(file);
    } catch (e) { setErr(e.message || "Gang sheet failed"); }
    finally { setCreating(false); setStep(""); }
  }

  function otherDim(f, r) {
    const aspect = f.width / f.height;
    const mm = Number(r.mm) || 0;
    return r.dim === "h" ? gsRound(mm * aspect) : gsRound(mm / aspect); // the auto side
  }

  // How many copies of this artwork (at its current size) fill a 1-metre sheet:
  // pack as a grid on the 57cm roll, respecting margins (images flush to the edges,
  // margin only BETWEEN them), taking the better orientation when auto-rotate is on.
  function fillMetreQty(f, r) {
    const rollW = (plan && plan.sheetWidthMm) || 570;
    const targetLen = 1000; // 1 metre
    const mH = Math.max(0, Number(marginH) || 0), mV = Math.max(0, Number(marginV) || 0);
    const aspect = f.width / f.height;
    const mm = Number(r.mm) || 0; if (!(mm > 0)) return 1;
    const w0 = r.dim === "w" ? mm : gsRound(mm * aspect);
    const h0 = r.dim === "w" ? gsRound(mm / aspect) : mm;
    const per = (w, h) => {
      if (w > rollW + 0.01) return 0;
      const cols = Math.max(1, Math.floor((rollW + mH) / (w + mH)));
      const rowsN = Math.max(0, Math.floor((targetLen + mV) / (h + mV)));
      return cols * rowsN;
    };
    let best = per(w0, h0);
    if (rotate) best = Math.max(best, per(h0, w0));
    return Math.max(1, best);
  }

  if (!usable.length) return (
    <div className="card" style={{ marginTop: 12 }}><div className="card__body">
      <div className="dim">None of the selected files are images with a known pixel size, so they can't be placed on a gang sheet. Upload PNGs and try again.</div>
      <div style={{ marginTop: 10 }}><button className="btn btn--ghost btn--sm" onClick={onCancel}>Close</button></div>
    </div></div>
  );

  const warn = plan && (plan.tooLarge || plan.overLength);
  const canCreate = plan && !planning && !warn && !creating;

  return (
    <div className="card gs-card" style={{ marginTop: 12 }}>
      <div className="card__head"><Icon name="image" /><h3>Create gang sheet</h3>
        <span className="right dim">57 cm roll · 300 DPI</span></div>
      <div className="card__body">
        {/* Flex-wrap, NOT grid: the builder is embedded in narrow order/shop
            panels. Two columns (controls | preview) sit side by side when there's
            room and wrap to stacked when there isn't — flex items never overlap,
            and this is inline so it can't break if styles.css is cached stale.
            minWidth:0 lets each column shrink instead of pushing the other. */}
        <div className="gs-grid" style={{ display: "flex", flexWrap: "wrap", gap: 18, alignItems: "flex-start" }}>

          {/* ---- Left: per-artwork controls ---- */}
          <div style={{ flex: "1 1 300px", minWidth: 0 }}>
            <div className="gs-rows">
              {rows.map(r => {
                const f = byId.get(r.fid); if (!f) return null;
                return (
                  <div key={r.rid} className="gs-row" style={{ display: "grid", gridTemplateColumns: "44px 1fr", gap: 10, alignItems: "center", padding: "8px 0", borderBottom: "1px solid var(--line,#eee)" }}>
                    <img src={DTF.artThumbUrlOwner(owner, f.id, 88)} alt="" style={{ width: 44, height: 44, objectFit: "contain", background: "#fafafa", borderRadius: 6 }} />
                    <div>
                      <div className="art-name" style={{ fontSize: 12.5, marginBottom: 4, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }} title={f.filename}>{f.filename}</div>
                      <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
                        <div className="seg gs-seg">
                          <button className={r.dim === "w" ? "is-active" : ""} onClick={() => setRow(r.rid, { dim: "w" })} title="Set width, height follows aspect">W</button>
                          <button className={r.dim === "h" ? "is-active" : ""} onClick={() => setRow(r.rid, { dim: "h" })} title="Set height, width follows aspect">H</button>
                        </div>
                        <label style={{ display: "inline-flex", alignItems: "center", gap: 4 }}>
                          <input className="rm-in num" type="number" min="1" step="1" value={r.mm}
                            onChange={e => setRow(r.rid, { mm: e.target.value })} style={{ width: 68 }} />
                          <span className="dim mono" style={{ fontSize: 11 }}>mm</span>
                        </label>
                        <span className="dim mono" style={{ fontSize: 11 }}>→ {r.dim === "w" ? `${gsRound(Number(r.mm) || 0)}×${otherDim(f, r)}` : `${otherDim(f, r)}×${gsRound(Number(r.mm) || 0)}`} mm</span>
                        <label style={{ display: "inline-flex", alignItems: "center", gap: 4 }}>
                          <span className="dim" style={{ fontSize: 11 }}>×</span>
                          <input className="rm-in num" type="number" min="1" step="1" value={r.qty}
                            onChange={e => setRow(r.rid, { qty: e.target.value.replace(/[^\d]/g, "") || "1" })} style={{ width: 52 }} title="Quantity" />
                        </label>
                        <button className="btn btn--ghost btn--sm" style={{ padding: "2px 8px" }} onClick={() => setRow(r.rid, { qty: fillMetreQty(f, r) })} title="Set quantity to fill a 1-metre sheet with this artwork at this size (respects margins)">Fill 1 m</button>
                        <button className="btn btn--ghost btn--sm" style={{ padding: "2px 7px", marginLeft: "auto" }} onClick={() => duplicateRow(r.rid)} title="Add another size for this artwork">+ size</button>
                        <button className="btn btn--ghost btn--sm" style={{ padding: "2px 8px", color: "#c0392b" }} onClick={() => removeRow(r.rid)} title={fidCount[r.fid] > 1 ? "Remove this size" : "Remove this image from the gang sheet"}>✕</button>
                      </div>
                    </div>
                  </div>
                );
              })}
            </div>

            <div style={{ display: "flex", gap: 14, flexWrap: "wrap", marginTop: 14, alignItems: "flex-end" }}>
              <label className="out-field" style={{ margin: 0 }}><span>Horizontal margin (mm)</span>
                <input className="rm-in num" type="number" min="0" step="1" value={marginH} onChange={e => setMarginH(e.target.value)} style={{ width: 90 }} /></label>
              <label className="out-field" style={{ margin: 0 }}><span>Vertical margin (mm)</span>
                <input className="rm-in num" type="number" min="0" step="1" value={marginV} onChange={e => setMarginV(e.target.value)} style={{ width: 90 }} /></label>
              <label style={{ display: "inline-flex", alignItems: "center", gap: 7, paddingBottom: 6 }}>
                <input type="checkbox" checked={rotate} onChange={e => setRotate(e.target.checked)} />
                <span>Auto-rotate to save film</span>
              </label>
              <label style={{ display: "inline-flex", alignItems: "center", gap: 7, paddingBottom: 6 }} title="Grid pieces by size into straight rows & columns so each piece cuts out with straight lines">
                <input type="checkbox" checked={cutMode} onChange={e => setCutMode(e.target.checked)} />
                <span>Prepare for cutting</span>
              </label>
              {cutMode &&
                <label style={{ display: "inline-flex", alignItems: "center", gap: 7, paddingBottom: 6 }} title="Print the red cut lines into the output file. For UV / rigid media only — on DTF film these transfer onto the garment.">
                  <input type="checkbox" checked={burnLines} onChange={e => setBurnLines(e.target.checked)} />
                  <span>Print cut lines (UV/rigid)</span>
                </label>}
            </div>
            <div className="dim" style={{ fontSize: 11.5, marginTop: 6 }}>Tip: 10mm for DTF, 30mm for UV (easier cutting). Images sit flush to the roll edges — margin only separates them.{cutMode ? " Cutting mode lines up each size into straight rows & columns; red lines show where to cut (guide only — not printed)." : ""}</div>
          </div>

          {/* ---- Right: live preview ---- */}
          <div style={{ flex: "1 1 340px", minWidth: 0 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 6 }}>
              <span className="dim" style={{ fontSize: 11.5, fontWeight: 600 }}>Preview</span>
              {plan && <>
                <div style={{ display: "inline-flex", alignItems: "center", gap: 6, marginLeft: "auto" }}>
                  <button className="btn btn--ghost btn--sm" title="Zoom out" onClick={() => setZoom(z => Math.max(0.4, Math.round((z - 0.25) * 100) / 100))}>−</button>
                  <input type="range" min="0.4" max="4" step="0.1" value={zoom} onChange={e => setZoom(Number(e.target.value))} style={{ width: 100 }} />
                  <button className="btn btn--ghost btn--sm" title="Zoom in" onClick={() => setZoom(z => Math.min(4, Math.round((z + 0.25) * 100) / 100))}>+</button>
                  <button className="btn btn--ghost btn--sm" title="Fill the column width" onClick={() => setZoom(1)}>Fit width</button>
                  <button className="btn btn--ghost btn--sm" title="Fit the whole length in view"
                    onClick={() => { const el = previewRef.current; if (!el) return; const cw = el.clientWidth - 22, ch = el.clientHeight - 22; setZoom(Math.max(0.4, Math.min(4, (ch * plan.sheetWidthMm) / (Math.max(plan.lengthMm, 1) * cw)))); }}>Fit length</button>
                </div>
              </>}
            </div>
            <div ref={previewRef} className="gs-preview" style={{ position: "relative", background: "#f3f4f6", border: "1px solid var(--line,#e5e7eb)", borderRadius: 8, padding: 10, height: "70vh", minHeight: 380, overflow: "auto" }}>
              {planning && <div className="dim" style={{ position: "absolute", top: 8, right: 10, fontSize: 11, zIndex: 2, background: "rgba(255,255,255,.85)", padding: "2px 6px", borderRadius: 6 }}><span className="spin spin--dark" /> laying out…</div>}
              {plan
                ? <div style={{ width: (zoom * 100) + "%", margin: "0 auto" }}><GangSheetPreview owner={owner} plan={plan} /></div>
                : <div className="dim" style={{ padding: 20, textAlign: "center", fontSize: 12.5 }}>Set sizes to preview the layout.</div>}
            </div>
            {plan && (() => {
              // Whole-sheet quote figure: film area (57cm × length) expressed in
              // A4-equivalents, with the m² and nearest ISO band in the tooltip.
              const m2 = (plan.sheetWidthMm * plan.lengthMm) / 1e6;
              const a4s = m2 / GS_A4_M2;
              const band = gsAreaBand(m2);
              const tip = `Film ${plan.sheetWidthMm / 10}×${gsRound(plan.lengthMm / 10)}cm = ${m2.toFixed(3)} m² = ${a4s.toFixed(1)}× A4 (nearest ISO band ${band})`;
              return (
                <div className="mono dim" style={{ fontSize: 12, marginTop: 8, textAlign: "center" }}>
                  {plan.count} piece{plan.count === 1 ? "" : "s"} · {plan.sheetWidthMm / 10}cm × <b style={{ color: warn ? "#c0392b" : "inherit" }}>{plan.lengthM} m</b>
                  {" "}· <span style={{ fontWeight: 700, color: "#0a7a55" }} title={tip}>≈ {band}</span>
                  {" "}· {plan.megapixels} MP
                </div>
              );
            })()}
          </div>
        </div>

        {warn && <div className="warn-banner" style={{ marginTop: 12 }}><Icon name="spark" /> {plan.tooLarge ? `This sheet is ${plan.megapixels} MP (${plan.lengthM} m) — over the ${plan.maxMpx} MP limit. Reduce sizes or quantities, or split into two sheets.` : `Sheet would be ${plan.lengthM} m — over the maximum length.`}</div>}
        {err && <div className="warn-banner" style={{ marginTop: 12 }}><Icon name="spark" /> {err}</div>}
        {creating && <div className="dim" style={{ marginTop: 10, fontSize: 12.5 }}>Queued for background rendering — a big sheet can take a few minutes. You can keep working; it lands in the artwork panel when done.</div>}

        {result && (
          <div className="card" style={{ marginTop: 12, borderColor: "var(--mint)", background: "var(--mint-wash)" }}>
            <div className="card__body">
              <div style={{ fontWeight: 600, marginBottom: 6 }}><Icon name="check" /> Gang sheet created</div>
              <div style={{ display: "flex", gap: 14, flexWrap: "wrap", fontSize: 13 }}>
                <a href={result.previewUrl} target="_blank" rel="noopener noreferrer">Public preview ↗</a>
                <a href={GS_BUILDER_URL + "?gs=" + encodeURIComponent(result.hash)} target="_blank" rel="noopener noreferrer">Open in website builder ↗</a>
              </div>
              <div className="dim" style={{ fontSize: 11, marginTop: 6, fontFamily: "var(--font-mono)" }}>{result.hash}</div>
            </div>
          </div>
        )}

        <div style={{ display: "flex", gap: 10, alignItems: "center", marginTop: 14 }}>
          <button className="btn btn--primary" disabled={!canCreate} onClick={proceed}>
            {creating ? <><span className="spin" /> {step || "Creating…"}</> : "Proceed — create gang sheet"}
          </button>
          <button className="btn btn--ghost" onClick={onCancel} disabled={creating}>{result ? "Close" : "Cancel"}</button>
        </div>
      </div>
    </div>
  );
}

// The scaled SVG layout: the roll drawn at its true proportions with each placed
// artwork thumbnail in position (auto-rotated ones turned 90°, matching output).
function GangSheetPreview({ owner, plan }) {
  const W = plan.sheetWidthMm, L = Math.max(plan.lengthMm, 1);
  const stroke = Math.max(W / 500, 0.8);
  const chk = Math.max(4, Math.round(W / 76)); // checker square size in mm
  return (
    <svg viewBox={`0 0 ${W} ${L}`} width="100%" style={{ display: "block", width: "100%", height: "auto", aspectRatio: `${W} / ${L}`, borderRadius: 4, boxShadow: "0 0 0 1px rgba(0,0,0,.08)" }} preserveAspectRatio="xMidYMin meet">
      {/* Transparency checkerboard so the sheet reads as transparent film and you
          can see through the transparent parts of each placed artwork. */}
      <defs>
        <pattern id="gs-checker" width={chk * 2} height={chk * 2} patternUnits="userSpaceOnUse">
          <rect width={chk * 2} height={chk * 2} fill="#ffffff" />
          <rect width={chk} height={chk} fill="#d8dce0" />
          <rect x={chk} y={chk} width={chk} height={chk} fill="#d8dce0" />
        </pattern>
      </defs>
      <rect x="0" y="0" width={W} height={L} fill="url(#gs-checker)" />
      {plan.placements.map((p, i) => {
        // Use a downscaled PNG thumb (keeps transparency over the checkerboard).
        // The full-res print file can be hundreds of megapixels — big enough that
        // the browser silently fails to decode it inside an inline SVG <image>, so
        // the placement showed blank even though the server render was fine. A
        // ~1200px PNG thumb always renders; identical artworks share the URL.
        const href = DTF.artThumbUrlOwner(owner, p.id, 1200, "png");
        const fs = Math.max(3, Math.min(Math.min(p.wmm, p.hmm) * 0.16, 12));
        const showLabel = p.wmm > fs * 3.4 && p.hmm > fs * 1.4;
        return (
          <g key={i}>
            {p.rotated
              ? <image href={href} width={p.hmm} height={p.wmm} transform={`translate(${p.xmm + p.wmm} ${p.ymm}) rotate(90)`} preserveAspectRatio="none" />
              : <image href={href} x={p.xmm} y={p.ymm} width={p.wmm} height={p.hmm} preserveAspectRatio="none" />}
            <rect x={p.xmm} y={p.ymm} width={p.wmm} height={p.hmm} fill="none" stroke="rgba(24,244,175,.95)" strokeWidth={stroke} />
            {showLabel && (
              <text x={p.xmm + p.wmm / 2} y={p.ymm + p.hmm / 2} fontSize={fs} textAnchor="middle" dominantBaseline="central"
                fill="#0a0a0a" stroke="#fff" strokeWidth={fs * 0.14} paintOrder="stroke" style={{ fontFamily: "JetBrains Mono, monospace", fontWeight: 600 }}>
                {Math.round(p.wmm)}×{Math.round(p.hmm)}{p.rotated ? "↻" : ""}
              </text>
            )}
          </g>
        );
      })}
      {/* Cutting guide lines (red, dashed) — straight guillotine cuts between pieces. */}
      {(plan.cutLines || []).map((c, i) => (
        <line key={"cut" + i} x1={c.x1} y1={c.y1} x2={c.x2} y2={c.y2}
          stroke="rgba(224,32,32,.9)" strokeWidth={stroke * 1.3} strokeDasharray={`${stroke * 4} ${stroke * 3}`} />
      ))}
    </svg>
  );
}

window.GangSheetBuilder = GangSheetBuilder;
