// screens/upload.jsx — Quote configurator, card-based 3-column layout.
// Left: price + 3D viewer with analysis. Middle: process / materials /
// notes accordions. Right: cart rail with live draft item + totals.

// The four families we stock. `available: false` = listed but awaiting
// stock — shown greyed-out and unselectable until filament arrives.
const UPLOAD_MATERIALS = [
  { code: "PETG Premium", density: 1.27, perGram: 0.13, type: "PETG", tag: "Tough, water-tight", available: true  },
  { code: "PLA Premium",  density: 1.24, perGram: 0.10, type: "PLA",  tag: "Workhorse",          available: false },
  { code: "ASA Premium",  density: 1.07, perGram: 0.15, type: "ASA",  tag: "UV-stable",          available: false },
  { code: "TPU 95A",      density: 1.21, perGram: 0.18, type: "TPU",  tag: "Flexible",           available: false },
];

const PROCESSES = [
  { id: "FDM", label: "FDM", desc: "Fused filament", available: true },
  { id: "SLA", label: "SLA", desc: "Resin · soon", available: false },
  { id: "SLS", label: "SLS", desc: "Sintering · soon", available: false },
];

// stock: 0 = shown greyed-out (coming soon) and unselectable.
// Currently only black PETG is on the shelf.
const FILAMENTS = [
  { id: "black",     hex: "#1d1d1f", name: "Black",     stock: 2400 },
  { id: "white",     hex: "#f2f0ea", name: "White",     stock: 0 },
  { id: "grey",      hex: "#9a9da2", name: "Grey",      stock: 0 },
  { id: "blue",      hex: "#2456c4", name: "Blue",      stock: 0 },
  { id: "red",       hex: "#c43a2e", name: "Red",       stock: 0 },
  { id: "yellow",    hex: "#e3b93a", name: "Yellow",    stock: 0 },
  { id: "green",     hex: "#3d7a4e", name: "Green",     stock: 0 },
  { id: "orange",    hex: "#d76b2a", name: "Orange",    stock: 0 },
];

const LAYER_OPTIONS = [
  { v: "0.28", l: "Draft (0.28mm)" },
  { v: "0.20", l: "Standard (0.2mm)" },
  { v: "0.16", l: "Quality (0.16mm)" },
  { v: "0.12", l: "Fine (0.12mm)" },
  { v: "0.08", l: "Ultra (0.08mm)" },
];

const INFILL_OPTIONS = [5, 10, 15, 20, 25, 30, 40, 50, 75, 100];

const LEAD_TIMES = [
  { id: "standard", label: "Standard : 3 Days", mult: 1.0 },
  { id: "priority", label: "Priority : Next day", mult: 1.35 },
  { id: "express",  label: "Express : Same day", mult: 1.85 },
];

const track = (e, p) => window.syncAnalytics && window.syncAnalytics.track(e, p);

function UploadScreen({ cart = [], removeCartItem, onAddToCart, onPlaceOrder, preloadBuffer, preloadName, onNav, brand }) {
  const [stage, setStage] = React.useState("drop");
  const [progress, setProgress] = React.useState(0);
  const [error, setError] = React.useState("");
  const [file, setFile] = React.useState(null);
  const [viewerKey, setViewerKey] = React.useState(0);
  const [units, setUnits] = React.useState("mm");
  const [parseKind, setParseKind] = React.useState("stl");

  // config
  const [proc, setProc] = React.useState("FDM");
  const [matCode, setMatCode] = React.useState("PETG Premium");
  const [filament, setFilament] = React.useState(FILAMENTS[0]);
  const [layer, setLayer] = React.useState("0.20");
  const [infill, setInfill] = React.useState(20);
  const [quantity, setQuantity] = React.useState(1);
  const [leadTime, setLeadTime] = React.useState("standard");
  const [matSearch, setMatSearch] = React.useState("");
  const [notes, setNotes] = React.useState("");
  const [open, setOpen] = React.useState({ process: true, materials: true, notes: false });

  const material = UPLOAD_MATERIALS.find(m => m.code === matCode) || UPLOAD_MATERIALS[0];
  const leadOpt = LEAD_TIMES.find(l => l.id === leadTime) || LEAD_TIMES[0];

  const fileInputRef = React.useRef(null);
  const addInputRef = React.useRef(null);
  const [dragOver, setDragOver] = React.useState(false);

  // ── parse ────────────────────────────────────────────────────────────────
  // Accepts STL natively; 3MF is unzipped + XML-parsed; STEP is tessellated
  // by the lazily-loaded OpenCascade WASM kernel. Non-STL formats are
  // converted to binary STL so the viewer / analysis / cart reuse one path.
  async function handleArrayBuffer(arrayBuffer, name) {
    const ext = ((name.match(/\.(\w+)$/) || [])[1] || "stl").toLowerCase();
    const kind = ext === "3mf" ? "3mf" : (ext === "step" || ext === "stp") ? "step" : "stl";
    setParseKind(kind);
    setStage("parsing");
    setProgress(8);
    track("upload_parse_started", { name, kind });
    const tick = (p, ms = 60) => new Promise(r => { setProgress(p); setTimeout(r, ms); });
    try {
      let geo, stlBuffer;
      if (kind === "step") {
        await tick(12);
        await SyncSTL.loadOcct();            // lazy CAD kernel (~one-time)
        await tick(40);
        geo = await SyncSTL.parseSTEP(arrayBuffer);
        await tick(60);
        stlBuffer = SyncSTL.geometryToSTL(geo);
      } else if (kind === "3mf") {
        await tick(20);
        geo = SyncSTL.parse3MF(arrayBuffer);
        await tick(55);
        stlBuffer = SyncSTL.geometryToSTL(geo);
      } else {
        await tick(20);
        geo = SyncSTL.parse(arrayBuffer);
        stlBuffer = arrayBuffer;
      }
      await tick(70);
      geo.computeBoundingBox();
      const bb = geo.boundingBox;
      const dims = [
        +(bb.max.x - bb.min.x).toFixed(1),
        +(bb.max.y - bb.min.y).toFixed(1),
        +(bb.max.z - bb.min.z).toFixed(1),
      ];
      const triangles = Math.round(geo.attributes.position.count / 3);
      const volMm3 = SyncSTL.volume(geo);
      await tick(84);
      const analysis = SyncSTL.analyze(geo, { orient: "zup", overhangAngleDeg: 45 });
      setProgress(95);
      geo.dispose();
      const meta = { name, arrayBuffer: stlBuffer, dims, triangles, volMm3, volCm3: volMm3 / 1000, analysis, sourceFormat: kind };
      track("upload_parse_ok", { name, kind, triangles, volCm3: +(volMm3 / 1000).toFixed(1), watertight: analysis.watertight });
      setTimeout(() => { setFile(meta); setStage("configure"); setProgress(100); }, 240);
    } catch (e) {
      console.error(e);
      track("upload_parse_failed", { name, kind });
      setError(
        kind === "step"
          ? "STEP import needs the CAD kernel and it couldn't load or read this file. Try again, or request a manual quote via Contact."
          : kind === "3mf"
          ? "Couldn't read a mesh out of that 3MF."
          : "Couldn't parse that file as STL."
      );
      setStage("drop");
    }
  }

  function handleFile(f) {
    if (!f) return;
    if (!/\.(stl|3mf|step|stp)$/i.test(f.name)) { setError("Supported formats: STL, 3MF, STEP."); return; }
    setError("");
    const reader = new FileReader();
    reader.onerror = () => setError("Couldn't read that file.");
    reader.onload = () => handleArrayBuffer(reader.result, f.name);
    reader.readAsArrayBuffer(f);
  }

  React.useEffect(() => {
    if (preloadBuffer && stage === "drop") {
      handleArrayBuffer(preloadBuffer, preloadName || "preview.stl");
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [preloadBuffer]);

  // ── pricing ────────────────────────────────────────────────────────────
  const slice = file ? SyncSTL.estimateSlice(
    file,
    { layerHeight: layer, infillPct: infill, walls: 3, nozzleWidth: 0.4, topBottomLayers: 4, supportsEnabled: file.analysis.needsSupport },
    material
  ) : null;

  const mass = slice?.massG ?? 0;
  const printHours = slice?.printHours ?? 0;
  // Full cost-model quote (no VAT — not VAT registered). Support material and
  // its print time are already inside `slice`; we never surface it separately.
  const q = slice ? SyncSTL.quote(slice, material.type, quantity) : null;
  const draftEx = q ? q.total * leadOpt.mult : 0;
  const draftInc = draftEx; // alias kept for existing JSX; no VAT applied

  const cartTotalInc = cart.reduce((a, b) => a + (b.subtotal || 0), 0);
  const grandInc = cartTotalInc + (file ? draftInc : 0);

  // ── cart ops ───────────────────────────────────────────────────────────
  function buildDraftItem() {
    return {
      file, filament, material, layer, infill, quantity,
      supports: file.analysis.needsSupport,
      leadTime: leadOpt, notes,
      unitPrice: draftInc / Math.max(1, quantity),
      subtotal: draftInc,
      analysis: file.analysis,
    };
  }
  function commitDraft() {
    if (!file) return;
    onAddToCart(buildDraftItem());
    track("added_to_cart", { name: file.name, value: +draftInc.toFixed(2), qty: quantity, material: material.code });
    setFile(null);
    setNotes("");
  }
  function addAnotherFile(f) {
    if (!f) return;
    if (file) commitDraft();
    handleFile(f);
  }
  function placeOrder() {
    if (file) commitDraft();
    track("place_order_clicked", { value: +grandInc.toFixed(2), items: cart.length + (file ? 1 : 0) });
    onPlaceOrder && onPlaceOrder();
  }

  // ── DROP STAGE ─────────────────────────────────────────────────────────
  if (stage === "drop") {
    return (
      <main style={{ padding: "var(--pad-y) var(--pad-x)" }}>
        <div style={{ maxWidth: 860, margin: "0 auto", textAlign: "center" }}>
          <div className="chip" style={{ marginBottom: 18 }}>Instant quote · no signup</div>
          <h1 className="serif" style={{ fontSize: "clamp(34px, 4.6vw, 56px)", lineHeight: 1.06, margin: 0, letterSpacing: "-0.03em" }}>
            Upload your model, see the price.
          </h1>
          <p style={{ maxWidth: 560, margin: "16px auto 0", fontSize: 16, color: "var(--ink-soft)" }}>
            Parsed locally in your browser — your file never leaves your machine
            until you place the order.
          </p>
        </div>

        <div
          className="card"
          role="button"
          aria-label="Browse for a 3D file"
          onClick={() => fileInputRef.current?.click()}
          onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
          onDragLeave={() => setDragOver(false)}
          onDrop={(e) => { e.preventDefault(); setDragOver(false); handleFile(e.dataTransfer.files?.[0]); }}
          style={{
            maxWidth: 860, margin: "28px auto 0",
            padding: "clamp(48px,7vw,96px) 24px",
            textAlign: "center",
            cursor: "pointer",
            display: "flex", flexDirection: "column", alignItems: "center", gap: 16,
            borderStyle: "dashed",
            borderColor: dragOver ? "var(--primary)" : "var(--line)",
            background: dragOver ? "var(--primary-soft)" : "var(--card)",
            transition: "all .15s",
          }}>
          <div className="sync-pulse" style={{
            width: 60, height: 60, borderRadius: 999,
            background: "var(--primary)", color: "#fff",
            display: "grid", placeItems: "center",
          }}>
            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M12 17V5M6 11l6-6 6 6" /><path d="M4 20h16" />
            </svg>
          </div>
          <div className="serif" style={{ fontSize: 28, letterSpacing: "-0.02em" }}>Drop your 3D file here</div>
          <div className="mono upper" style={{ color: "var(--muted)" }}>STL · 3MF · STEP · ≤ 50MB · parsed locally</div>
          <input ref={fileInputRef} type="file" accept=".stl,.3mf,.step,.stp" style={{ display: "none" }}
            onClick={(e) => e.stopPropagation()}
            onChange={(e) => handleFile(e.target.files?.[0])} />
          <div style={{ display: "flex", gap: 10, marginTop: 8, flexWrap: "wrap", justifyContent: "center" }}>
            <button onClick={(e) => { e.stopPropagation(); fileInputRef.current?.click(); }} className="btn-primary" data-track="quote_browse_click">Browse for a file</button>
            <button onClick={(e) => { e.stopPropagation(); handleArrayBuffer(SyncSTL.buildCubeSTL(20), "calibration_cube_20mm.stl"); }} className="btn-outline" data-track="quote_sample_click">Try a 20mm cube</button>
          </div>
          {error && <div className="mono" style={{ color: "#b3261e", marginTop: 6, fontSize: 12 }}>{error}</div>}
        </div>

        <div className="mono" style={{ maxWidth: 860, margin: "14px auto 0", color: "var(--muted)", fontSize: 11, textAlign: "center" }}>
          By uploading you confirm you own the model or have rights to print it.
        </div>
        <div style={{ height: "var(--pad-y)" }} />
        <Footer brand={brand} onNav={onNav} />
        <FloatingCTA onUpload={() => fileInputRef.current?.click()} />
      </main>
    );
  }

  // ── PARSING STAGE ──────────────────────────────────────────────────────
  if (stage === "parsing") {
    return (
      <main style={{ padding: "var(--pad-y) var(--pad-x)" }}>
        <div className="card" style={{ maxWidth: 720, margin: "0 auto", padding: 36 }}>
          <div className="mono upper" style={{ color: "var(--muted)", marginBottom: 18 }}>
            Parsing · running geometry analysis
          </div>
          <div className="serif" style={{ fontSize: 34, letterSpacing: "-0.025em", lineHeight: 1.1 }}>
            {({
              stl:  ["Reading triangles…", "Computing volume…", "Checking watertight & overhangs…"],
              "3mf": ["Unzipping 3MF…", "Reading mesh XML…", "Checking watertight & overhangs…"],
              step: ["Loading CAD kernel…", "Tessellating B-rep surfaces…", "Checking watertight & overhangs…"],
            }[parseKind] || [])[progress < 50 ? 0 : progress < 80 ? 1 : 2]}
          </div>
          <div className="mono" style={{ marginTop: 22, color: "var(--ink-soft)", display: "flex", justifyContent: "space-between", fontSize: 12 }}>
            <span>{progress < 30 ? "parsing" : progress < 60 ? "bounding box" : progress < 90 ? "overhang scan" : "final checks"}</span>
            <span>{Math.round(progress)}%</span>
          </div>
          <div style={{ marginTop: 8, height: 6, background: "var(--primary-soft)", borderRadius: 4, overflow: "hidden" }}>
            <div style={{ width: progress + "%", height: "100%", background: "var(--primary)", transition: "width 0.2s", borderRadius: 4 }} />
          </div>
        </div>
      </main>
    );
  }

  // ── CONFIGURE STAGE ────────────────────────────────────────────────────
  const filteredMats = UPLOAD_MATERIALS.filter(m =>
    !matSearch ||
    m.code.toLowerCase().includes(matSearch.toLowerCase()) ||
    m.type.toLowerCase().includes(matSearch.toLowerCase())
  );
  const dimsLabel = units === "mm"
    ? file.dims.map(d => d.toFixed(1)).join(" × ") + " mm"
    : file.dims.map(d => (d / 25.4).toFixed(2)).join(" × ") + " in";

  return (
    <main style={{ padding: "calc(var(--pad-y) * 0.4) var(--pad-x) var(--pad-y)" }}>
      <button onClick={() => { setFile(null); setStage("drop"); }} data-track="quote_go_back" style={{
        border: "none", background: "transparent", cursor: "pointer",
        display: "inline-flex", alignItems: "center", gap: 8,
        color: "var(--primary)", fontSize: 14.5, fontWeight: 600,
        padding: "8px 0", marginBottom: 12, whiteSpace: "nowrap",
      }}>
        <span style={{ fontSize: 18 }}>←</span> Go back
      </button>

      <div style={{
        display: "grid",
        gridTemplateColumns: "1.05fr 1fr 0.95fr",
        gap: 20,
        alignItems: "start",
      }}>
        {/* ── LEFT — price + viewer ─────────────────────────────────── */}
        <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
          <section className="card" style={{ padding: 22 }}>
            <div className="serif" style={{ fontSize: 17, letterSpacing: "-0.01em", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
              {file.name}
            </div>
            <div className="mono upper" style={{ color: "var(--muted)", marginTop: 6, fontSize: 10 }}>Price · no VAT</div>
            <div style={{ display: "flex", alignItems: "baseline", gap: 10, marginTop: 4 }}>
              <div className="serif" style={{ fontSize: 44, letterSpacing: "-0.03em", lineHeight: 1, color: "var(--success)", fontVariantNumeric: "tabular-nums" }}>
                £{draftEx.toFixed(2)}
              </div>
              <CheckDot ok={file.analysis.watertight} />
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 12, marginTop: 14, flexWrap: "wrap" }}>
              <span className="mono" style={{ fontSize: 13, color: "var(--ink-soft)" }}>£{(draftEx / Math.max(1, quantity)).toFixed(2)}/Unit</span>
              <Stepper value={quantity} onChange={(v) => setQuantity(v)} min={1} max={500} />
            </div>
            <div className="mono" style={{ display: "flex", gap: 6, marginTop: 12, flexWrap: "wrap" }}>
              <span style={{ background: "color-mix(in srgb, var(--primary) 10%, transparent)", color: "var(--primary)", padding: "5px 9px", borderRadius: 6, fontSize: 11.5, fontWeight: 600 }}>
                {mass < 1 ? mass.toFixed(2) : mass.toFixed(1)} g filament
              </span>
              <span style={{ background: "color-mix(in srgb, var(--ink) 6%, transparent)", color: "var(--ink-soft)", padding: "5px 9px", borderRadius: 6, fontSize: 11.5 }}>
                {(mass * quantity).toFixed(mass * quantity < 10 ? 1 : 0)} g total · {(mass * quantity / 1000).toFixed(2)} kg
              </span>
              <span style={{ background: "color-mix(in srgb, var(--ink) 6%, transparent)", color: "var(--ink-soft)", padding: "5px 9px", borderRadius: 6, fontSize: 11.5 }}>
                ≈ {formatFilamentLength(mass * quantity, material.density)} of 1.75mm
              </span>
            </div>
            <select className="sel" value={leadTime} onChange={(e) => { setLeadTime(e.target.value); track("lead_time_changed", { lead: e.target.value }); }} style={{ marginTop: 14 }}>
              {LEAD_TIMES.map(o => <option key={o.id} value={o.id}>{o.label}</option>)}
            </select>
          </section>

          <section className="card" style={{ position: "relative", overflow: "hidden" }}>
            <div style={{ aspectRatio: "1 / 1.02", position: "relative" }}>
              <SyncSTL.Viewer
                key={viewerKey}
                arrayBuffer={file.arrayBuffer}
                color={filament.hex}
                showSupports={file.analysis.needsSupport}
                supportColor="#9ec8e2"
              />
              {/* volume + units */}
              <div style={{ position: "absolute", top: 12, left: 12, display: "flex", flexDirection: "column", gap: 8 }}>
                <span className="mono" style={{ fontSize: 11, color: "var(--ink-soft)", background: "color-mix(in srgb, var(--card) 85%, transparent)", padding: "4px 8px", borderRadius: 6, backdropFilter: "blur(6px)" }}>
                  Volume: {file.volMm3.toFixed(0)} mm³
                </span>
                <select className="sel" value={units} onChange={(e) => setUnits(e.target.value)} style={{ width: 84, padding: "6px 26px 6px 10px", fontSize: 12.5 }}>
                  <option value="mm">mm</option>
                  <option value="inch">inch</option>
                </select>
              </div>
              {/* analysis */}
              <div style={{ position: "absolute", top: 12, right: 12, textAlign: "right" }}>
                <AnalysisMini file={file} />
              </div>
              {/* colour list */}
              <div className="card" style={{
                position: "absolute", left: 12, top: "37%",
                padding: 6, display: "flex", flexDirection: "column", gap: 2,
                boxShadow: "var(--shadow-pop)",
              }}>
                {FILAMENTS.map(f => {
                  const oos = f.stock === 0;
                  const active = filament.id === f.id;
                  return (
                    <button key={f.id} disabled={oos} onClick={() => { setFilament(f); track("colour_selected", { colour: f.name }); }} style={{
                      border: "none", appearance: "none", font: "inherit",
                      cursor: oos ? "not-allowed" : "pointer",
                      display: "flex", alignItems: "center", gap: 8,
                      padding: "5px 10px 5px 6px", borderRadius: 8,
                      background: active ? "var(--primary-soft)" : "transparent",
                      color: active ? "var(--primary)" : "var(--ink-soft)",
                      opacity: oos ? 0.4 : 1,
                      fontSize: 12.5, fontWeight: active ? 600 : 500,
                    }}>
                      <span style={{
                        width: 16, height: 16, borderRadius: 999, background: f.hex,
                        boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.12)",
                      }} />
                      {f.name}
                    </button>
                  );
                })}
              </div>
              {/* bottom toolbar */}
              <div style={{
                position: "absolute", bottom: 10, left: 12, right: 12,
                display: "flex", justifyContent: "space-between", alignItems: "center",
              }}>
                <button className="btn-outline" onClick={() => setViewerKey(k => k + 1)} style={{ padding: "7px 14px", fontSize: 12.5 }}>
                  ⌖ Center
                </button>
                <span className="mono" style={{ fontSize: 11, color: "var(--muted)" }}>{dimsLabel} · drag to rotate</span>
              </div>
            </div>
          </section>
        </div>

        {/* ── MIDDLE — accordions ───────────────────────────────────── */}
        <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
          <Accordion
            title="Select Process"
            done
            open={open.process}
            onToggle={() => setOpen(o => ({ ...o, process: !o.process }))}
          >
            <div className="keep-cols" style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 10 }}>
              {PROCESSES.map(p => {
                const active = proc === p.id;
                return (
                  <button key={p.id} disabled={!p.available} onClick={() => setProc(p.id)} style={{
                    appearance: "none", font: "inherit", cursor: p.available ? "pointer" : "not-allowed",
                    padding: "14px 10px", borderRadius: 10, textAlign: "center",
                    border: active ? "1.5px solid var(--primary)" : "1px solid var(--line)",
                    background: active ? "var(--primary-soft)" : "var(--card)",
                    color: active ? "var(--primary)" : "var(--ink)",
                    opacity: p.available ? 1 : 0.45,
                  }}>
                    <div style={{ fontWeight: 700, fontSize: 15 }}>{p.label}</div>
                    <div className="mono" style={{ fontSize: 10.5, marginTop: 3, opacity: 0.7 }}>{p.desc}</div>
                  </button>
                );
              })}
            </div>
          </Accordion>

          <Accordion
            title="Capabilities and Materials"
            done
            open={open.materials}
            onToggle={() => setOpen(o => ({ ...o, materials: !o.materials }))}
          >
            <div style={{ position: "relative" }}>
              <input className="inp" value={matSearch} onChange={(e) => setMatSearch(e.target.value)}
                placeholder={`Search material (${UPLOAD_MATERIALS.length})`} style={{ paddingRight: 38 }} />
              <svg width="16" height="16" viewBox="0 0 20 20" fill="none" stroke="var(--muted)" strokeWidth="1.8" strokeLinecap="round"
                style={{ position: "absolute", right: 13, top: "50%", transform: "translateY(-50%)" }}>
                <circle cx="9" cy="9" r="6" /><path d="M13.5 13.5 17 17" />
              </svg>
            </div>
            <div style={{ marginTop: 10, maxHeight: 236, overflowY: "auto", border: "1px solid var(--line)", borderRadius: 10 }}>
              {filteredMats.map((m, i) => {
                const active = m.code === matCode;
                const oos = m.available === false;
                return (
                  <button key={m.code} disabled={oos} onClick={() => { setMatCode(m.code); track("material_selected", { material: m.code }); }} title={`${m.tag} · ${m.density} g/cm³${oos ? " · awaiting stock" : ""}`} style={{
                    border: "none", appearance: "none", font: "inherit", cursor: oos ? "not-allowed" : "pointer",
                    display: "flex", width: "100%", boxSizing: "border-box",
                    padding: "11px 14px",
                    background: active ? "var(--primary)" : "var(--card)",
                    color: active ? "#fff" : "var(--ink)",
                    opacity: oos ? 0.45 : 1,
                    borderBottom: i < filteredMats.length - 1 ? "1px solid var(--line)" : "none",
                    justifyContent: "space-between", alignItems: "center", gap: 14,
                  }}>
                    <span style={{ fontSize: 13.5, fontWeight: active ? 700 : 500 }}>{m.code}</span>
                    <span style={{ display: "inline-flex", alignItems: "center", gap: 10 }}>
                      {oos ? (
                        <span className="mono upper" style={{ fontSize: 9.5, opacity: 0.8 }}>Awaiting stock</span>
                      ) : (
                        <span className="mono" style={{ fontSize: 11, opacity: active ? 0.9 : 0.55 }}>£{(SyncSTL.filamentPerGram(m.type) * SyncSTL.PRICING.markup).toFixed(2)}/g</span>
                      )}
                      <span aria-hidden style={{
                        width: 16, height: 16, borderRadius: 999, display: "inline-grid", placeItems: "center",
                        background: active ? "rgba(255,255,255,.25)" : "var(--primary-soft)",
                        color: active ? "#fff" : "var(--primary)",
                        fontSize: 10, fontWeight: 700, fontStyle: "italic", fontFamily: "Georgia, serif",
                      }}>i</span>
                    </span>
                  </button>
                );
              })}
              {filteredMats.length === 0 && (
                <div className="mono" style={{ padding: 16, color: "var(--muted)", fontSize: 12 }}>No matches</div>
              )}
            </div>

            <FieldRow label="Infill" hint="structural density">
              <select className="sel" value={infill} onChange={(e) => setInfill(parseInt(e.target.value))}>
                {INFILL_OPTIONS.map(v => <option key={v} value={v}>{v === 20 ? "Standard (20%)" : v + "%"}</option>)}
              </select>
            </FieldRow>
            <FieldRow label="Layer height" hint="finer = slower, prettier">
              <select className="sel" value={layer} onChange={(e) => setLayer(e.target.value)}>
                {LAYER_OPTIONS.map(o => <option key={o.v} value={o.v}>{o.l}</option>)}
              </select>
            </FieldRow>
          </Accordion>

          <Accordion
            title="Technical Drawings and Notes"
            open={open.notes}
            onToggle={() => setOpen(o => ({ ...o, notes: !o.notes }))}
          >
            <textarea className="inp" rows={5} value={notes} onChange={(e) => setNotes(e.target.value)}
              placeholder="Tolerances, threaded inserts, finish, drawing references…"
              style={{ resize: "vertical", fontFamily: "var(--sans)", lineHeight: 1.5 }} />
            <div className="mono" style={{ marginTop: 8, color: "var(--muted)", fontSize: 11 }}>
              Notes travel with the order — our operator reads every one.
            </div>
          </Accordion>
        </div>

        {/* ── RIGHT — cart rail ─────────────────────────────────────── */}
        <div style={{ display: "flex", flexDirection: "column", gap: 14, position: "sticky", top: 76 }}>
          <section className="card" style={{ padding: 18 }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 14 }}>
              <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
                <span className="serif" style={{ fontSize: 19 }}>My cart</span>
                <span className="mono" style={{ fontSize: 12, color: "var(--muted)" }}>{cart.length + 1} item{cart.length ? "s" : ""}</span>
              </div>
              <button className="btn-outline" onClick={() => addInputRef.current?.click()} data-track="quote_add_files" style={{ padding: "8px 12px", fontSize: 12.5 }}>
                Add Files
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M12 17V5M6 11l6-6 6 6" />
                </svg>
              </button>
              <input ref={addInputRef} type="file" accept=".stl,.3mf,.step,.stp" style={{ display: "none" }}
                onChange={(e) => { addAnotherFile(e.target.files?.[0]); e.target.value = ""; }} />
            </div>

            {/* live draft item */}
            <CartItemCard
              live
              name={file.name}
              dims={dimsLabel}
              price={draftInc}
              qty={quantity}
              onQty={setQuantity}
              filament={filament}
              onFilament={(id) => { const f = FILAMENTS.find(x => x.id === id); if (f) setFilament(f); }}
              materialLabel={material.code}
            />

            {/* committed items */}
            {cart.map((it, i) => (
              <CartItemCard
                key={i}
                name={it.file.name}
                dims={(it.file.dims || []).join(" × ") + " mm"}
                price={it.subtotal}
                qty={it.quantity}
                filament={it.filament}
                materialLabel={it.material?.code}
                onRemove={() => removeCartItem && removeCartItem(i)}
              />
            ))}

            {/* drop mini-zone */}
            <div
              onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
              onDragLeave={() => setDragOver(false)}
              onDrop={(e) => { e.preventDefault(); setDragOver(false); addAnotherFile(e.dataTransfer.files?.[0]); }}
              style={{
                marginTop: 12, padding: "18px 12px",
                border: `1.5px dashed ${dragOver ? "var(--primary)" : "var(--line)"}`,
                borderRadius: 10, textAlign: "center",
                background: dragOver ? "var(--primary-soft)" : "transparent",
                transition: "all .15s",
              }}>
              <div className="mono" style={{ fontSize: 11.5, color: "var(--muted)" }}>
                ⤒ Drag and drop your file here
              </div>
            </div>
          </section>

          <section className="card" style={{ padding: 20 }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline" }}>
              <span className="serif" style={{ fontSize: 19 }}>Total Price</span>
              <span className="serif" style={{ fontSize: 26, color: "var(--primary)", fontVariantNumeric: "tabular-nums" }}>£{grandInc.toFixed(2)}</span>
            </div>
            <div className="mono upper" style={{ color: "var(--muted)", fontSize: 10, textAlign: "right", marginTop: 4 }}>No VAT · not registered</div>
            <button className="btn-outline" data-track="manual_quote_click" onClick={() => onNav && onNav("contact", { subject: "quote" })}
              style={{ width: "100%", marginTop: 16 }}>
              Request Manual Quote
            </button>
            <button className="btn-primary" data-track="place_order_click" onClick={placeOrder} style={{ width: "100%", marginTop: 10 }}>
              <span>Place Order</span>
              <span style={{
                width: 22, height: 22, borderRadius: 999, background: "rgba(255,255,255,.22)",
                display: "inline-grid", placeItems: "center", fontSize: 13,
              }}>→</span>
            </button>
            <div className="mono" style={{ marginTop: 12, color: "var(--muted)", fontSize: 10.5, textAlign: "center" }}>
              {formatHours(printHours)} print · dispatch {leadOpt.label.split(" : ")[1]?.toLowerCase() || "in 3 days"} · London UK
            </div>
          </section>
        </div>
      </div>
      <div style={{ height: "var(--pad-y)" }} />
      <Footer brand={brand} onNav={onNav} />
    </main>
  );
}

// ── sub-components ─────────────────────────────────────────────────────────
function Accordion({ title, done, open, onToggle, children }) {
  return (
    <section className="card" style={{ overflow: "hidden" }}>
      <button onClick={onToggle} style={{
        border: "none", appearance: "none", font: "inherit", cursor: "pointer",
        width: "100%", display: "flex", alignItems: "center", gap: 12,
        padding: "16px 18px", background: "transparent", textAlign: "left",
      }}>
        {done ? (
          <span style={{ width: 22, height: 22, borderRadius: 999, background: "#e8f6ee", display: "grid", placeItems: "center", flexShrink: 0 }}>
            <svg width="12" height="12" viewBox="0 0 14 14"><path d="M3 7.2 5.8 10 11 4.2" fill="none" stroke="var(--success)" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" /></svg>
          </span>
        ) : (
          <span style={{ width: 22, height: 22, borderRadius: 999, background: "var(--bg)", display: "grid", placeItems: "center", flexShrink: 0, color: "var(--muted)", fontSize: 14, fontWeight: 600 }}>+</span>
        )}
        <span style={{ fontWeight: 700, fontSize: 15, color: "var(--ink)", flex: 1 }}>{title}</span>
        <svg width="12" height="8" viewBox="0 0 12 8" style={{ transform: open ? "rotate(180deg)" : "none", transition: "transform .18s", opacity: 0.4 }}>
          <path d="M1 1l5 5 5-5" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </button>
      {open && <div style={{ padding: "2px 18px 18px" }}>{children}</div>}
    </section>
  );
}

function FieldRow({ label, hint, children }) {
  return (
    <div style={{ marginTop: 14 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 7 }}>
        <span style={{ fontSize: 13.5, fontWeight: 600 }}>{label}</span>
        {hint && <span className="mono" style={{ fontSize: 10.5, color: "var(--muted)" }}>{hint}</span>}
      </div>
      {children}
    </div>
  );
}

function CartItemCard({ live, name, dims, price, qty, onQty, filament, onFilament, materialLabel, onRemove }) {
  return (
    <article style={{
      border: live ? "1.5px solid var(--primary)" : "1px solid var(--line)",
      borderRadius: 12, padding: 12, marginTop: 10,
      background: live ? "var(--card)" : "var(--card)",
    }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
        <span style={{
          border: "1.5px solid var(--primary)", color: "var(--primary)",
          borderRadius: 8, padding: "3px 10px", fontWeight: 700, fontSize: 14,
          fontVariantNumeric: "tabular-nums", background: "var(--card)",
        }}>£{price.toFixed(2)}</span>
        {onFilament ? (
          <select className="sel" value={filament.id} onChange={(e) => onFilament(e.target.value)}
            style={{ width: "auto", padding: "5px 28px 5px 10px", fontSize: 12.5, borderRadius: 999 }}>
            {FILAMENTS.filter(f => f.stock > 0).map(f => <option key={f.id} value={f.id}>{f.name}</option>)}
          </select>
        ) : (
          <span className="mono" style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12, color: "var(--ink-soft)" }}>
            <span style={{ width: 12, height: 12, borderRadius: 999, background: filament.hex, boxShadow: "inset 0 0 0 1px rgba(0,0,0,.12)" }} />
            {filament.name}
          </span>
        )}
      </div>
      <div style={{ display: "flex", gap: 12, marginTop: 10, alignItems: "flex-start" }}>
        <span aria-hidden style={{
          width: 44, height: 44, borderRadius: 8, flexShrink: 0,
          background: "var(--bg)", display: "grid", placeItems: "center",
        }}>
          <span style={{ width: 20, height: 20, borderRadius: 4, background: filament.hex, boxShadow: "inset 0 0 0 1px rgba(0,0,0,.1)" }} />
        </span>
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ fontWeight: 600, fontSize: 13.5, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{name}</div>
          <div className="mono" style={{ fontSize: 11, color: "var(--muted)", marginTop: 2 }}>{dims}</div>
          <div style={{ display: "flex", gap: 6, marginTop: 7, flexWrap: "wrap" }}>
            <span className="chip">3D Printing</span>
            <span className="chip">FDM</span>
            {materialLabel && <span className="chip" style={{ background: "var(--bg)", color: "var(--ink-soft)" }}>{materialLabel}</span>}
          </div>
        </div>
        <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 6 }}>
          {onQty ? <Stepper small value={qty} onChange={onQty} min={1} max={500} /> :
            <span className="mono" style={{ fontSize: 12, color: "var(--ink-soft)" }}>× {qty}</span>}
          {onRemove && (
            <button onClick={onRemove} style={{ border: "none", background: "transparent", cursor: "pointer", color: "var(--muted)", fontSize: 11, textDecoration: "underline", padding: 0 }}>
              Remove
            </button>
          )}
        </div>
      </div>
    </article>
  );
}

function AnalysisMini({ file }) {
  const a = file.analysis;
  const checks = [
    { l: "Closed Geometry (Watertight)", ok: a.watertight },
    { l: "Thin walls", ok: true },
    { l: "Overhangs and Supports", ok: !a.needsSupport, warn: a.needsSupport },
    { l: "Part Integrity", ok: a.partIntegrity },
    { l: "Build volume", ok: file.dims[0] <= 400 && file.dims[1] <= 400 && file.dims[2] <= 380 },
  ];
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 5, alignItems: "flex-end" }}>
      <span className="mono upper" style={{ fontSize: 10, color: "var(--ink-soft)", fontWeight: 600, background: "color-mix(in srgb, var(--card) 85%, transparent)", padding: "3px 8px", borderRadius: 6, backdropFilter: "blur(6px)" }}>
        AI geometry analysis
      </span>
      {checks.map((c, i) => (
        <span key={i} className="mono" style={{
          fontSize: 10.5, color: "var(--ink-soft)",
          display: "inline-flex", alignItems: "center", gap: 6,
          background: "color-mix(in srgb, var(--card) 85%, transparent)",
          padding: "2px 8px", borderRadius: 6, backdropFilter: "blur(6px)",
        }}>
          {c.l}
          <CheckDot ok={c.ok} warn={c.warn} />
        </span>
      ))}
    </div>
  );
}

function CheckDot({ ok, warn }) {
  const fill = ok ? "var(--success)" : warn ? "#c98a18" : "#b3261e";
  return (
    <svg width="14" height="14" viewBox="0 0 16 16" style={{ flexShrink: 0 }}>
      <circle cx="8" cy="8" r="7" fill={fill} />
      <path d={ok ? "M4.5 8.4l2.2 2.2L11.6 6" : warn ? "M8 4.5v4M8 10.8v.5" : "M5.5 5.5l5 5M10.5 5.5l-5 5"}
        fill="none" stroke="#fff" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

const Stepper = ({ value, onChange, min = 1, max = 500, small }) => (
  <div style={{ display: "inline-flex", alignItems: "center", border: "1px solid var(--line)", borderRadius: 8, overflow: "hidden", background: "var(--card)" }}>
    <button onClick={() => onChange(Math.max(min, value - 1))} style={{
      border: "none", background: "transparent", cursor: "pointer",
      width: small ? 26 : 32, height: small ? 26 : 32, fontSize: 15, color: "var(--ink-soft)",
    }}>−</button>
    <input type="number" value={value} min={min} max={max}
      onChange={(e) => onChange(Math.max(min, Math.min(max, parseInt(e.target.value) || min)))}
      style={{
        border: "none", outline: "none", width: small ? 34 : 44, height: small ? 26 : 32,
        textAlign: "center", fontSize: 13, fontFamily: "var(--mono)",
        fontVariantNumeric: "tabular-nums", background: "transparent", color: "var(--ink)",
        MozAppearance: "textfield",
      }} />
    <button onClick={() => onChange(Math.min(max, value + 1))} style={{
      border: "none", background: "transparent", cursor: "pointer",
      width: small ? 26 : 32, height: small ? 26 : 32, fontSize: 15, color: "var(--ink-soft)",
    }}>+</button>
  </div>
);

// Human-readable duration
function formatHours(hrs) {
  if (!hrs || hrs < 0.01) return "0min";
  const totalMin = Math.round(hrs * 60);
  if (totalMin < 60) return `${totalMin}min`;
  const h = Math.floor(totalMin / 60);
  const m = totalMin % 60;
  return m === 0 ? `${h}h` : `${h}h ${m}m`;
}

window.UploadScreen = UploadScreen;

// 1.75mm filament: cross-section π(0.875mm)² = 2.405 mm². length_mm = vol_mm³ / 2.405.
// vol_mm³ = mass_g / density_g_per_cm³ × 1000.
function formatFilamentLength(massG, density = 1.24) {
  if (!massG || massG <= 0) return "0 m";
  const volMm3 = (massG / density) * 1000;
  const lengthM = volMm3 / 2.405 / 1000;
  return lengthM < 1 ? `${(lengthM * 100).toFixed(0)} cm` : `${lengthM.toFixed(lengthM < 10 ? 1 : 0)} m`;
}
