/* global React, PageHero, CONTACT, MATERIALS */

/* ============================================================
   Constants
   ============================================================ */
const ROOM_TYPES = [
  { id: "kitchen",   label: "Kitchen" },
  { id: "bathroom",  label: "Bathroom" },
  { id: "utility",   label: "Utility" },
  { id: "fireplace", label: "Fireplace" },
  { id: "memorial",  label: "Memorial" },
  { id: "other",     label: "Something else" },
];

const QUOTE_MATERIALS = [
  { id: "quartz",    label: "Quartz",       cls: "stone-quartz" },
  { id: "granite",   label: "Granite",      cls: "stone-granite" },
  { id: "porcelain", label: "Porcelain",    cls: "stone-porcelain" },
  { id: "unsure",    label: "Not sure yet", cls: "" },
];

const THICKNESS_OPTS = [
  { id: "20mm", label: "20mm" },
  { id: "30mm", label: "30mm" },
];

const EDGE_OPTS = [
  { id: "square",   label: "Square",   sub: "Standard", photo: "/images/edge-standard.jpg" },
  { id: "ogee",     label: "Ogee",     sub: "Shaped",   photo: "/images/edge-ogee.jpg" },
  { id: "bullnose", label: "Bullnose", sub: "Rounded",  photo: "/images/edge-bullnose.jpg" },
];

const GEO_TEMPLATES = [
  { id: "straight", label: "Straight run", hint: "One wall run" },
  { id: "l",        label: "L-shape",      hint: "Two joined runs" },
  { id: "u",        label: "U-shape",      hint: "Three runs" },
  { id: "island",   label: "Island",       hint: "Single top" },
  { id: "two-runs", label: "Two runs",     hint: "Opposite walls" },
];

const TEMPLATE_BLOCKS = {
  straight: [
    { id:"A", type:"run",    name:"Run A",  x:26, y:45, w:48, h:10, rotated:false, length:"3.0", depth:"0.62" },
  ],
  l: [
    // Run A is the back-wall run; Run B starts where A ends and runs
    // perpendicular, sitting beside A at the corner — they abut, never overlap.
    { id:"A", type:"run",    name:"Run A",  x:10, y:8,  w:51, h:10, rotated:false, length:"3.2", depth:"0.62" },
    { id:"B", type:"run",    name:"Run B",  x:10, y:18, w:10, h:43, rotated:true,  length:"2.7", depth:"0.62" },
  ],
  u: [
    { id:"A", type:"run",    name:"Run A",  x:6,  y:8,  w:10, h:38, rotated:true,  length:"2.4", depth:"0.62" },
    { id:"B", type:"run",    name:"Run B",  x:16, y:36, w:32, h:10, rotated:false, length:"2.0", depth:"0.62" },
    { id:"C", type:"run",    name:"Run C",  x:48, y:8,  w:10, h:38, rotated:true,  length:"2.4", depth:"0.62" },
  ],
  island: [
    { id:"A", type:"island", name:"Island", x:31, y:42, w:38, h:16, rotated:false, length:"2.4", depth:"1.0" },
  ],
  "two-runs": [
    { id:"A", type:"run",    name:"Run A",  x:26, y:20, w:48, h:10, rotated:false, length:"3.0", depth:"0.62" },
    { id:"B", type:"run",    name:"Run B",  x:26, y:60, w:48, h:10, rotated:false, length:"3.0", depth:"0.62" },
  ],
};

const TIMELINES = [
  "As soon as possible", "Within the next month",
  "1–3 months", "3+ months", "Just exploring",
];

const PRICING = {
  materials: {
    quartz:    { min:510, max:570 },  // €316–354/lm at 0.62m depth; base €330/lm
    granite:   { min:510, max:570 },
    porcelain: { min:610, max:760 },  // from ~€380/lm
    unsure:    { min:510, max:760 },
  },
  thickness: { "20mm":1.0, "30mm":1.0 },
  edge:      { square:0, ogee:22, bullnose:25 },
  // Colour pricing tiers. The estimate uses the HIGHEST bracket the customer
  // picks across all selected colours.
  //   1 = Zeal (standard, base price)
  //   2 = Exo  (small uplift — same quality, dearer stone)
  brackets:  { 1:1.0, 2:1.16 },
  extras: {
    highUpstandMetres: { min:150, max:220 },
    splashbackMetres:  { min:180, max:280 },
    hobPanelMetres:    { min:150, max:220 },  // high panel behind hob, per lm
    drainerGrooves:    { min:85,  max:140 },
    waterfallEnds:     { min:130, max:190 },
  },
};

/* ============================================================
   Utils
   ============================================================ */
function clamp(n, min, max) { return Math.max(min, Math.min(max, n)); }

function parseMeasure(v) {
  const n = parseFloat(String(v || "").replace(",", ".").replace(/[^\d.]/g, ""));
  return Number.isFinite(n) ? n : 0;
}

function formatMeasure(n) { return String(Math.round(n * 100) / 100); }

function totalLinearMetres(blocks) {
  return blocks.reduce((s, b) => s + parseMeasure(b.length), 0);
}

const PLAN_SCALE = 16;

function sizeFromMeasures(block) {
  const length = parseMeasure(block.length);
  const depth  = parseMeasure(block.depth);
  // Keep current visual size if length is not a valid number — prevents
  // the block jumping when the field is cleared or mid-edit.
  if (!length) return { w: block.w, h: block.h };
  const long  = clamp(length * PLAN_SCALE, 5, 90);
  const short = clamp((depth || 0.62) * PLAN_SCALE, 1.5, 40);
  return block.rotated ? { w: short, h: long } : { w: long, h: short };
}

// Sum the real metres of marked block edges (full-height walls / waterfall sides).
function sumEdges(blocks, key) {
  return (blocks || []).reduce((t, b) => t + (b[key] || []).reduce((s, edge) => {
    const isTopBottom = edge === "top" || edge === "bottom";
    return s + parseMeasure(isTopBottom ? (b.rotated ? b.depth : b.length) : (b.rotated ? b.length : b.depth));
  }, 0), 0);
}

// Highest pricing tier across the customer's selected colours (looked up in the
// materials library). Multi-colour jobs are priced at the dearest stone chosen.
function selectedBracketMultiplier(form) {
  const lib = (typeof MATERIALS !== "undefined" ? MATERIALS : []).find(m => m.id === form.material);
  const names = form.colours && form.colours.length ? form.colours : (form.colour ? [form.colour] : []);
  if (!lib || !names.length) return 1;
  const top = names.reduce((mx, name) => {
    const c = (lib.colours || []).find(x => x.name === name);
    return Math.max(mx, (c && c.bracket) || 1);
  }, 1);
  return PRICING.brackets[top] || 1;
}

function computeEstimate(form) {
  const lm = totalLinearMetres(form.blocks);
  if (!lm || !form.material || form.material === "unsure") return null;
  const baseP  = PRICING.materials[form.material];
  const bMult  = selectedBracketMultiplier(form);
  const prices = { min: baseP.min * bMult, max: baseP.max * bMult };
  const mult   = PRICING.thickness[form.thickness] || 1;
  const edgeEx = PRICING.edge[form.edgeProfile] || 0;
  const sqm    = lm * 0.62;
  let lo = sqm * prices.min * mult + lm * edgeEx;
  let hi = sqm * prices.max * mult + lm * edgeEx;
  const ex = form.extras || {};
  // Full-height splashback & waterfall are marked on the layout (which walls /
  // which edges), so we price the real metres. If they uploaded a photo instead
  // of drawing, nothing is marked — fall back to a sensible default.
  const fhM = ex.fullHeight ? (sumEdges(form.blocks, "fullHeightEdges") || lm) : 0;
  lo += fhM * PRICING.extras.highUpstandMetres.min; hi += fhM * PRICING.extras.highUpstandMetres.max;
  if (ex.hobPanel) { lo += 0.9 * PRICING.extras.hobPanelMetres.min; hi += 0.9 * PRICING.extras.hobPanelMetres.max; }
  const drainers = parseMeasure(ex.drainerGrooves) || 0;
  lo += drainers * PRICING.extras.drainerGrooves.min; hi += drainers * PRICING.extras.drainerGrooves.max;
  const wfM = ex.waterfall ? (sumEdges(form.blocks, "waterfallEdges") || (0.62 * (ex.waterfallEnds || 1))) : 0;
  lo += wfM * 0.85 * prices.min * mult; hi += wfM * 0.85 * prices.max * mult;
  const sills  = parseMeasure(ex.windowSill) || 0;
  const sillW  = parseMeasure(ex.windowSillWidth) || 1;
  lo += sills * sillW * 0.25 * prices.min; hi += sills * sillW * 0.25 * prices.max;
  const splashbackTotal = fhM + (ex.hobPanel ? 0.9 : 0);
  return { min: Math.round(lo / 50) * 50, max: Math.round(hi / 50) * 50, splashbackTotal };
}

function formatEuro(n) { return "€" + n.toLocaleString("en-IE"); }

const LS_KEY = "es_quote_draft";
const LS_VER = "v4";
// Strip the uploaded file out of the saved draft — it can be a megabyte+ and
// would blow the localStorage quota. It only needs to live until submit.
function saveDraft(form)  { try { const { planUpload, ...rest } = form; localStorage.setItem(LS_KEY, JSON.stringify({ ...rest, _v: LS_VER })); } catch(e) {} }
function loadDraft()      { try { const d = JSON.parse(localStorage.getItem(LS_KEY)); return d && d._v === LS_VER ? d : null; } catch(e) { return null; } }
function clearDraft()     { try { localStorage.removeItem(LS_KEY); } catch(e) {} }

/* ============================================================
   ProgressBar
   ============================================================ */
function ProgressBar({ step }) {
  const labels = ["Room", "Spec", "Layout", "Extras", "Contact"];
  const pct = ((step - 1) / (labels.length - 1)) * 100;
  return (
    <div className="qpb-wrap" role="progressbar" aria-valuenow={step} aria-valuemin={1} aria-valuemax={5}>
      <div className="qpb-track">
        <div className="qpb-fill" style={{ width: pct + "%" }} />
      </div>
      <div className="qpb-steps">
        {labels.map((l, i) => {
          const state = step > i + 1 ? " done" : step === i + 1 ? " active" : "";
          return (
            <div key={l} className={"qpb-step" + state}>
              <div className="qpb-dot" />
              <span>{l}</span>
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ============================================================
   Stepper
   ============================================================ */
function Stepper({ value, onChange, min = 0, max = 10 }) {
  return (
    <div className="qs-stepper" role="group">
      <button type="button" className="qs-btn" onClick={() => onChange(Math.max(min, value - 1))} disabled={value <= min} aria-label="Decrease">–</button>
      <span className="qs-val" aria-live="polite">{value}</span>
      <button type="button" className="qs-btn" onClick={() => onChange(Math.min(max, value + 1))} disabled={value >= max} aria-label="Increase">+</button>
    </div>
  );
}

/* ============================================================
   Tooltip
   ============================================================ */
function Tip({ text }) {
  const [open, setOpen] = React.useState(false);
  return (
    <span className="qtip-wrap">
      <button
        type="button"
        className="qtip-btn"
        aria-label={"More info: " + text}
        onClick={() => setOpen(v => !v)}
        onBlur={() => setOpen(false)}
      >?</button>
      {open && <span className="qtip-popup" role="tooltip">{text}</span>}
    </span>
  );
}

/* ============================================================
   ShapeThumb
   ============================================================ */
function ShapeThumb({ type }) {
  return (
    <svg className="shape-sketch" viewBox="0 0 80 56" aria-hidden="true">
      {type === "straight" && (
        <rect x="6" y="22" width="68" height="12" rx="1" fill="currentColor" opacity="0.6" />
      )}
      {type === "l" && (
        <React.Fragment>
          <rect x="10" y="6"  width="12" height="44" rx="1" fill="currentColor" opacity="0.6" />
          <rect x="10" y="38" width="58" height="12" rx="1" fill="currentColor" opacity="0.6" />
        </React.Fragment>
      )}
      {type === "u" && (
        <React.Fragment>
          <rect x="6"  y="6"  width="12" height="44" rx="1" fill="currentColor" opacity="0.6" />
          <rect x="18" y="38" width="44" height="12" rx="1" fill="currentColor" opacity="0.6" />
          <rect x="62" y="6"  width="12" height="44" rx="1" fill="currentColor" opacity="0.6" />
        </React.Fragment>
      )}
      {type === "island" && (
        <rect x="16" y="12" width="48" height="32" rx="2" fill="currentColor" opacity="0.6" />
      )}
      {type === "two-runs" && (
        <React.Fragment>
          <rect x="6" y="12" width="68" height="10" rx="1" fill="currentColor" opacity="0.6" />
          <rect x="6" y="34" width="68" height="10" rx="1" fill="currentColor" opacity="0.6" />
        </React.Fragment>
      )}
    </svg>
  );
}

/* ============================================================
   Room icons
   ============================================================ */
function RoomIcon({ id }) {
  const icons = {
    kitchen: (
      <svg viewBox="0 0 32 32" fill="none" aria-hidden="true">
        <rect x="4" y="16" width="24" height="5" rx="1" fill="currentColor" opacity="0.9"/>
        <rect x="6" y="21" width="20" height="7" rx="1" fill="currentColor" opacity="0.25"/>
        <circle cx="11" cy="12" r="2.5" fill="currentColor" opacity="0.7"/>
        <circle cx="21" cy="12" r="2.5" fill="currentColor" opacity="0.7"/>
      </svg>
    ),
    island: (
      <svg viewBox="0 0 32 32" fill="none" aria-hidden="true">
        <rect x="5" y="13" width="22" height="7" rx="1" fill="currentColor" opacity="0.9"/>
        <rect x="9"  y="20" width="3" height="5" rx="1" fill="currentColor" opacity="0.45"/>
        <rect x="20" y="20" width="3" height="5" rx="1" fill="currentColor" opacity="0.45"/>
      </svg>
    ),
    bathroom: (
      <svg viewBox="0 0 32 32" fill="none" aria-hidden="true">
        <rect x="5" y="17" width="22" height="8" rx="2" fill="currentColor" opacity="0.9"/>
        <rect x="9" y="10" width="8" height="7" rx="1" fill="currentColor" opacity="0.45"/>
        <circle cx="13" cy="13" r="2" fill="currentColor" opacity="0.2"/>
      </svg>
    ),
    utility: (
      <svg viewBox="0 0 32 32" fill="none" aria-hidden="true">
        <rect x="4" y="13" width="24" height="5" rx="1" fill="currentColor" opacity="0.9"/>
        <rect x="5"  y="18" width="10" height="8" rx="1" fill="currentColor" opacity="0.35"/>
        <rect x="17" y="18" width="10" height="8" rx="1" fill="currentColor" opacity="0.35"/>
      </svg>
    ),
    fireplace: (
      <svg viewBox="0 0 32 32" fill="none" aria-hidden="true">
        <rect x="4"  y="24" width="24" height="4" rx="1" fill="currentColor" opacity="0.9"/>
        <rect x="4"  y="8"  width="5"  height="16" rx="1" fill="currentColor" opacity="0.6"/>
        <rect x="23" y="8"  width="5"  height="16" rx="1" fill="currentColor" opacity="0.6"/>
        <rect x="9"  y="8"  width="14" height="3"  rx="1" fill="currentColor" opacity="0.4"/>
      </svg>
    ),
    memorial: (
      <svg viewBox="0 0 32 32" fill="none" aria-hidden="true">
        <rect x="10" y="7" width="12" height="18" rx="6" fill="currentColor" opacity="0.9"/>
        <rect x="5"  y="25" width="22" height="3" rx="1" fill="currentColor" opacity="0.5"/>
      </svg>
    ),
    other: (
      <svg viewBox="0 0 32 32" fill="none" aria-hidden="true">
        <circle cx="16" cy="16" r="11" stroke="currentColor" strokeWidth="1.8" opacity="0.7"/>
        <text x="16" y="21" textAnchor="middle" fill="currentColor" fontSize="13" fontWeight="700" fontFamily="sans-serif">?</text>
      </svg>
    ),
  };
  return <div className="room-icon">{icons[id] || icons.other}</div>;
}

/* ============================================================
   PlanBuilder helpers
   ============================================================ */
function findFreePos(blocks, w, h) {
  const margin = 3;
  for (let ty = 4; ty + h <= 95; ty += 4) {
    for (let tx = 4; tx + w <= 95; tx += 4) {
      const clear = !blocks.some(b =>
        tx < b.x + b.w + margin &&
        tx + w + margin > b.x &&
        ty < b.y + b.h + margin &&
        ty + h + margin > b.y
      );
      if (clear) return { x: tx, y: ty };
    }
  }
  const maxY = blocks.reduce((m, b) => Math.max(m, b.y + b.h), 0);
  return { x: 4, y: Math.min(maxY + margin, 95 - h) };
}

/* ============================================================
   PlanBuilder
   ============================================================ */
function PlanBuilder({ blocks, onBlocks }) {
  const [activeId, setActiveId] = React.useState(blocks[0]?.id || "");
  const [interaction, setInteraction] = React.useState(null);
  const canvasRef = React.useRef(null);

  React.useEffect(() => {
    if (!blocks.some(b => b.id === activeId) && blocks.length > 0) {
      setActiveId(blocks[0].id);
    }
  }, [blocks, activeId]);

  const activeBlock = blocks.find(b => b.id === activeId);

  const updateBlock = (id, patch) =>
    onBlocks(blocks.map(b => b.id === id ? { ...b, ...patch } : b));

  // Standard stone slab: 3.2m × 1.8m. Islands larger than this need multiple
  // slabs and a seam, so we cap what customers can submit at slab dimensions.
  const ISLAND_MAX_LENGTH = 3.2;
  const ISLAND_MAX_DEPTH  = 1.8;
  const capIslandString = (str, maxVal) => {
    const n = parseMeasure(str);
    if (!n || n <= maxVal) return str;
    return formatMeasure(maxVal);
  };

  const updateMeasured = (id, patch) => {
    const src = blocks.find(b => b.id === id);
    if (!src) return;
    // Cap island dimensions to one standard slab.
    const p = { ...patch };
    if (src.type === "island") {
      if (p.length !== undefined) p.length = capIslandString(p.length, ISLAND_MAX_LENGTH);
      if (p.depth  !== undefined) p.depth  = capIslandString(p.depth,  ISLAND_MAX_DEPTH);
    }
    const next = { ...src, ...p };
    const size = sizeFromMeasures(next);
    updateBlock(id, { ...p, w: size.w, h: size.h,
      x: clamp(next.x, 0, 100 - size.w),
      y: clamp(next.y, 0, 100 - size.h) });
  };

  const addBlock = (type) => {
    const id = type + "-" + Date.now();
    const n  = blocks.length + 1;
    const proto = type === "island"
      ? { id, type:"island", name:"Island " + n, rotated:false, length:"1.2", depth:"0.9",  splashbackEdges:[] }
      : type === "sill"
      ? { id, type:"sill",   name:"Window sill " + n, rotated:false, length:"1.0", depth:"0.22", splashbackEdges:[] }
      : { id, type:"run",    name:"Run " + n,          rotated:false, length:"2.0", depth:"0.62", splashbackEdges:[] };
    const size = sizeFromMeasures({ ...proto, w: 0, h: 0 });
    const pos  = findFreePos(blocks, size.w, size.h);
    onBlocks([...blocks, { ...proto, ...pos, w: size.w, h: size.h }]);
    setActiveId(id);
  };

  const removeActive = () => {
    if (!activeBlock || blocks.length === 1) return;
    const rest = blocks.filter(b => b.id !== activeBlock.id);
    onBlocks(rest);
    setActiveId(rest[0]?.id || "");
  };

  const turnBlock = (e, block) => {
    e.preventDefault(); e.stopPropagation();
    updateBlock(block.id, {
      rotated: !block.rotated, w: block.h, h: block.w,
      x: clamp(block.x, 0, 100 - block.h),
      y: clamp(block.y, 0, 100 - block.w),
    });
  };

  const startInteraction = (e, block, mode) => {
    e.preventDefault(); e.stopPropagation();
    setActiveId(block.id);
    setInteraction({ mode, id: block.id,
      startX: e.clientX, startY: e.clientY,
      x: block.x, y: block.y, w: block.w, h: block.h });
  };

  const onPointerMove = (e) => {
    if (!interaction) return;
    const rect = canvasRef.current?.getBoundingClientRect();
    if (!rect) return;
    const dx = ((e.clientX - interaction.startX) / rect.width)  * 100;
    const dy = ((e.clientY - interaction.startY) / rect.height) * 100;
    const block = blocks.find(b => b.id === interaction.id);
    if (!block) return;
    if (interaction.mode === "move") {
      updateBlock(interaction.id, {
        x: clamp(interaction.x + dx, 0, 100 - block.w),
        y: clamp(interaction.y + dy, 0, 100 - block.h),
      });
    } else {
      const nw = clamp(interaction.w + dx, 1.5, 100 - block.x);
      const nh = clamp(interaction.h + dy, 1.5, 100 - block.y);
      const vl = block.rotated ? nh : nw;
      const vd = block.rotated ? nw : nh;
      updateBlock(interaction.id, { w: nw, h: nh,
        length: formatMeasure(vl / PLAN_SCALE),
        depth:  formatMeasure(vd / PLAN_SCALE) });
    }
  };

  const lm = totalLinearMetres(blocks);

  return (
    <div className="plan-builder">
      <div ref={canvasRef} className="plan-canvas"
        onPointerMove={onPointerMove}
        onPointerUp={() => setInteraction(null)}
        onPointerLeave={() => setInteraction(null)}
      >
        {blocks.map((block, i) => (
          <div key={block.id}
            role="button" tabIndex={0}
            aria-label={block.name + ", " + (block.length || "?") + "m"}
            className={"plan-block"
              + (block.id === activeId ? " active" : "")
              + (block.type === "island" ? " island" : "")
              + (block.type === "sill" ? " sill" : "")}
            style={{ left: block.x+"%", top: block.y+"%", width: block.w+"%", height: block.h+"%" }}
            onPointerDown={e => startInteraction(e, block, "move")}
            onKeyDown={e => e.key === "Enter" && setActiveId(block.id)}
          >
            <span>{block.type === "sill" ? "Sill" : String.fromCharCode(65 + i)}</span>
            <small>{block.length || "?"}m</small>
            <button type="button" className="turn-handle"
              onPointerDown={e => turnBlock(e, block)}
              aria-label="Rotate">↻</button>
            <button type="button" className="resize-handle"
              onPointerDown={e => startInteraction(e, block, "resize")}
              aria-label="Resize" />
          </div>
        ))}
      </div>

      <div className="plan-controls">
        {/* Row 1: Worktop run · Island top · Remove */}
        <div className="plan-actions-row">
          <button type="button" className="plan-add-btn" onClick={() => addBlock("run")}>
            <span className="plan-add-icon">＋</span>
            <span className="plan-add-main">Worktop run</span>
            <span className="plan-add-sub">A section along a wall</span>
          </button>
          <button type="button" className="plan-add-btn" onClick={() => addBlock("island")}>
            <span className="plan-add-icon">＋</span>
            <span className="plan-add-main">Island top</span>
            <span className="plan-add-sub">Freestanding centre piece</span>
          </button>
          <button type="button" className="plan-add-btn remove" onClick={removeActive} disabled={blocks.length <= 1}>
            <span className="plan-add-icon">✕</span>
            <span className="plan-add-main">Remove</span>
            <span className="plan-add-sub">Delete selected block</span>
          </button>
        </div>

        {activeBlock && (
          <div className="plan-editor">
            <label>
              <span>Name</span>
              <input className="input" value={activeBlock.name}
                onChange={e => updateBlock(activeBlock.id, { name: e.target.value })} />
            </label>
            <label>
              <span>Wall length (m)</span>
              <input className="input" value={activeBlock.length} placeholder="e.g. 3.2"
                onChange={e => updateMeasured(activeBlock.id, { length: e.target.value })} />
            </label>
            <label title="How deep, front to back. A standard worktop is 0.62m; islands are usually wider.">
              <span>How deep (m)</span>
              <input className="input" value={activeBlock.depth} placeholder="0.62 = standard"
                onChange={e => updateMeasured(activeBlock.id, { depth: e.target.value })} />
            </label>
            {activeBlock.type === "island" && (
              <div style={{ gridColumn: "1 / -1", fontSize: 12.5, color: "var(--grey)", lineHeight: 1.5, marginTop: -8 }}>
                Islands are capped at <strong>3.2m × 1.8m</strong> — one standard slab. Larger islands need multiple slabs and a seam, so those are quoted separately.
              </div>
            )}
          </div>
        )}

        <div className="plan-total">
          <span>Total linear metres</span>
          <strong>{lm > 0 ? lm.toFixed(1) + " m" : "—"}</strong>
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   Screen 1 — Room type
   ============================================================ */
function Screen1({ form, set, onNext }) {
  const toggle = (id) => {
    const current = form.roomTypes || [];
    const next = current.includes(id) ? current.filter(x => x !== id) : [...current, id];
    set("roomTypes", next);
  };

  const selected = form.roomTypes || [];
  const kitchenOn = selected.includes("kitchen");

  return (
    <div className="qf-screen fade-up">
      <div className="qf-head">
        <div className="kicker" style={{ color: "var(--teal)" }}>Step 1 of 5</div>
        <h3 className="qf-screen-title">What are you looking for?</h3>
        <p className="lede" style={{ marginTop: 8, fontSize: 16 }}>Select all that apply.</p>
      </div>

      <div className="qf-room-grid" role="group" aria-label="Room types">
        {ROOM_TYPES.map(r => (
          <button key={r.id} type="button"
            aria-pressed={selected.includes(r.id)}
            className={"qf-room-btn" + (selected.includes(r.id) ? " selected" : "")}
            onClick={() => toggle(r.id)}
          >
            <RoomIcon id={r.id} />
            <span>{r.label}</span>
          </button>
        ))}
      </div>

      <div className="qf-nav">
        <div />
        <button type="button" className="btn btn-teal" onClick={onNext} disabled={selected.length === 0}>
          Continue <span className="arr">→</span>
        </button>
      </div>
    </div>
  );
}

/* ============================================================
   Screen 2 — Material & finish
   ============================================================ */
function Screen2({ form, set, onNext, onBack }) {
  const isMemorial  = (form.roomTypes || []).includes("memorial");
  const isFireplace = (form.roomTypes || []).includes("fireplace");

  // Fireplace hearths must handle direct heat — Granite only.
  React.useEffect(() => {
    if (isFireplace && form.material !== "granite") set("material", "granite");
  }, [isFireplace]); // eslint-disable-line react-hooks/exhaustive-deps

  // Colour choices come straight from the materials library (window.MATERIALS).
  const matEntry = (typeof MATERIALS !== "undefined" ? MATERIALS : []).find(x => x.id === form.material);
  const colours  = (matEntry && matEntry.colours) || [];

  return (
    <div className="qf-screen fade-up">
      <div className="qf-head">
        <div className="kicker" style={{ color: "var(--teal)" }}>Step 2 of 5</div>
        <h3 className="qf-screen-title">Material & finish</h3>
        <p className="lede" style={{ marginTop: 8, fontSize: 16 }}>
          {isMemorial ? "What stone are you considering?" : "Pick your material and colour direction."}
        </p>
      </div>

      <div className="qf-section">
        <div className="qf-section-label">Material</div>
        {isFireplace && (
          <p style={{ marginTop: -4, marginBottom: 14, fontSize: 13.5, color: "var(--grey)", maxWidth: 520 }}>
            Granite is recommended for fireplaces — it's the only one of our stones rated for direct heat.
          </p>
        )}
        <div className="qf-mat-grid">
          {QUOTE_MATERIALS.map(m => {
            const disabled = isFireplace && m.id !== "granite";
            return (
              <button key={m.id} type="button"
                disabled={disabled}
                title={disabled ? "Granite is recommended for fireplaces due to heat resistance" : undefined}
                className={"qf-mat-btn" + (form.material === m.id ? " selected" : "") + (disabled ? " is-disabled" : "")}
                onClick={() => { if (!disabled) set("material", m.id); }}
              >
                <div className={"tile-swatch stone" + (m.cls ? " " + m.cls : " unsure-swatch")} />
                <div className="tile-name">{m.label}</div>
              </button>
            );
          })}
        </div>
      </div>

      {form.material && form.material !== "unsure" && colours.length > 0 && (
        <div className="qf-section fade-up">
          <div className="qf-section-label">
            Colour <span style={{ fontWeight: 400, textTransform: "none", letterSpacing: 0, color: "var(--grey)" }}>· pick one or more (e.g. a different colour for the island) — confirmed at the showroom</span>
          </div>
          {(() => {
            const renderTile = c => {
              const picked = (form.colours || []).includes(c.name);
              return (
                <button key={c.name} type="button"
                  className={"qf-colour-tile" + (picked ? " selected" : "")}
                  aria-pressed={picked}
                  onClick={() => {
                    const cur = form.colours || [];
                    set("colours", picked ? cur.filter(n => n !== c.name) : [...cur, c.name]);
                  }}
                >
                  <div className="qf-colour-swatch" style={{ background: c.swatch }}>
                    {c.slug && <img src={`/images/${c.slug}-slab.jpg`} alt={c.name} loading="lazy"
                      onError={e => { e.currentTarget.style.display = "none"; }} />}
                  </div>
                  <span className="qf-colour-name">{c.name}</span>
                </button>
              );
            };
            const zeal = colours.filter(c => (c.bracket || 1) === 1);
            const exo  = colours.filter(c => c.bracket === 2);
            // If nothing is tier-tagged, fall back to a single grid.
            if (!zeal.length || !exo.length) {
              return <div className="qf-colour-grid">{colours.map(renderTile)}</div>;
            }
            return (
              <React.Fragment>
                {zeal.length > 0 && (
                  <React.Fragment>
                    <div className="qf-tier-label">Zeal</div>
                    <div className="qf-colour-grid" style={{ marginBottom: 24 }}>{zeal.map(renderTile)}</div>
                  </React.Fragment>
                )}
                {exo.length > 0 && (
                  <React.Fragment>
                    <div className="qf-tier-label">Exo</div>
                    <div className="qf-colour-grid">{exo.map(renderTile)}</div>
                  </React.Fragment>
                )}
              </React.Fragment>
            );
          })()}
        </div>
      )}

      {!isMemorial && (() => {
        // If the customer picked any colour we only stock at 20mm, disable the
        // 30mm thickness option and force-select 20mm.
        const only20mmNames = new Set(
          (colours || []).filter(c => c.only20mm).map(c => c.name)
        );
        const has20mmOnly = (form.colours || []).some(n => only20mmNames.has(n));
        if (has20mmOnly && form.thickness !== "20mm") {
          // side-effect via setState during render is fine in this component's flow;
          // the ternary keeps it idempotent.
          Promise.resolve().then(() => { set("thickness", "20mm"); set("edgeProfile", "square"); });
        }
        const only20mmPicks = (form.colours || []).filter(n => only20mmNames.has(n));
        return (
        <React.Fragment>
          <div className="qf-section">
            <div className="qf-section-label">Thickness</div>
            <div className="qf-toggle-row" role="radiogroup">
              {THICKNESS_OPTS.map(t => {
                const disabled = t.id === "30mm" && has20mmOnly;
                return (
                <button key={t.id} type="button" role="radio"
                  aria-checked={form.thickness === t.id}
                  aria-disabled={disabled}
                  disabled={disabled}
                  className={"qf-toggle-btn" + (form.thickness === t.id ? " selected" : "") + (disabled ? " disabled" : "")}
                  onClick={() => {
                    if (disabled) return;
                    set("thickness", t.id);
                    if (t.id === "20mm") set("edgeProfile", "square");
                  }}
                >
                  <span className="qf-toggle-main">{t.label}</span>
                </button>
                );
              })}
            </div>
            {has20mmOnly && (
              <div className="qf-help" style={{ marginTop: 10, color: "var(--grey)", fontSize: 13.5, lineHeight: 1.5 }}>
                {only20mmPicks.join(", ")} {only20mmPicks.length > 1 ? "are" : "is"} available in 20mm only.
              </div>
            )}
          </div>

          {form.thickness === "30mm" ? (
            <div className="qf-section">
              <div className="qf-section-label">
                Edge profile <span style={{ fontWeight: 400, textTransform: "none", letterSpacing: 0, color: "var(--grey)" }}>· so you can see what you're picking</span>
              </div>
              <div className="qf-edge-grid" role="radiogroup">
                {EDGE_OPTS.map(e => (
                  <button key={e.id} type="button" role="radio" aria-checked={form.edgeProfile === e.id}
                    className={"qf-edge-card" + (form.edgeProfile === e.id ? " selected" : "")}
                    onClick={() => set("edgeProfile", e.id)}
                  >
                    <div className="qf-edge-photo" style={{ backgroundImage: `url(${e.photo})` }} />
                    <span className="qf-edge-name">{e.label}<span className="qf-edge-sub"> · {e.sub}</span></span>
                  </button>
                ))}
              </div>
            </div>
          ) : (
            <div className="qf-section">
              <div className="qf-section-label">Edge</div>
              <div className="qf-edge-single">
                <div className="qf-edge-photo" style={{ backgroundImage: "url(/images/edge-20mm.jpg)" }} />
                <div>
                  <div className="qf-edge-name">20mm square edge</div>
                  <p style={{ marginTop: 6, fontSize: 13.5, color: "var(--grey)", lineHeight: 1.6, maxWidth: 420 }}>
                    20mm worktops come with a clean square edge as standard. The shaped profiles — ogee and bullnose — are available on 30mm.
                  </p>
                </div>
              </div>
            </div>
          )}
        </React.Fragment>
        );
      })()}

      <div className="qf-nav">
        <button type="button" className="btn-text" onClick={onBack}>Back</button>
        <button type="button" className="btn btn-teal" onClick={onNext} disabled={!form.material}>
          Continue <span className="arr">→</span>
        </button>
      </div>
    </div>
  );
}

/* ============================================================
   Screen 3 — Geometry
   ============================================================ */
function UploadZone({ fileName, onChange }) {
  return (
    <div className="qf-upload-zone">
      <label className="qf-upload-label">
        <input type="file" accept="image/*,.pdf" style={{ display: "none" }} onChange={onChange} />
        <div className="qf-upload-inner">
          {fileName ? (
            <React.Fragment>
              <div className="qf-upload-icon" style={{ color: "var(--teal)" }}>✓</div>
              <div className="qf-upload-name">{fileName}</div>
              <div className="qf-upload-hint">Click to replace</div>
            </React.Fragment>
          ) : (
            <React.Fragment>
              <div className="qf-upload-icon">↑</div>
              <div className="qf-upload-title">Upload a plan or photo</div>
              <div className="qf-upload-hint">JPG, PNG or PDF · up to 10MB</div>
            </React.Fragment>
          )}
        </div>
      </label>
    </div>
  );
}

/* Downscale an uploaded image to a JPEG data URL — keeps the AI-extraction
   payload small (raster images only; PDFs are skipped). */
function fileToScaledDataUrl(file, maxDim = 1600) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = reject;
    reader.onload = () => {
      const img = new Image();
      img.onerror = reject;
      img.onload = () => {
        const scale = Math.min(1, maxDim / Math.max(img.width, img.height));
        const w = Math.max(1, Math.round(img.width * scale));
        const h = Math.max(1, Math.round(img.height * scale));
        const canvas = document.createElement("canvas");
        canvas.width = w; canvas.height = h;
        canvas.getContext("2d").drawImage(img, 0, 0, w, h);
        resolve(canvas.toDataURL("image/jpeg", 0.85));
      };
      img.src = reader.result;
    };
    reader.readAsDataURL(file);
  });
}

/* Lay the extracted runs out as canvas blocks, reproducing the real kitchen
   shape from the mm rectangles the AI returned (x,y,w,h on a top-down grid).
   The rectangles are scaled to fit the square canvas, centred, so the runs sit
   in their true L / U / peninsula positions. Falls back to the old template
   mapping when the AI didn't return geometry. */
function blocksFromGeometry(runs) {
  const minX = Math.min(...runs.map(r => r.x));
  const minY = Math.min(...runs.map(r => r.y));
  const maxX = Math.max(...runs.map(r => r.x + r.w));
  const maxY = Math.max(...runs.map(r => r.y + r.h));
  const span = Math.max(maxX - minX, maxY - minY) || 1;
  const FIT = 84;                                  // use 84% of the canvas
  const scale = FIT / span;
  const offX = (100 - (maxX - minX) * scale) / 2;  // centre the plan
  const offY = (100 - (maxY - minY) * scale) / 2;
  return runs.map((r, i) => ({
    id: "ai-" + i + "-" + Date.now(),
    type: r.island ? "island" : "run",
    name: "Run " + (r.label || String.fromCharCode(65 + i)),
    rotated: r.h > r.w,                             // vertical piece
    length: String(Math.round(Math.max(r.w, r.h) / 10) / 100 || r.length || "2.0"),
    depth:  String(Math.round(Math.min(r.w, r.h) / 10) / 100 || r.depth || "0.62"),
    x: clamp(offX + (r.x - minX) * scale, 0, 99),
    y: clamp(offY + (r.y - minY) * scale, 0, 99),
    w: clamp(r.w * scale, 1.5, 100),
    h: clamp(r.h * scale, 1.5, 100),
    splashbackEdges: [],
  }));
}

function blocksFromExtraction(shape, runs) {
  const list = runs || [];
  const haveGeom = list.length > 0 &&
    list.every(r => ["x","y","w","h"].every(k => Number.isFinite(r[k])));
  if (haveGeom) return blocksFromGeometry(list);
  // Fallback: position by the matching template, override measured length/depth.
  const tpl = TEMPLATE_BLOCKS[shape];
  return list.map((r, i) => {
    const base = (tpl && tpl[i]) || { type: "run", rotated: false, x: 20, y: 14 + i * 24 };
    const b = {
      id: "ai-" + i + "-" + Date.now(),
      type: base.type || "run",
      name: "Run " + (r.label || String.fromCharCode(65 + i)),
      rotated: !!base.rotated,
      length: String(r.length || base.length || "2.0"),
      depth: String(r.depth || base.depth || "0.62"),
      x: base.x != null ? base.x : 20,
      y: base.y != null ? base.y : 14 + i * 24,
      splashbackEdges: [],
    };
    const size = sizeFromMeasures(b);
    return { ...b, w: size.w, h: size.h };
  });
}

function Screen3({ form, set, onNext, onBack }) {
  const isMemorial = (form.roomTypes || []).includes("memorial");
  const [mode, setMode] = React.useState(form.geometryMode || (isMemorial ? "photo" : null));
  const [photoData, setPhotoData]   = React.useState(null); // downscaled data URL (images only)
  const [extracting, setExtracting] = React.useState(false);
  const [extractMsg, setExtractMsg] = React.useState("");
  const [extractNote, setExtractNote] = React.useState(""); // post-extract "check this" banner on the canvas

  const selectMode = (m) => {
    setMode(m);
    set("geometryMode", m);
    if (m !== "photo") set("photoFileName", "");
  };

  const loadTemplate = (id) => {
    const tpl = TEMPLATE_BLOCKS[id];
    if (!tpl) return;
    set("blocks", tpl.map(b => {
      const size = sizeFromMeasures(b);
      return { ...b, w: size.w, h: size.h };
    }));
    set("selectedTemplate", id);
    selectMode("canvas");
  };

  const handleFile = (e) => {
    const f = e.target.files[0];
    if (!f) return;
    set("photoFileName", f.name);
    setExtractMsg("");
    setPhotoData(null);
    set("planUpload", null);
    if (/^image\//.test(f.type)) {
      // Downscale once: serves both the AI extraction and the office copy.
      // ~2000px keeps the dimension numbers legible while staying well under
      // Vercel's request-body limit when sent on to the office.
      fileToScaledDataUrl(f, 2000).then(url => {
        setPhotoData(url);
        set("planUpload", { name: f.name, type: "image/jpeg", dataUrl: url });
      }).catch(() => setPhotoData(null));
    } else {
      // PDFs / other: send the raw file to the office if it's small enough to
      // fit in the request body (~4.5MB cap → keep the base64 under ~3.6MB).
      const reader = new FileReader();
      reader.onload = () => {
        const dataUrl = reader.result;
        if (typeof dataUrl === "string" && dataUrl.length < 3600000) {
          set("planUpload", { name: f.name, type: f.type || "application/octet-stream", dataUrl });
        } else {
          set("planUpload", { name: f.name, type: f.type, tooLarge: true });
        }
      };
      reader.onerror = () => set("planUpload", { name: f.name, type: f.type, tooLarge: true });
      reader.readAsDataURL(f);
    }
    selectMode("photo");
  };

  const handleExtract = async () => {
    if (!photoData) return;
    setExtracting(true);
    setExtractMsg("");
    try {
      const res  = await fetch("/api/extract-plan", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ image: photoData }),
      });
      const data = await res.json();
      if (data && data.ok && data.runs && data.runs.length) {
        set("blocks", blocksFromExtraction(data.shape, data.runs));
        set("selectedTemplate", data.shape || null);
        // Reading dimensions off a photographed plan is never exact — always ask
        // the customer to check, and lean harder on it when confidence is low.
        const lowConf = typeof data.confidence === "number" && data.confidence < 0.75;
        setExtractNote(lowConf
          ? "We've sketched a rough starting point from your plan — please check each run's length and depth below, as photos of plans aren't always exact. Our team confirms the precise measurements at the site visit."
          : "We've read your plan into the canvas below — give each run a quick check and adjust anything before continuing. We confirm exact measurements at the site visit.");
        selectMode("canvas");
      } else {
        setExtractNote("");
        setExtractMsg((data && data.reason) || "We couldn't read the dimensions — please draw the layout manually or we'll confirm at measure.");
      }
    } catch (err) {
      setExtractMsg("We couldn't read the dimensions — please draw the layout manually or we'll confirm at measure.");
    }
    setExtracting(false);
  };

  const canContinue = mode === "photo"
    ? !!form.photoFileName
    : mode === "canvas";

  if (isMemorial) {
    return (
      <div className="qf-screen fade-up">
        <div className="qf-head">
          <div className="kicker" style={{ color: "var(--teal)" }}>Step 3 of 5</div>
          <h3 className="qf-screen-title">Design & dimensions</h3>
          <p className="lede" style={{ marginTop: 8, fontSize: 16 }}>
            Upload a photo of the design, or describe what you need below.
          </p>
        </div>

        <UploadZone fileName={form.photoFileName} onChange={handleFile} />

        <div className="qf-section">
          <label className="qf-section-label" htmlFor="mem-desc">Or describe what you need</label>
          <textarea id="mem-desc" className="textarea" rows={4} value={form.notes}
            onChange={e => set("notes", e.target.value)}
            placeholder="e.g. Headstone, approx 900mm tall × 600mm wide, granite, with inscription..." />
        </div>

        <div className="qf-nav">
          <button type="button" className="btn-text" onClick={onBack}>Back</button>
          <button type="button" className="btn btn-teal" onClick={onNext}
            disabled={!form.photoFileName && !form.notes}>
            Continue <span className="arr">→</span>
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className="qf-screen fade-up">
      <div className="qf-head">
        <div className="kicker" style={{ color: "var(--teal)" }}>Step 3 of 5</div>
        <h3 className="qf-screen-title">Your layout</h3>
        {!mode && (
          <p className="lede" style={{ marginTop: 8, fontSize: 16 }}>
            Pick a template to start, draw your own, or upload a plan.
          </p>
        )}
      </div>

      {!mode && (
        <React.Fragment>
          <button type="button" className="qf-upload-primary" onClick={() => selectMode("photo")}>
            <div className="qf-upload-primary-icon">↑</div>
            <div className="qf-upload-primary-text">
              <div className="qf-upload-primary-title">Upload a drawing or plan</div>
              <div className="qf-upload-primary-sub">From your carpenter, kitchen designer or architect · JPG, PNG or PDF</div>
            </div>
          </button>

          <div className="qf-section" style={{ marginTop:28 }}>
            <div className="qf-section-label">Or draw your layout</div>
            <div className="qf-tpl-grid">
              {GEO_TEMPLATES.filter(t => {
                // Bathrooms/utilities are simple straight runs — hide kitchen shapes
                const rt = form.roomTypes || [];
                const simpleOnly = (rt.includes("bathroom") || rt.includes("utility")) && !rt.includes("kitchen");
                return !simpleOnly || t.id === "straight";
              }).map(t => (
                <button key={t.id} type="button" className="qf-tpl-btn" onClick={() => loadTemplate(t.id)}>
                  <ShapeThumb type={t.id} />
                  <span className="qf-tpl-label">{t.label}</span>
                  <span className="qf-tpl-hint">{t.hint}</span>
                </button>
              ))}
            </div>
            <div className="qf-geo-alt" style={{ marginTop:14 }}>
              <button type="button" className="qf-alt-btn" onClick={() => {
                set("blocks", [{ id:"run-1", type:"run", name:"Run A", x:26, y:45, w:48, h:10, rotated:false, length:"3.0", depth:"0.62", splashbackEdges:[] }]);
                selectMode("canvas");
              }}>Start blank canvas</button>
            </div>
          </div>
        </React.Fragment>
      )}

      {mode === "canvas" && (
        <div className="qf-section">
          <div style={{ display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:12, gap:10, flexWrap:"wrap" }}>
            <div className="qf-section-label" style={{ marginBottom:0 }}>Your layout</div>
            <div style={{ display:"flex", gap:10, flexWrap:"wrap" }}>
              <button type="button" className="qf-alt-btn" onClick={() => selectMode("photo")}>↑ Upload a plan instead</button>
              <button type="button" className="qf-alt-btn" onClick={() => selectMode(null)}>← Change template</button>
            </div>
          </div>
          {extractNote && (
            <div className="qf-canvas-tip" style={{ background:"rgba(42,140,170,0.10)", borderColor:"var(--teal)" }}>
              <strong>From your plan:</strong> {extractNote}
            </div>
          )}
          <div className="qf-canvas-tip">
            <strong>How it works:</strong> Each block is one worktop section. Tap to select, then type its length — or drag the corner handle to resize. Standard depth is <strong>0.62m</strong>, so leave it unless yours is different. Splashbacks and extras come on the next step.
          </div>
          <PlanBuilder blocks={form.blocks} onBlocks={b => set("blocks", b)} />
        </div>
      )}

      {mode === "photo" && (
        <React.Fragment>
          <UploadZone fileName={form.photoFileName} onChange={handleFile} />
          {form.photoFileName && photoData && (
            <div style={{ marginTop:16, textAlign:"center" }}>
              <button type="button" className="btn btn-teal" onClick={handleExtract} disabled={extracting}>
                {extracting ? "Reading your drawing…" : "Extract measurements from this drawing"} <span className="arr">→</span>
              </button>
              <div style={{ marginTop:10, fontSize:12.5, color:"var(--grey)", maxWidth:430, margin:"10px auto 0", lineHeight:1.55 }}>
                We'll read the runs and dimensions off your plan and pre-fill the canvas — you can tweak anything before sending.
              </div>
            </div>
          )}
          {extractMsg && (
            <div style={{ marginTop:14, padding:"12px 16px", background:"rgba(192,122,0,0.08)", borderLeft:"2px solid #c07a00", fontSize:13.5, color:"var(--slate)", lineHeight:1.55, maxWidth:520, margin:"14px auto 0" }}>
              {extractMsg}
            </div>
          )}
          <div style={{ textAlign:"center", marginTop:16 }}>
            <button type="button" className="qf-alt-btn" onClick={() => selectMode(null)}>← Back to templates</button>
          </div>
        </React.Fragment>
      )}

      <div className="qf-nav">
        <button type="button" className="btn-text" onClick={onBack}>Back</button>
        <button type="button" className="btn btn-teal" onClick={onNext} disabled={!canContinue}>
          Continue <span className="arr">→</span>
        </button>
      </div>
    </div>
  );
}

/* ============================================================
   EdgeMarker — read-only mini layout used on the extras step. The
   customer taps which walls/edges get a finish (full-height splashback,
   waterfall). Marks are stored per block under [edgeKey].
   ============================================================ */
function EdgeMarker({ blocks, onBlocks, edgeKey }) {
  const toggle = (id, edge) => {
    onBlocks(blocks.map(b => {
      if (b.id !== id) return b;
      const arr = b[edgeKey] || [];
      return { ...b, [edgeKey]: arr.includes(edge) ? arr.filter(e => e !== edge) : [...arr, edge] };
    }));
  };
  const panel = 12;
  return (
    <div className="plan-canvas edge-marker">
      {blocks.flatMap(b => (b[edgeKey] || []).map(edge => {
        const style =
          edge === "top"    ? { left: b.x+"%", top: Math.max(0, b.y-panel)+"%", width: b.w+"%", height: panel+"%" }
        : edge === "bottom" ? { left: b.x+"%", top: (b.y+b.h)+"%", width: b.w+"%", height: Math.min(panel, 100-(b.y+b.h))+"%" }
        : edge === "left"   ? { left: Math.max(0, b.x-panel)+"%", top: b.y+"%", width: panel+"%", height: b.h+"%" }
        :                     { left: (b.x+b.w)+"%", top: b.y+"%", width: Math.min(panel, 100-(b.x+b.w))+"%", height: b.h+"%" };
        return (
          <div key={b.id+"-m-"+edge} className="splashback-panel" style={style}
            onClick={(e) => { e.stopPropagation(); toggle(b.id, edge); }}>
            <span>tap to remove</span>
          </div>
        );
      }))}
      {blocks.map((block, i) => (
        <div key={block.id}
          className={"plan-block" + (block.type === "island" ? " island" : "") + (block.type === "sill" ? " sill" : "")}
          style={{ left: block.x+"%", top: block.y+"%", width: block.w+"%", height: block.h+"%" }}>
          <span>{block.type === "sill" ? "Sill" : String.fromCharCode(65 + i)}</span>
          <small>{block.length || "?"}m</small>
          {["top","bottom","left","right"].map(edge => (
            <button key={edge} type="button"
              className={"sb-edge sb-"+edge + ((block[edgeKey]||[]).includes(edge) ? " on" : "")}
              onClick={() => toggle(block.id, edge)}
              aria-label={"Mark " + edge + " edge"} />
          ))}
        </div>
      ))}
    </div>
  );
}

/* ============================================================
   Finish icons — fallback line-art for the add-on cards. A real
   photo at /images/finish-[id].jpg covers the icon when present.
   ============================================================ */
function FinishIcon({ id }) {
  const c = { fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round" };
  const icons = {
    hob:        <svg viewBox="0 0 24 24" {...c}><path d="M3 17h18" /><rect x="6" y="5" width="12" height="12" /><circle cx="10" cy="13.5" r="1.1" /><circle cx="14" cy="13.5" r="1.1" /></svg>,
    fullheight: <svg viewBox="0 0 24 24" {...c}><path d="M4 6h16" /><path d="M5 6v11h14V6" /><path d="M3 17h18" /></svg>,
    sill:       <svg viewBox="0 0 24 24" {...c}><rect x="6" y="4" width="12" height="11" /><path d="M12 4v11M6 9.5h12" /><path d="M4 18h16" /></svg>,
    drainer:    <svg viewBox="0 0 24 24" {...c}><path d="M3 18h18" /><rect x="13" y="9" width="6" height="5" rx="1" /><path d="M5 14l3-4M8.5 14l3-4" /></svg>,
    waterfall:  <svg viewBox="0 0 24 24" {...c}><path d="M5 8h11v12" /><path d="M5 8V6h13" /></svg>,
    upstand:    <svg viewBox="0 0 24 24" {...c}><path d="M3 16h18" /><path d="M4 16v-3h3" /></svg>,
  };
  return <span className="qf-finish-svg">{icons[id] || null}</span>;
}

/* ============================================================
   Screen 4 — Add-ons
   ============================================================ */
function Screen4({ form, set, onNext, onBack }) {
  const isMemorial = (form.roomTypes || []).includes("memorial");
  const setExtra   = (k, v) => set("extras", { ...form.extras, [k]: v });
  const ex         = form.extras;
  const hideImg    = (e) => { e.currentTarget.style.display = "none"; };
  // Did they draw a layout? Only then can we pop the canvas up to mark walls/edges.
  const hasLayout  = form.geometryMode === "canvas" && (form.blocks || []).length > 0;
  const setBlocks  = (b) => set("blocks", b);

  return (
    <div className="qf-screen fade-up">
      <div className="qf-head">
        <div className="kicker" style={{ color: "var(--teal)" }}>Step 4 of 5</div>
        <h3 className="qf-screen-title">{isMemorial ? "Any extras?" : "Add-ons"}</h3>
        {!isMemorial && (
          <p className="lede" style={{ marginTop:8, fontSize:16 }}>
            Standard cutouts (sink, hob, tap holes) are confirmed when we visit — no need to list them here.
          </p>
        )}
      </div>

      {isMemorial ? (
        <div className="qf-section">
          <label className="qf-section-label" htmlFor="mem-extras">Any special requirements?</label>
          <textarea id="mem-extras" className="textarea" rows={4} value={form.notes}
            onChange={e => set("notes", e.target.value)}
            placeholder="e.g. Specific inscription, photo etching, vase holder, border detail..." />
        </div>
      ) : (
        <React.Fragment>
          {/* Included as standard */}
          <div className="qf-section">
            <div className="qf-section-label">Included as standard</div>
            <div className="qf-addon-card included">
              <span className="qf-addon-img">
                <FinishIcon id="upstand" />
                <img src="/images/finish-upstand.jpg" alt="" onError={hideImg} />
              </span>
              <span className="qf-addon-text">
                <span className="qf-addon-title">A small stone lip along the back</span>
                <span className="qf-addon-desc">A 100mm stone upstand runs along your back walls — cleaner than tiles, no extra cost.</span>
              </span>
              <span className="qf-addon-check is-included">✓ Included</span>
            </div>
          </div>

          {/* Add-ons — tap cards, no measurements */}
          <div className="qf-section">
            <div className="qf-section-label">Add anything you'd like</div>
            <p style={{ marginTop: -4, marginBottom: 16, fontSize: 13.5, color: "var(--grey)", lineHeight: 1.6, maxWidth: 540 }}>
              Tap to add — we measure everything exactly when we visit, so there's nothing to size here.
            </p>
            <div className="qf-addon-list">
              {/* Splashback behind the hob */}
              <div className={"qf-addon-card" + (ex.hobPanel ? " selected" : "")}>
                <button type="button" className="qf-addon-toggle" aria-pressed={!!ex.hobPanel} onClick={() => setExtra("hobPanel", !ex.hobPanel)}>
                  <span className="qf-addon-img"><FinishIcon id="hob" /><img src="/images/finish-hob.jpg" alt="" onError={hideImg} /></span>
                  <span className="qf-addon-text">
                    <span className="qf-addon-title">Tall splashback behind the hob <span className="qf-addon-badge">Recommended</span></span>
                    <span className="qf-addon-desc">A taller stone panel behind the hob — protects the wall and ties in with the worktop.</span>
                  </span>
                  <span className="qf-addon-check">{ex.hobPanel ? "✓ Added" : "Add"}</span>
                </button>
              </div>

              {/* Full-height splashback */}
              <div className={"qf-addon-card" + (ex.fullHeight ? " selected" : "")}>
                <button type="button" className="qf-addon-toggle" aria-pressed={!!ex.fullHeight} onClick={() => setExtra("fullHeight", !ex.fullHeight)}>
                  <span className="qf-addon-img"><FinishIcon id="fullheight" /><img src="/images/finish-fullheight.jpg" alt="" onError={hideImg} /></span>
                  <span className="qf-addon-text">
                    <span className="qf-addon-title">Full-height splashback</span>
                    <span className="qf-addon-desc">Stone all the way up to your wall units, instead of tiles.</span>
                  </span>
                  <span className="qf-addon-check">{ex.fullHeight ? "✓ Added" : "Add"}</span>
                </button>
                {ex.fullHeight && hasLayout && (
                  <div className="qf-addon-extra qf-addon-canvas">
                    <span>Tap the walls it runs up — full height is priced by the metre, so it's the dearest add-on.</span>
                    <EdgeMarker blocks={form.blocks} onBlocks={setBlocks} edgeKey="fullHeightEdges" />
                  </div>
                )}
              </div>

              {/* Window sill */}
              <div className={"qf-addon-card" + (ex.windowSill > 0 ? " selected" : "")}>
                <button type="button" className="qf-addon-toggle" aria-pressed={ex.windowSill > 0} onClick={() => setExtra("windowSill", ex.windowSill > 0 ? 0 : 1)}>
                  <span className="qf-addon-img"><FinishIcon id="sill" /><img src="/images/finish-sill.jpg" alt="" onError={hideImg} /></span>
                  <span className="qf-addon-text">
                    <span className="qf-addon-title">Stone window sill</span>
                    <span className="qf-addon-desc">A matching stone board on your window ledge.</span>
                  </span>
                  <span className="qf-addon-check">{ex.windowSill > 0 ? "✓ Added" : "Add"}</span>
                </button>
                {ex.windowSill > 0 && (
                  <React.Fragment>
                    <div className="qf-addon-extra"><span>How many?</span>
                      <Stepper value={ex.windowSill} onChange={v => setExtra("windowSill", Math.max(1, v))} min={1} max={6} />
                    </div>
                    <div className="qf-addon-extra"><span>Rough size?</span>
                      <div className="qf-size-pick">
                        {[["s","Small",0.7],["m","Medium",1.1],["l","Large",1.6]].map(([k,label,w]) => (
                          <button key={k} type="button"
                            className={"qf-size-btn" + ((ex.windowSillWidth || 1.1) === w ? " on" : "")}
                            onClick={() => setExtra("windowSillWidth", w)}>{label}</button>
                        ))}
                      </div>
                    </div>
                  </React.Fragment>
                )}
              </div>

              {/* Drainer grooves */}
              <div className={"qf-addon-card" + (ex.drainerGrooves > 0 ? " selected" : "")}>
                <button type="button" className="qf-addon-toggle" aria-pressed={ex.drainerGrooves > 0} onClick={() => setExtra("drainerGrooves", ex.drainerGrooves > 0 ? 0 : 1)}>
                  <span className="qf-addon-img"><FinishIcon id="drainer" /><img src="/images/finish-drainer.jpg" alt="" onError={hideImg} /></span>
                  <span className="qf-addon-text">
                    <span className="qf-addon-title">Drainer grooves</span>
                    <span className="qf-addon-desc">Grooves cut into the stone beside the sink so water runs off.</span>
                  </span>
                  <span className="qf-addon-check">{ex.drainerGrooves > 0 ? "✓ Added" : "Add"}</span>
                </button>
                {ex.drainerGrooves > 0 && (
                  <div className="qf-addon-extra"><span>How many?</span>
                    <Stepper value={ex.drainerGrooves} onChange={v => setExtra("drainerGrooves", Math.max(1, v))} min={1} max={4} />
                  </div>
                )}
              </div>

              {/* Waterfall edge */}
              <div className={"qf-addon-card" + (ex.waterfall ? " selected" : "")}>
                <button type="button" className="qf-addon-toggle" aria-pressed={!!ex.waterfall} onClick={() => setExtra("waterfall", !ex.waterfall)}>
                  <span className="qf-addon-img"><FinishIcon id="waterfall" /><img src="/images/finish-waterfall.jpg" alt="" onError={hideImg} /></span>
                  <span className="qf-addon-text">
                    <span className="qf-addon-title">Waterfall edge</span>
                    <span className="qf-addon-desc">Stone flowing down the side of the island to the floor.</span>
                  </span>
                  <span className="qf-addon-check">{ex.waterfall ? "✓ Added" : "Add"}</span>
                </button>
                {ex.waterfall && (hasLayout ? (
                  <div className="qf-addon-extra qf-addon-canvas">
                    <span>Tap the side it drops down — a waterfall on a long island costs more than on a short run, so we price the edge you pick.</span>
                    <EdgeMarker blocks={form.blocks} onBlocks={setBlocks} edgeKey="waterfallEdges" />
                  </div>
                ) : (
                  <div className="qf-addon-extra"><span>How many ends?</span>
                    <Stepper value={ex.waterfallEnds || 1} onChange={v => setExtra("waterfallEnds", v)} min={1} max={4} />
                  </div>
                ))}
              </div>
            </div>
          </div>

          {/* Cutouts — informational only */}
          <div className="qf-section">
            <div style={{ padding: "14px 16px", background: "rgba(15,37,53,0.04)", borderLeft: "2px solid var(--teal)", fontSize: 13.5, color: "var(--slate)", lineHeight: 1.6 }}>
              <strong>Sink, hob &amp; tap cutouts</strong> are marked at the template visit — no need to add them here.
            </div>
          </div>
        </React.Fragment>
      )}

      <div className="qf-nav">
        <button type="button" className="btn-text" onClick={onBack}>Back</button>
        <button type="button" className="btn btn-teal" onClick={onNext}>
          Continue <span className="arr">→</span>
        </button>
      </div>
    </div>
  );
}

/* ============================================================
   Screen 5 — Contact + estimate
   ============================================================ */
function Screen5({ form, set, onSubmit, onBack, submitting }) {
  // Estimate is intentionally NOT shown on screen — it is emailed to the
  // customer instead (see ThankYou + the "Customer Estimate Email" Make.com
  // scenario). computeEstimate still runs in buildPayload so the band is
  // captured in Airtable. This keeps competitors from harvesting live pricing.
  const valid = form.name && form.email && form.phone;

  return (
    <div className="qf-screen fade-up">
      <div className="qf-head">
        <div className="kicker" style={{ color: "var(--teal)" }}>Step 5 of 5</div>
        <h3 className="qf-screen-title">Your details</h3>
        <p className="lede" style={{ marginTop:8, fontSize:16 }}>How to reach you, and when.</p>
      </div>

      <div className="qf-contact-grid">
        <div className="field">
          <label htmlFor="qf-name">Full name <span className="req">*</span></label>
          <input id="qf-name" className="input" required value={form.name}
            onChange={e => set("name", e.target.value)} placeholder="e.g. Aoife Curran" />
        </div>
        <div className="field">
          <label htmlFor="qf-email">Email <span className="req">*</span></label>
          <input id="qf-email" className="input" required type="email" value={form.email}
            onChange={e => set("email", e.target.value)} placeholder="you@example.ie" />
        </div>
        <div className="field">
          <label htmlFor="qf-phone">Phone <span className="req">*</span></label>
          <input id="qf-phone" className="input" required type="tel" value={form.phone}
            onChange={e => set("phone", e.target.value)} placeholder="e.g. 086 123 4567" />
        </div>
        <div className="field">
          <label htmlFor="qf-county">County</label>
          <select id="qf-county" className="select" value={form.county || ""}
            onChange={e => set("county", e.target.value)}>
            <option value="">Select county…</option>
            {["Carlow","Cavan","Clare","Cork","Donegal","North Dublin","South Dublin","Dublin City","West Dublin","Galway","Kerry","Kildare","Kilkenny","Laois","Leitrim","Limerick","Longford","Louth","Mayo","Meath","Monaghan","Offaly","Roscommon","Sligo","Tipperary","Waterford","Westmeath","Wexford","Wicklow","Antrim","Armagh","Down","Fermanagh","Derry","Tyrone"].map(c => (
              <option key={c} value={c}>{c}</option>
            ))}
          </select>
        </div>
        <div className="field">
          <label htmlFor="qf-eircode">Eircode</label>
          <input id="qf-eircode" className="input" value={form.eircode}
            onChange={e => set("eircode", e.target.value)} placeholder="e.g. N91 X4F0" />
          <div style={{marginTop:6, fontSize:12.5, color:"var(--grey)", lineHeight:1.4}}>No eircode? Use a neighbour's and leave a note below.</div>
        </div>
        <div className="field">
          <label htmlFor="qf-address">Address</label>
          <input id="qf-address" className="input" value={form.address}
            onChange={e => set("address", e.target.value)} placeholder="e.g. 12 Main Street, Kilbeggan" />
        </div>
      </div>

      <div className="field" style={{ maxWidth:360, marginTop:4 }}>
        <label htmlFor="qf-timeline">Timeline</label>
        <select id="qf-timeline" className="select" value={form.timeline}
          onChange={e => set("timeline", e.target.value)}>
          <option value="">Select...</option>
          {TIMELINES.map(t => <option key={t} value={t}>{t}</option>)}
        </select>
      </div>

      <div className="field" style={{ marginTop:28 }}>
        <label htmlFor="qf-notes">Anything else we should know</label>
        <textarea id="qf-notes" className="textarea" rows={4} value={form.notes}
          onChange={e => set("notes", e.target.value)}
          placeholder="Sink type, hob model, edge detail, anything about the space..." />
      </div>

      <div className="qf-nav" style={{ marginTop:48 }}>
        <button type="button" className="btn-text" onClick={onBack}>Back</button>
        <button type="button" className="btn btn-teal" style={{ padding:"20px 36px" }}
          onClick={onSubmit} disabled={!valid || submitting}>
          {submitting ? "Sending…" : "Send my quote"} <span className="arr">→</span>
        </button>
      </div>
    </div>
  );
}

/* ============================================================
   Save-later bar
   ============================================================ */
function SaveLaterBar({ form, step }) {
  const [saved, setSaved] = React.useState(false);
  if (step === 1) return null;
  const save = () => {
    saveDraft({ ...form, _step: step });
    setSaved(true);
    setTimeout(() => setSaved(false), 3000);
  };
  return (
    <div className="qf-save-bar">
      <button type="button" className="qf-save-btn" onClick={save}>
        {saved ? "Saved in this browser ✓" : "Save & finish later"}
      </button>
    </div>
  );
}

/* ============================================================
   Thank you
   ============================================================ */
function ThankYou({ name, email }) {
  const steps = [
    { day:"Today",       label:"Confirmation email", desc:"Lands in your inbox within a few minutes." },
    { day:"Within 24h",  label:"Costed estimate",    desc:"Our office prepares an all-in figure based on what you've told us." },
    { day:"When ready",  label:"Book the measure",   desc:"We'll send you a booking link to pick a date that suits. We measure once your kitchen units are installed, level and ready to template." },
    { day:"Measure day", label:"On-site template",   desc:"Allow 45 minutes to an hour and a half on site — sometimes longer for complex layouts. Our measurer uses a digital laser jig, no worktop surface needed yet." },
    { day:"7–10 days",   label:"Fitted & sealed",    desc:"Most in-stock worktops are fitted within 7–10 days of the measure." },
  ];
  return (
    <div className="qf-ty">
      <div className="kicker" style={{ color:"var(--teal)", marginBottom:24 }}>Quote request received</div>
      <h2 className="title" style={{ maxWidth:720, margin:"0 auto", color:"var(--navy)" }}>
        Thank you{name ? ", " + name.split(" ")[0] : ""}.<br />
        <em className="pull" style={{ color:"var(--teal)" }}>We'll be in touch within 24 hours.</em>
      </h2>
      <p className="lede" style={{ maxWidth:560, margin:"32px auto 0" }}>
        Thanks{name ? " " + name.split(" ")[0] : ""} — your estimate is on its way
        {email ? " to " + email : ""}. Please note this is an automated estimate
        only — your confirmed quote will follow within 24 hours, once our office
        has reviewed your details and arranged a free site measure.
      </p>
      <p className="lede" style={{ maxWidth:560, margin:"18px auto 0", fontSize:16 }}>
        Here's exactly what happens next — no surprises.
      </p>
      <div className="qf-next-steps">
        {steps.map((s, i, a) => (
          <div key={i} className="qf-next-step"
            style={{ borderBottom: i < a.length - 1 ? "1px solid var(--line)" : "none" }}>
            <div className="qf-next-day">{s.day}</div>
            <div>
              <div className="qf-next-label">{s.label}</div>
              <div className="qf-next-desc">{s.desc}</div>
            </div>
          </div>
        ))}
      </div>
      <div className="qf-ty-contact">
        Need us sooner? {CONTACT.phoneDisplay} · {CONTACT.email}
      </div>
    </div>
  );
}

/* ============================================================
   Layout SVG generator
   ============================================================ */
function generateLayoutSVG(blocks) {
  const S = 560;
  const runFill    = "#C9DFE8", runStroke    = "rgba(27,61,90,0.38)";
  const islandFill = "#E7DDD0", islandStroke = "rgba(100,80,60,0.38)";
  const sillFill   = "#D6D0C4", sillStroke   = "rgba(100,88,72,0.45)";
  const sbFill     = "#A8CCDC";

  const panelPct = 11;

  // Helper: render one edge panel strip for a block
  function edgePanel(b, edge) {
    let px, py, pw, ph;
    if (edge === "top")    { px = b.x; py = Math.max(0, b.y - panelPct); pw = b.w; ph = Math.min(panelPct, b.y); }
    else if (edge === "bottom") { px = b.x; py = b.y + b.h; pw = b.w; ph = Math.min(panelPct, 100 - (b.y + b.h)); }
    else if (edge === "left")   { px = Math.max(0, b.x - panelPct); py = b.y; pw = Math.min(panelPct, b.x); ph = b.h; }
    else                        { px = b.x + b.w; py = b.y; pw = Math.min(panelPct, 100 - (b.x + b.w)); ph = b.h; }
    if (pw <= 0 || ph <= 0) return "";
    const x = px/100*S, y = py/100*S, w = pw/100*S, h = ph/100*S;
    const cx = x + w/2, cy = y + h/2;
    return { x, y, w, h, cx, cy, isVertical: h > w * 1.5 };
  }

  // Full-height splashback panels (green hatch) — stored as fullHeightEdges
  const fhPanels = blocks.flatMap(b => (b.fullHeightEdges || []).map(edge => {
    const p = edgePanel(b, edge); if (!p) return "";
    const rot = p.isVertical ? ` transform="rotate(-90,${p.cx.toFixed(1)},${p.cy.toFixed(1)})"` : "";
    return `<rect x="${p.x.toFixed(1)}" y="${p.y.toFixed(1)}" width="${p.w.toFixed(1)}" height="${p.h.toFixed(1)}"
      fill="url(#fhHatch)" stroke="rgba(30,120,65,0.8)" stroke-width="1.5"/>
    <text x="${p.cx.toFixed(1)}" y="${p.cy.toFixed(1)}" text-anchor="middle" dominant-baseline="middle"
      font-family="sans-serif" font-size="7.5" font-weight="700" fill="rgba(10,80,40,0.95)"${rot}>SPLASHBACK</text>`;
  })).join("\n  ");

  // Waterfall panels (blue hatch) — stored as waterfallEdges
  const wfPanels = blocks.flatMap(b => (b.waterfallEdges || []).map(edge => {
    const p = edgePanel(b, edge); if (!p) return "";
    const rot = p.isVertical ? ` transform="rotate(-90,${p.cx.toFixed(1)},${p.cy.toFixed(1)})"` : "";
    return `<rect x="${p.x.toFixed(1)}" y="${p.y.toFixed(1)}" width="${p.w.toFixed(1)}" height="${p.h.toFixed(1)}"
      fill="url(#wfHatch)" stroke="rgba(27,61,130,0.75)" stroke-width="1.5"/>
    <text x="${p.cx.toFixed(1)}" y="${p.cy.toFixed(1)}" text-anchor="middle" dominant-baseline="middle"
      font-family="sans-serif" font-size="7.5" font-weight="700" fill="rgba(15,40,110,0.9)"${rot}>WATERFALL</text>`;
  })).join("\n  ");

  // Worktop blocks
  const blockEls = blocks.map((b, i) => {
    const x = b.x/100*S, y = b.y/100*S, w = b.w/100*S, h = b.h/100*S;
    const cx = x + w/2, cy = y + h/2;
    const hasFH  = (b.fullHeightEdges || []).length > 0;
    const fill   = hasFH ? sbFill : b.type === "island" ? islandFill : b.type === "sill" ? sillFill : runFill;
    const stroke = b.type === "island" ? islandStroke : b.type === "sill" ? "rgba(100,88,72,0.45)" : runStroke;
    const letter = b.type === "sill" ? "Sill" : String.fromCharCode(65 + i);
    const lenDim = parseMeasure(b.length).toFixed(2) + "m";
    const depDim = parseMeasure(b.depth).toFixed(2) + "m";
    const small  = w < 44 || h < 22;
    const tiny   = w < 22 || h < 12;
    return `<rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${w.toFixed(1)}" height="${h.toFixed(1)}"
      fill="${fill}" stroke="${stroke}" stroke-width="1.5" rx="1"/>
    ${!tiny ? `<text x="${cx.toFixed(1)}" y="${(cy + (small ? 1 : -7)).toFixed(1)}" text-anchor="middle" dominant-baseline="middle"
      font-family="monospace" font-size="${small ? 10 : 12}" font-weight="700" fill="#1B3D5A">${letter}  ${lenDim}</text>` : ""}
    ${!small ? `<text x="${cx.toFixed(1)}" y="${(cy + 8).toFixed(1)}" text-anchor="middle" dominant-baseline="middle"
      font-family="sans-serif" font-size="9" fill="#5E7A8F">↔ ${depDim}</text>` : ""}`;
  }).join("\n  ");

  // 45° mitre cuts — where two run-type blocks share a corner
  const TOL = 5; // % tolerance for corner proximity
  const mitreSvg = [];
  for (let i = 0; i < blocks.length; i++) {
    for (let j = i + 1; j < blocks.length; j++) {
      const a = blocks[i], b = blocks[j];
      if (a.type !== "run" || b.type !== "run") continue;
      const aHoriz = a.w >= a.h;
      const bHoriz = b.w >= b.h;
      if (aHoriz === bHoriz) continue; // parallel runs — no mitre needed
      const aCorners = [
        { x: a.x,     y: a.y,     dx: -1, dy: -1 },
        { x: a.x+a.w, y: a.y,     dx:  1, dy: -1 },
        { x: a.x,     y: a.y+a.h, dx: -1, dy:  1 },
        { x: a.x+a.w, y: a.y+a.h, dx:  1, dy:  1 },
      ];
      const bCorners = [
        { x: b.x,     y: b.y,     dx: -1, dy: -1 },
        { x: b.x+b.w, y: b.y,     dx:  1, dy: -1 },
        { x: b.x,     y: b.y+b.h, dx: -1, dy:  1 },
        { x: b.x+b.w, y: b.y+b.h, dx:  1, dy:  1 },
      ];
      for (const ac of aCorners) {
        for (const bc of bCorners) {
          if (Math.abs(ac.x - bc.x) < TOL && Math.abs(ac.y - bc.y) < TOL) {
            const px = (ac.x + bc.x) / 2;
            const py = (ac.y + bc.y) / 2;
            // Mitre size proportional to the shallower worktop depth, capped visually
            const ms = Math.min(Math.min(a.w, a.h), Math.min(b.w, b.h), 9) * 0.65;
            // p1: along A's long axis inward from this corner
            const p1x = px + (aHoriz ? -ac.dx * ms : 0);
            const p1y = py + (aHoriz ? 0 : -ac.dy * ms);
            // p2: along B's long axis inward from this corner
            const p2x = px + (bHoriz ? -bc.dx * ms : 0);
            const p2y = py + (bHoriz ? 0 : -bc.dy * ms);
            // Convert to SVG coords
            const spx  = px/100*S,  spy  = py/100*S;
            const sp1x = p1x/100*S, sp1y = p1y/100*S;
            const sp2x = p2x/100*S, sp2y = p2y/100*S;
            mitreSvg.push(
              `<polygon points="${spx.toFixed(1)},${spy.toFixed(1)} ${sp1x.toFixed(1)},${sp1y.toFixed(1)} ${sp2x.toFixed(1)},${sp2y.toFixed(1)}"
      fill="rgba(255,240,180,0.92)" stroke="rgba(150,100,0,0.85)" stroke-width="1.4" stroke-linejoin="round"/>
    <line x1="${sp1x.toFixed(1)}" y1="${sp1y.toFixed(1)}" x2="${sp2x.toFixed(1)}" y2="${sp2y.toFixed(1)}"
      stroke="rgba(150,100,0,0.85)" stroke-width="1.4" stroke-dasharray="3 2"/>`
            );
          }
        }
      }
    }
  }

  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${S} ${S}" width="${S}" height="${S}">
  <defs>
    <pattern id="g" width="20" height="20" patternUnits="userSpaceOnUse">
      <path d="M20 0L0 0 0 20" fill="none" stroke="rgba(27,61,90,0.07)" stroke-width="1"/>
    </pattern>
    <pattern id="fhHatch" width="7" height="7" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
      <rect width="7" height="7" fill="rgba(52,168,100,0.13)"/>
      <line x1="0" y1="0" x2="0" y2="7" stroke="rgba(30,120,65,0.5)" stroke-width="2.8"/>
    </pattern>
    <pattern id="wfHatch" width="7" height="7" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
      <rect width="7" height="7" fill="rgba(52,100,200,0.10)"/>
      <line x1="0" y1="0" x2="0" y2="7" stroke="rgba(27,61,130,0.45)" stroke-width="2.8"/>
    </pattern>
  </defs>
  <rect width="${S}" height="${S}" fill="#FAFBFB"/>
  <rect width="${S}" height="${S}" fill="url(#g)"/>
  ${fhPanels}
  ${wfPanels}
  ${blockEls}
  ${mitreSvg.join("\n  ")}
</svg>`;
}

/* Generates a simple SVG shell that embeds a customer-uploaded photo/plan
   so the office always receives a displayable layout image regardless of route. */
function generatePhotoSVG(dataUrl, fileName) {
  const S = 560;
  const safeFile = (fileName || "customer plan").replace(/[<>&"]/g, " ");
  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 ${S} ${S}" width="${S}" height="${S}">
  <rect width="${S}" height="${S}" fill="#F6F6F4"/>
  <image href="${dataUrl}" x="10" y="10" width="${S - 20}" height="${S - 30}" preserveAspectRatio="xMidYMid meet"/>
  <text x="${S/2}" y="${S - 6}" text-anchor="middle" font-family="sans-serif" font-size="10" fill="rgba(27,61,90,0.45)">${safeFile}</text>
</svg>`;
}

function downloadLayoutSVG(blocks, customerName) {
  const svg  = generateLayoutSVG(blocks);
  const blob = new Blob([svg], { type: "image/svg+xml" });
  const url  = URL.createObjectURL(blob);
  const a    = document.createElement("a");
  a.href     = url;
  a.download = "egan-stone-layout" + (customerName ? "-" + customerName.split(" ")[0].toLowerCase() : "") + ".svg";
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}

/* ============================================================
   Payload builders
   ============================================================ */
function buildPayload(form) {
  const lm = totalLinearMetres(form.blocks);
  const pathTaken = (form.geometryMode === "photo" || !lm || !form.material || form.material === "unsure")
    ? "photo-review" : "auto-quote";
  const estimate = computeEstimate(form);
  const colourList = (form.colours && form.colours.length)
    ? form.colours
    : (form.colour ? [form.colour] : []);
  const colourSelection = colourList.join(", ");
  return {
    roomType:         (form.roomTypes || []).join(", ") + (form.hasIsland ? " + island" : ""),
    material:         form.material,
    finish:           colourSelection || form.finish,
    colour:           colourSelection || null,
    colours:          colourList,
    colourSelection:  colourSelection || null,
    thickness:        form.thickness,
    edgeProfile:      form.edgeProfile,
    geometry:         form.blocks.map(b => ({
      label:      b.name,
      type:       b.type,
      lengthMm:   Math.round(parseMeasure(b.length) * 1000),
      widthMm:    Math.round(parseMeasure(b.depth)  * 1000),
      splashback: !!b.splashback,
    })),
    totalLinearMetres: Math.round(lm * 100) / 100,
    photoUploadUrl:    null,
    photoFileName:     form.photoFileName || null,
    planUpload:        (form.planUpload && form.planUpload.dataUrl) ? form.planUpload : null,
    planUploadTooLarge: !!(form.planUpload && form.planUpload.tooLarge),
    splashbackRuns:    [form.extras.fullHeight ? "Full-height splashback" : null, form.extras.hobPanel ? "Hob splashback" : null].filter(Boolean).join("; ") || null,
    windowSillCount:   form.extras.windowSill || 0,
    fullHeightMetres:  form.extras.fullHeight ? Math.round(sumEdges(form.blocks, "fullHeightEdges") * 100) / 100 : 0,
    waterfallMetres:   form.extras.waterfall  ? Math.round(sumEdges(form.blocks, "waterfallEdges")  * 100) / 100 : 0,
    ...form.extras,
    timeline:     form.timeline,
    contact:      { name: form.name, email: form.email, phone: form.phone, postcode: form.eircode, county: form.county, address: form.address },
    estimateBand:    estimate ? (formatEuro(estimate.min) + "–" + formatEuro(estimate.max)) : null,
    splashbackTotal: estimate ? estimate.splashbackTotal : 0,
    submittedAt:     new Date().toISOString(),
    pathTaken,
    notes:           form.notes,
  };
}

function buildEmailBody(p) {
  return [
    "Name: "    + p.contact.name,
    "Phone: "   + p.contact.phone,
    "Email: "   + p.contact.email,
    "Eircode: " + p.contact.postcode,
    "",
    "Room: "      + p.roomType,
    "Material: "  + p.material,
    "Colour(s): " + (p.colourSelection || "Not specified"),
    "Thickness: " + p.thickness,
    "Edge: "      + p.edgeProfile,
    "Total LM: "  + p.totalLinearMetres + "m",
    "",
    "--- LAYOUT ---",
    p.pathTaken === "auto-quote"
      ? "Layout drawing attached (egan-stone-layout.svg) — open in any browser or image viewer."
      : p.photoFileName ? "Customer uploaded: " + p.photoFileName : "No drawing provided.",
    "",
    ...p.geometry.map(g => {
      const type  = g.type === "sill" ? "[SILL]" : g.type === "island" ? "[ISLAND]" : "[RUN]";
      const dims  = (g.lengthMm/1000).toFixed(2) + "m x " + (g.widthMm/1000).toFixed(2) + "m";
      const sb    = g.splashback ? "  << SPLASHBACK" : "";
      return "  " + type + " " + g.label + ":  " + dims + sb;
    }),
    "  Total worktop LM: " + p.totalLinearMetres + "m",
    "",
    "--- WALL FINISHES ---",
    "100mm upstand: "          + (p.upstand100 ? "yes (included)" : "no"),
    "Hob splashback: "         + (p.hobPanel ? "yes" : "no"),
    "Full-height splashback: " + (p.fullHeight ? "yes" + (p.fullHeightMetres ? " (~" + p.fullHeightMetres + "m of wall marked)" : "") : "no"),
    "Window sills: "           + (p.windowSill || 0) + (p.windowSill ? " (~" + (p.windowSillWidth || 1.1) + "m each)" : ""),
    "",
    "--- MACHINING ---",
    "Drainer grooves: "     + (p.drainerGrooves || 0),
    "Waterfall edge: "      + (p.waterfall ? "yes" + (p.waterfallMetres ? " (~" + p.waterfallMetres + "m marked)" : " (" + (p.waterfallEnds || 1) + " end)") : "no"),
    "",
    "Estimate: " + (p.estimateBand || "N/A"),
    "Timeline: " + (p.timeline || "Not specified"),
    "Path: "     + p.pathTaken,
    "",
    "Notes:",
    p.notes || "None",
  ].join("\n");
}

/* ============================================================
   Main component
   ============================================================ */
const EMPTY_FORM = {
  roomTypes: [], hasIsland: false,
  material: "", finish: "", colour: "", colours: [], thickness: "30mm", edgeProfile: "square",
  geometryMode: null, selectedTemplate: null,
  blocks: [
    { id:"run-1", type:"run", name:"Run A", x:26, y:45, w:48, h:10, rotated:false, length:"3.0", depth:"0.62", splashbackEdges:[] },
  ],
  photoFileName: "", planUpload: null,
  extras: {
    upstand100: true, hobPanel: true, fullHeight: false,
    windowSill: 0, windowSillWidth: 1.1, drainerGrooves: 0,
    waterfall: false, waterfallEnds: 1,
  },
  name:"", email:"", phone:"", eircode:"", county:"", address:"", timeline:"", notes:"",
};

function Quotes({ navigate }) {
  const [step, setStep]           = React.useState(1);
  const [submitted, setSubmitted] = React.useState(false);
  const [submitting, setSubmitting] = React.useState(false);
  const [form, setForm]           = React.useState(() => loadDraft() || { ...EMPTY_FORM });

  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
  const formRef = React.useRef(null);

  const scrollUp = () => {
    if (formRef.current) {
      const top = formRef.current.getBoundingClientRect().top + window.scrollY - 24;
      window.scrollTo({ top, behavior: "smooth" });
    }
  };

  const next = () => { setStep(s => Math.min(5, s + 1)); scrollUp(); };
  const back = () => { setStep(s => Math.max(1, s - 1)); scrollUp(); };

  const submit = async (e) => {
    e.preventDefault();
    setSubmitting(true);
    const payload = buildPayload(form);
    if (form.geometryMode === "canvas" && form.blocks.length > 0) {
      payload.layoutSVG = generateLayoutSVG(form.blocks);
    } else if (form.planUpload && form.planUpload.dataUrl) {
      // Photo route: embed the uploaded plan image in an SVG so the office
      // always receives a displayable layout regardless of how it was captured.
      payload.layoutSVG = generatePhotoSVG(form.planUpload.dataUrl, form.planUpload.name);
    }
    try {
      const res = await fetch("/api/quote", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload),
      });
      if (!res.ok) throw new Error("API error " + res.status);
    } catch (err) {
      // Fallback to mailto if API fails
      const subject = encodeURIComponent("Quote request — " + (form.name || "website visitor"));
      const body    = encodeURIComponent(buildEmailBody(payload));
      window.open("mailto:" + CONTACT.email + "?subject=" + subject + "&body=" + body);
    }
    clearDraft();
    setSubmitted(true);
    setSubmitting(false);
    window.scrollTo({ top: 0, behavior: "smooth" });
  };

  return (
    <React.Fragment>
      <PageHero
        kicker="Quotes"
        title={
          <React.Fragment>
            Get a quote.<br />
            <em className="pull" style={{ color:"#7CC3D8" }}>Fast, honest, no obligation.</em>
          </React.Fragment>
        }
        sub="Five quick steps. Most estimates are back within 24 hours."
      />

      <section ref={formRef} className="section" style={{ paddingTop:"clamp(48px, 6vw, 80px)" }}>
        <div className="container" style={{ maxWidth:880 }}>
          {submitted ? (
            <ThankYou name={form.name} email={form.email} />
          ) : (
            <form onSubmit={submit} noValidate>
              <ProgressBar step={step} />
              <SaveLaterBar form={form} step={step} />

              {step === 1 && <Screen1 form={form} set={set} onNext={next} />}
              {step === 2 && <Screen2 form={form} set={set} onNext={next} onBack={back} />}
              {step === 3 && <Screen3 form={form} set={set} onNext={next} onBack={back} />}
              {step === 4 && <Screen4 form={form} set={set} onNext={next} onBack={back} />}
              {step === 5 && <Screen5 form={form} set={set} onSubmit={submit} onBack={back} submitting={submitting} />}
            </form>
          )}
        </div>
      </section>

      {!submitted && step < 5 && (
        <section className="section lt tight">
          <div className="container" style={{ maxWidth:1100 }}>
            <div style={{ display:"grid", gridTemplateColumns:"repeat(3,1fr)", gap:48 }}>
              {[
                { h:"24-hour turnaround", d:"Most quotes returned the next working day. If we're busy, we'll say so." },
                { h:"No obligation",      d:"Free estimate, free site measure. Nothing owed until you sign the order." },
                { h:"Your details stay here", d:"We don't share or sell your enquiry. Used only to cost your project." },
              ].map((b, i) => (
                <div key={i}>
                  <div className="kicker" style={{ marginBottom:14, color:"var(--teal)" }}>0{i+1}</div>
                  <div style={{ fontFamily:"var(--serif)", fontSize:22, fontWeight:500, color:"var(--navy)", marginBottom:8 }}>{b.h}</div>
                  <div style={{ fontSize:14.5, color:"var(--grey)", lineHeight:1.65 }}>{b.d}</div>
                </div>
              ))}
            </div>
          </div>
        </section>
      )}
    </React.Fragment>
  );
}

window.Quotes = Quotes;
