/* ============================================================
   Lumino — Doodle Studio
   A whiteboard for stamping doodles, adding Caveat text, and
   sketching freehand. Export PNG or SVG.
   ============================================================ */
(function () {
  const { useState, useEffect, useRef, useMemo, useCallback } = React;

  LuminoChrome.mount({ active: "doodle", pageLabel: "Doodle Studio" });

  // ── Palette ──────────────────────────────────────────
  const INK_PALETTE = [
    { name: "ink",      color: "#2B2420" },
    { name: "marker",   color: "#E8A87C" },
    { name: "berry",    color: "#C4538F" },
    { name: "forest",   color: "#5F8F6F" },
    { name: "ocean",    color: "#4F7FA3" },
    { name: "sunflower",color: "#E5B847" },
    { name: "rust",     color: "#B55230" },
    { name: "violet",   color: "#8863B0" },
    { name: "chalk",    color: "#FAF7F0" },
  ];
  const STROKES = [
    { id: "fine", label: "FINE", px: 1.8 },
    { id: "med",  label: "MED",  px: 2.6 },
    { id: "bold", label: "BOLD", px: 3.6 },
  ];
  const PAPERS = [
    { id: "white",  label: "White" },
    { id: "cream",  label: "Cream" },
    { id: "slate",  label: "Slate" },
    { id: "navy",   label: "Navy" },
    { id: "graph",  label: "Graph" },
    { id: "dot",    label: "Dots" },
    { id: "ruled",  label: "Ruled" },
    { id: "image",  label: "Image" },
  ];
  const DARK_PAPERS = new Set(["slate", "navy"]);

  // Tool SVG icons (inline so we don't depend on icon names)
  const ToolIcon = {
    select: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M5 3l7 16 2.5-6.5L21 10z"/></svg>),
    stamp:  (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3v7M8 10h8l-1 4H9zM5 18h14"/></svg>),
    pen:    (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M4 20l4-1 11-11-3-3L5 16l-1 4z"/></svg>),
    text:   (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M5 5h14M12 5v14M8 19h8"/></svg>),
    eraser: (<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M7 21h10M15 3l6 6-10 10H5v-6z"/></svg>),
  };

  const TOOLS = ["select", "stamp", "pen", "text", "eraser"];

  // ── DoodleStudio component ───────────────────────────
  function DoodleStudio() {
    const [tool, setTool] = useState("stamp");
    const [colorIdx, setColorIdx] = useState(0);
    const [strokeIdx, setStrokeIdx] = useState(1);
    const [paper, setPaper] = useState("cream");
    const [bgImage, setBgImage] = useState(null); // data URL for custom paper
    const [bgFit, setBgFit] = useState("cover"); // cover | contain | tile
    const bgInputRef = useRef(null);

    // Pan & zoom
    const [zoom, setZoom] = useState(1);
    const [pan, setPan] = useState({ x: 0, y: 0 });
    const [spaceDown, setSpaceDown] = useState(false);
    const panRef = useRef(null); // active panning drag state

    // Screen → world coordinate conversion
    const toWorld = (clientX, clientY) => {
      const r = stageRef.current.getBoundingClientRect();
      return {
        x: (clientX - r.left - pan.x) / zoom,
        y: (clientY - r.top  - pan.y) / zoom,
      };
    };

    // Zoom around a point (null center = viewport center). factor > 1 zooms in.
    const zoomAt = (point, factor) => {
      setZoom(z => {
        const next = Math.max(0.25, Math.min(4, z * factor));
        if (next === z) return z;
        const r = stageRef.current?.getBoundingClientRect();
        const cx = point?.x ?? (r ? r.width / 2 : 0);
        const cy = point?.y ?? (r ? r.height / 2 : 0);
        setPan(p => ({
          x: cx - ((cx - p.x) * (next / z)),
          y: cy - ((cy - p.y) * (next / z)),
        }));
        return next;
      });
    };
    const resetView = () => { setZoom(1); setPan({ x: 0, y: 0 }); };
    const fitView = () => {
      // Fit all items into viewport with 40px padding
      if (!items.length) { resetView(); return; }
      let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
      items.forEach(it => {
        if (it.kind === "stroke") {
          it.points.forEach(([x, y]) => { minX = Math.min(minX, x); minY = Math.min(minY, y); maxX = Math.max(maxX, x); maxY = Math.max(maxY, y); });
        } else {
          const s = Math.max(it.w ?? it.size ?? 80, it.h ?? it.size ?? 80) / 2;
          minX = Math.min(minX, it.x - s); minY = Math.min(minY, it.y - s);
          maxX = Math.max(maxX, it.x + s); maxY = Math.max(maxY, it.y + s);
        }
      });
      const r = stageRef.current.getBoundingClientRect();
      const pad = 40;
      const z = Math.min(4, Math.max(0.25, Math.min((r.width - pad*2) / (maxX - minX || 1), (r.height - pad*2) / (maxY - minY || 1))));
      setZoom(z);
      setPan({
        x: r.width / 2 - ((minX + maxX) / 2) * z,
        y: r.height / 2 - ((minY + maxY) / 2) * z,
      });
    };
    const [pickedDoodle, setPickedDoodle] = useState("rocket");
    const [ddQuery, setDdQuery] = useState("");
    const [ddCat, setDdCat] = useState("All");

    // Stage items: stamps, labels, strokes
    const [items, setItems] = useState([]);
    const [selectedId, setSelectedId] = useState(null);
    const [history, setHistory] = useState([]); // undo stack
    const stageRef = useRef(null);
    const drawingRef = useRef(null);
    const isDrawingRef = useRef(false);
    const [, forceTick] = useState(0);
    const rerender = () => forceTick(n => n + 1);

    // Adjust color if paper changes to keep contrast usable
    useEffect(() => {
      if (DARK_PAPERS.has(paper) && INK_PALETTE[colorIdx].name === "ink") setColorIdx(INK_PALETTE.findIndex(p => p.name === "chalk"));
      if (!DARK_PAPERS.has(paper) && INK_PALETTE[colorIdx].name === "chalk") setColorIdx(INK_PALETTE.findIndex(p => p.name === "ink"));
    }, [paper]);

    const currentColor = INK_PALETTE[colorIdx].color;
    const currentStroke = STROKES[strokeIdx].px;

    const pushHistory = useCallback(() => {
      setHistory(h => [...h.slice(-20), JSON.stringify(items)]);
    }, [items]);

    const undo = () => {
      setHistory(h => {
        if (!h.length) return h;
        const last = h[h.length - 1];
        setItems(JSON.parse(last));
        return h.slice(0, -1);
      });
    };

    const clear = () => {
      if (items.length && !confirm("Clear the whole board?")) return;
      pushHistory();
      setItems([]);
      setSelectedId(null);
    };

    // ── Add items ──────────────────────────────────────
    const nextId = useRef(1);
    const newId = () => "it_" + (nextId.current++);

    const addStamp = (name, x, y) => {
      pushHistory();
      const id = newId();
      setItems(items => [...items, {
        id, kind: "stamp", name,
        x, y, w: 84, h: 84, rot: (Math.random() * 6 - 3),
        color: currentColor, stroke: currentStroke,
        wobble: 1, lockAspect: true,
      }]);
      setSelectedId(id);
    };

    const addText = (x, y) => {
      pushHistory();
      const id = newId();
      const text = prompt("Label?", "idea!");
      if (!text) return;
      setItems(items => [...items, {
        id, kind: "text", text,
        x, y, size: 28, rot: (Math.random() * 6 - 3),
        color: currentColor,
      }]);
      setSelectedId(id);
    };

    // ── Pen tool ───────────────────────────────────────
    const startDraw = (e) => {
      const { x, y } = toWorld(e.clientX, e.clientY);
      pushHistory();
      const id = newId();
      const stroke = { id, kind: "stroke", points: [[x, y]], color: currentColor, width: currentStroke };
      drawingRef.current = stroke;
      isDrawingRef.current = true;
      setItems(items => [...items, stroke]);
    };
    const continueDraw = (e) => {
      if (!isDrawingRef.current || !drawingRef.current) return;
      const { x, y } = toWorld(e.clientX, e.clientY);
      drawingRef.current.points.push([x, y]);
      rerender();
    };
    const endDraw = () => {
      isDrawingRef.current = false;
      drawingRef.current = null;
    };

    // ── Stage click ────────────────────────────────────
    const onStageClick = (e) => {
      if (e.target !== stageRef.current && !e.target.classList.contains("ds-world")) return;
      if (panRef.current?.didPan) { panRef.current = null; return; }
      const { x, y } = toWorld(e.clientX, e.clientY);
      if (tool === "stamp") addStamp(pickedDoodle, x, y);
      else if (tool === "text") addText(x, y);
      else if (tool === "select") setSelectedId(null);
    };
    const onStageDown = (e) => {
      if (tool !== "pen") return;
      if (e.target !== stageRef.current && !e.target.classList.contains("ds-world")) return;
      startDraw(e);
    };

    // ── Item drag ──────────────────────────────────────
    const dragRef = useRef(null);
    const startDrag = (e, item) => {
      e.stopPropagation();
      if (tool === "eraser") {
        pushHistory();
        setItems(items => items.filter(i => i.id !== item.id));
        setSelectedId(null);
        return;
      }
      if (tool !== "select" && tool !== "stamp" && tool !== "text") return;
      setSelectedId(item.id);
      const w = toWorld(e.clientX, e.clientY);
      const ox = w.x - item.x;
      const oy = w.y - item.y;
      dragRef.current = { id: item.id, ox, oy };
    };
    const onDrag = (e) => {
      if (!dragRef.current) return;
      const { id, ox, oy } = dragRef.current;
      const w = toWorld(e.clientX, e.clientY);
      setItems(items => items.map(i => i.id === id ? { ...i, x: w.x - ox, y: w.y - oy } : i));
    };
    const endDrag = () => { dragRef.current = null; };

    // ── Resize handle ──────────────────────────────────
    const resizeRef = useRef(null);
    const startResize = (e, item) => {
      e.stopPropagation();
      resizeRef.current = {
        id: item.id, startX: e.clientX, startY: e.clientY,
        startW: item.w ?? item.size ?? 84,
        startH: item.h ?? item.size ?? 84,
        startSize: item.size, // for text
        kind: item.kind,
        locked: item.kind === "text" ? true : !!item.lockAspect,
      };
    };
    const onResize = (e) => {
      if (!resizeRef.current) return;
      const { id, startX, startY, startW, startH, startSize, kind, locked } = resizeRef.current;
      // Convert screen drag delta to world delta (undo zoom)
      const dx = (e.clientX - startX) / zoom, dy = (e.clientY - startY) / zoom;
      if (kind === "text") {
        const d = Math.max(dx, dy);
        const next = Math.max(12, Math.min(200, startSize + d));
        setItems(items => items.map(i => i.id === id ? { ...i, size: next } : i));
        return;
      }
      let nw, nh;
      if (locked || e.shiftKey) {
        const d = Math.max(dx, dy);
        const ratio = startH / startW;
        nw = Math.max(20, Math.min(500, startW + d));
        nh = Math.max(20, Math.min(500, nw * ratio));
      } else {
        nw = Math.max(20, Math.min(500, startW + dx));
        nh = Math.max(20, Math.min(500, startH + dy));
      }
      setItems(items => items.map(i => i.id === id ? { ...i, w: nw, h: nh } : i));
    };
    const endResize = () => { resizeRef.current = null; };

    // Keyboard: delete / escape
    useEffect(() => {
      const onKey = (e) => {
        if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA") return;
        // Zoom hotkeys
        if ((e.metaKey || e.ctrlKey) && (e.key === "=" || e.key === "+")) { e.preventDefault(); zoomAt(null, 1.2); return; }
        if ((e.metaKey || e.ctrlKey) && e.key === "-") { e.preventDefault(); zoomAt(null, 1/1.2); return; }
        if ((e.metaKey || e.ctrlKey) && e.key === "0") { e.preventDefault(); resetView(); return; }
        if ((e.metaKey || e.ctrlKey) && e.key === "1") { e.preventDefault(); setZoom(1); setPan({x:0,y:0}); return; }
        // Space-hold pan mode
        if (e.code === "Space" && !spaceDown) { e.preventDefault(); setSpaceDown(true); return; }
        if ((e.key === "Backspace" || e.key === "Delete") && selectedId) {
          pushHistory();
          setItems(items => items.filter(i => i.id !== selectedId));
          setSelectedId(null);
        }
        if (e.key === "Escape") setSelectedId(null);
        if ((e.metaKey || e.ctrlKey) && e.key === "z") { e.preventDefault(); undo(); }
      };
      const onKeyUp = (e) => {
        if (e.code === "Space") setSpaceDown(false);
      };
      window.addEventListener("keydown", onKey);
      window.addEventListener("keyup", onKeyUp);
      return () => {
        window.removeEventListener("keydown", onKey);
        window.removeEventListener("keyup", onKeyUp);
      };
    }, [selectedId, items, spaceDown, zoom, pan]);

    // ── Doodle picker filter ───────────────────────────
    const DD_CATS = window.DOODLE_CATEGORIES || {};
    const DD_ALL = window.DOODLE_NAMES || [];
    const visibleDoodles = useMemo(() => {
      const pool = ddCat === "All" ? DD_ALL : (DD_CATS[ddCat] || []);
      const q = ddQuery.trim().toLowerCase();
      return pool.filter(n => !q || n.toLowerCase().includes(q));
    }, [ddCat, ddQuery]);

    // Set selected item props
    const updateSelected = (patch) => {
      setItems(items => items.map(i => i.id === selectedId ? { ...i, ...patch } : i));
    };
    const deleteSelected = () => {
      pushHistory();
      setItems(items => items.filter(i => i.id !== selectedId));
      setSelectedId(null);
    };
    const duplicateSelected = () => {
      pushHistory();
      const it = items.find(i => i.id === selectedId);
      if (!it) return;
      const id = newId();
      const copy = { ...it, id, x: it.x + 24, y: it.y + 24 };
      setItems(items => [...items, copy]);
      setSelectedId(id);
    };
    const selected = items.find(i => i.id === selectedId);

    // ── Export ─────────────────────────────────────────
    const exportSVG = async () => {
      const stage = stageRef.current;
      const w = stage.offsetWidth, h = stage.offsetHeight;
      const paperBgs = {
        white: "#FAFAF6", cream: "#F4ECDB", slate: "#2A2825", navy: "#1A2438",
        graph: "#FAFAF6", dot: "#FAFAF6", ruled: "#FAFAF6", image: "#FAFAF6",
      };
      const bg = paperBgs[paper];

      // Custom-image background — embed as <image> element sized to fit
      let bgImageSvg = "";
      if (paper === "image" && bgImage) {
        if (bgFit === "tile") {
          // Measure the image so we can build a tile pattern
          const probe = await new Promise((res) => {
            const im = new Image();
            im.onload = () => res({ w: im.naturalWidth, h: im.naturalHeight });
            im.onerror = () => res({ w: 200, h: 200 });
            im.src = bgImage;
          });
          bgImageSvg = `<defs><pattern id="bgimg" width="${probe.w}" height="${probe.h}" patternUnits="userSpaceOnUse"><image href="${bgImage}" width="${probe.w}" height="${probe.h}"/></pattern></defs>
          <rect width="100%" height="100%" fill="url(#bgimg)"/>`;
        } else {
          // cover / contain — compute the rect
          const probe = await new Promise((res) => {
            const im = new Image();
            im.onload = () => res({ w: im.naturalWidth, h: im.naturalHeight });
            im.onerror = () => res({ w: w, h: h });
            im.src = bgImage;
          });
          const sW = w / probe.w, sH = h / probe.h;
          const s = bgFit === "cover" ? Math.max(sW, sH) : Math.min(sW, sH);
          const iw = probe.w * s, ih = probe.h * s;
          const ix = (w - iw) / 2, iy = (h - ih) / 2;
          bgImageSvg = `<image href="${bgImage}" x="${ix}" y="${iy}" width="${iw}" height="${ih}" preserveAspectRatio="none"/>`;
        }
      }

      const patternDef = paper === "graph"
        ? `<pattern id="p" width="24" height="24" patternUnits="userSpaceOnUse"><path d="M24 0H0V24" fill="none" stroke="rgba(60,60,60,0.08)" stroke-width="1"/></pattern>`
        : paper === "dot"
        ? `<pattern id="p" width="20" height="20" patternUnits="userSpaceOnUse"><circle cx="10" cy="10" r="1.2" fill="rgba(60,60,60,0.2)"/></pattern>`
        : paper === "ruled"
        ? `<pattern id="p" width="28" height="28" patternUnits="userSpaceOnUse"><path d="M0 27h28" stroke="rgba(100,140,220,0.25)" stroke-width="1" fill="none"/></pattern>`
        : null;
      // Render items into SVG strings
      const itemSvg = items.map(it => {
        if (it.kind === "stamp") {
          // pull innerHTML of the rendered <Doodle> at its current DOM position
          const el = stage.querySelector(`[data-id="${it.id}"] svg`);
          if (!el) return "";
          const inner = el.innerHTML;
          const w = it.w ?? it.size ?? 84;
          const h = it.h ?? it.size ?? 84;
          const sx = it.flipX ? -1 : 1;
          const sy = it.flipY ? -1 : 1;
          return `<g transform="translate(${it.x}, ${it.y}) rotate(${it.rot}) scale(${sx}, ${sy}) translate(${-w/2}, ${-h/2})">
            <svg width="${w}" height="${h}" viewBox="0 0 64 64" preserveAspectRatio="none" style="color:${it.color}">${inner}</svg>
          </g>`;
        }
        if (it.kind === "text") {
          return `<text x="${it.x}" y="${it.y}" fill="${it.color}" font-family="Caveat, cursive" font-size="${it.size}" transform="rotate(${it.rot}, ${it.x}, ${it.y})">${escapeXml(it.text)}</text>`;
        }
        if (it.kind === "stroke") {
          const d = "M " + it.points.map(p => p.join(",")).join(" L ");
          return `<path d="${d}" fill="none" stroke="${it.color}" stroke-width="${it.width}" stroke-linecap="round" stroke-linejoin="round"/>`;
        }
        return "";
      }).join("\n");
      const svg = `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
  <defs>${patternDef || ""}</defs>
  <rect width="100%" height="100%" fill="${bg}"/>
  ${bgImageSvg}
  ${patternDef ? `<rect width="100%" height="100%" fill="url(#p)"/>` : ""}
  ${itemSvg}
</svg>`;
      return svg;
    };

    const downloadSVG = async () => {
      const svg = await exportSVG();
      const blob = new Blob([svg], { type: "image/svg+xml" });
      const a = document.createElement("a");
      a.href = URL.createObjectURL(blob);
      a.download = `doodle-${Date.now()}.svg`;
      a.click();
      URL.revokeObjectURL(a.href);
    };

    const downloadPNG = async () => {
      const svg = await exportSVG();
      const img = new Image();
      const stage = stageRef.current;
      const w = stage.offsetWidth, h = stage.offsetHeight;
      const scale = 2; // 2x for retina
      await new Promise((resolve, reject) => {
        img.onload = resolve;
        img.onerror = reject;
        img.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg);
      });
      const cv = document.createElement("canvas");
      cv.width = w * scale; cv.height = h * scale;
      const ctx = cv.getContext("2d");
      ctx.drawImage(img, 0, 0, w * scale, h * scale);
      cv.toBlob(blob => {
        const a = document.createElement("a");
        a.href = URL.createObjectURL(blob);
        a.download = `doodle-${Date.now()}.png`;
        a.click();
        URL.revokeObjectURL(a.href);
      }, "image/png");
    };

    // expose for FullscreenShell save
    useEffect(() => {
      window.__dsExport = {
        async svg() {
          const data = await exportSVG();
          return { filename: `doodle-${Date.now()}.svg`, data };
        },
        async png() {
          const svg = await exportSVG();
          const img = new Image();
          const stage = stageRef.current;
          const w = stage.offsetWidth, h = stage.offsetHeight;
          await new Promise((resolve, reject) => {
            img.onload = resolve; img.onerror = reject;
            img.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg);
          });
          const cv = document.createElement("canvas");
          cv.width = w * 2; cv.height = h * 2;
          cv.getContext("2d").drawImage(img, 0, 0, w * 2, h * 2);
          const blob = await new Promise(r => cv.toBlob(r, "image/png"));
          return { filename: `doodle-${Date.now()}.png`, data: blob };
        },
      };
    }, [items, paper]);

    // ── Render ─────────────────────────────────────────
    return (
      <div className="ds-shell">
        {/* Sidebar */}
        <div className="ds-sidebar">
          <h4>Tools</h4>
          <div className="ds-tools">
            {TOOLS.map(t => (
              <button key={t} className={tool === t ? "on" : ""} onClick={() => setTool(t)} title={t}>
                {ToolIcon[t]}
              </button>
            ))}
          </div>

          <h4>Ink</h4>
          <div className="ds-colors">
            {INK_PALETTE.map((p, i) => (
              <button key={p.name} className={"ds-swatch" + (i === colorIdx ? " on" : "")}
                style={{background: p.color, boxShadow: p.name === "chalk" ? "inset 0 0 0 1px var(--color-border-hairline)" : "none"}}
                title={p.name} onClick={() => setColorIdx(i)} />
            ))}
          </div>

          <h4>Stroke</h4>
          <div className="ds-stroke-row">
            {STROKES.map((s, i) => (
              <button key={s.id} className={i === strokeIdx ? "on" : ""} onClick={() => setStrokeIdx(i)}>{s.label}</button>
            ))}
          </div>

          <h4>Doodle · picks the stamp</h4>
          <input className="ds-doodle-search" placeholder="rocket, star…" value={ddQuery} onChange={e => setDdQuery(e.target.value)} />
          <div className="ds-doodle-cats">
            {["All", ...Object.keys(DD_CATS)].map(c => (
              <button key={c} className={ddCat === c ? "on" : ""} onClick={() => setDdCat(c)}>{c}</button>
            ))}
          </div>
          <div className="ds-doodle-grid">
            {visibleDoodles.map(n => (
              <button key={n} title={n} onClick={() => { setPickedDoodle(n); setTool("stamp"); }}
                style={{color: n === pickedDoodle ? "var(--color-accent)" : undefined,
                        borderColor: n === pickedDoodle ? "var(--color-accent)" : undefined}}>
                <Doodle name={n} size={28} stroke={2} />
              </button>
            ))}
          </div>
        </div>

        {/* Canvas */}
        <div className="ds-canvas-wrap">
          <div className="ds-canvas-bar">
            <div className="chip">Paper</div>
            <div className="ds-paper-picker">
              {PAPERS.map(p => (
                p.id === "image" ? (
                  <button key={p.id}
                    className={"pp-image" + (paper === "image" ? " on" : "") + (bgImage ? " has-image" : "")}
                    style={bgImage ? {backgroundImage: `url(${bgImage})`, backgroundSize: "cover", backgroundPosition: "center"} : undefined}
                    title={bgImage ? "Custom image (click to change)" : "Upload image"}
                    onClick={() => bgInputRef.current?.click()}>
                    {!bgImage && <span className="pp-image-plus">+</span>}
                  </button>
                ) : (
                  <button key={p.id} className={"pp-" + p.id + (paper === p.id ? " on" : "")}
                    title={p.label} onClick={() => setPaper(p.id)} />
                )
              ))}
              <input
                ref={bgInputRef} type="file" accept="image/*"
                style={{display: "none"}}
                onChange={(e) => {
                  const file = e.target.files?.[0];
                  if (!file) return;
                  const reader = new FileReader();
                  reader.onload = (ev) => {
                    setBgImage(ev.target.result);
                    setPaper("image");
                  };
                  reader.readAsDataURL(file);
                  e.target.value = ""; // allow re-selecting same file
                }}
              />
            </div>
            {paper === "image" && bgImage && (
              <div className="ds-bg-opts">
                <button className={bgFit === "cover" ? "on" : ""} onClick={() => setBgFit("cover")}>Cover</button>
                <button className={bgFit === "contain" ? "on" : ""} onClick={() => setBgFit("contain")}>Fit</button>
                <button className={bgFit === "tile" ? "on" : ""} onClick={() => setBgFit("tile")}>Tile</button>
                <button className="danger" onClick={() => { setBgImage(null); setPaper("cream"); }} title="Remove image">×</button>
              </div>
            )}
            <div className="sp"></div>
            <span className="chip">{items.length} items</span>
            <button onClick={undo} disabled={!history.length} title="Undo · ⌘Z">Undo</button>
            <button onClick={clear}>Clear</button>
            <button onClick={downloadSVG}>⬇ SVG</button>
            <button onClick={downloadPNG}>⬇ PNG</button>
          </div>

          <div
            ref={stageRef}
            className={"ds-stage" + (tool === "eraser" ? " eraser-mode" : "") + (spaceDown ? " panning" : "") + (panRef.current?.active ? " grabbing" : "")}
            data-paper={paper}
            style={paper === "image" && bgImage ? {
              backgroundImage: `url(${bgImage})`,
              backgroundSize: bgFit === "tile" ? "auto" : bgFit,
              backgroundRepeat: bgFit === "tile" ? "repeat" : "no-repeat",
              backgroundPosition: "center",
            } : undefined}
            onMouseDown={(e) => {
              // Pan: space+drag, middle-click, or always if target is stage with no tool match
              if (spaceDown || e.button === 1) {
                e.preventDefault();
                panRef.current = { active: true, didPan: false, startX: e.clientX, startY: e.clientY, startPan: { ...pan } };
                rerender();
                return;
              }
              onStageDown(e);
            }}
            onMouseMove={(e) => {
              if (panRef.current?.active) {
                const dx = e.clientX - panRef.current.startX;
                const dy = e.clientY - panRef.current.startY;
                if (Math.abs(dx) + Math.abs(dy) > 3) panRef.current.didPan = true;
                setPan({ x: panRef.current.startPan.x + dx, y: panRef.current.startPan.y + dy });
                return;
              }
              continueDraw(e); onDrag(e); onResize(e);
            }}
            onMouseUp={() => {
              if (panRef.current?.active) { panRef.current.active = false; rerender(); return; }
              endDraw(); endDrag(); endResize();
            }}
            onMouseLeave={() => {
              if (panRef.current?.active) { panRef.current.active = false; rerender(); }
              endDraw(); endDrag(); endResize();
            }}
            onWheel={(e) => {
              // Pinch zoom (ctrl key set by trackpad pinch) or ctrl/cmd + wheel
              if (e.ctrlKey || e.metaKey) {
                e.preventDefault();
                const r = stageRef.current.getBoundingClientRect();
                const px = e.clientX - r.left, py = e.clientY - r.top;
                const factor = Math.exp(-e.deltaY * 0.01);
                zoomAt({ x: px, y: py }, factor);
              } else {
                // Pan with two-finger scroll
                setPan(p => ({ x: p.x - e.deltaX, y: p.y - e.deltaY }));
              }
            }}
            onClick={onStageClick}
          >
            <div className="ds-world" style={{ transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})` }}>
              {items.length === 0 && (
                <div className="ds-hint"><div className="inner">click to stamp · drag to draw · space+drag to pan · ⌘ + scroll to zoom</div></div>
              )}

              {/* Stamps + text */}
              {items.filter(i => i.kind !== "stroke").map(it => {
              const selected = it.id === selectedId;
              const sx = it.flipX ? -1 : 1;
              const sy = it.flipY ? -1 : 1;
              const common = {
                className: "ds-stamp" + (selected ? " sel" : ""),
                "data-id": it.id,
                style: {
                  left: it.x, top: it.y,
                  transform: `translate(-50%, -50%) rotate(${it.rot}deg) scale(${sx}, ${sy})`,
                  color: it.color,
                },
                onMouseDown: (e) => startDrag(e, it),
                onClick: (e) => { e.stopPropagation(); setSelectedId(it.id); },
              };
              if (it.kind === "stamp") {
                const w = it.w ?? it.size ?? 84;
                const h = it.h ?? it.size ?? 84;
                return (
                  <div key={it.id} {...common}>
                    <Doodle name={it.name} width={w} height={h} stroke={it.stroke} wobble={it.wobble} />
                    {selected && <div className="ds-stamp-handle" onMouseDown={(e) => startResize(e, it)} />}
                  </div>
                );
              }
              if (it.kind === "text") {
                return (
                  <div key={it.id} {...common}>
                    <div className="ds-stamp-label" style={{color: it.color, fontSize: it.size}}>{it.text}</div>
                    {selected && <div className="ds-stamp-handle" onMouseDown={(e) => startResize(e, it)} />}
                  </div>
                );
              }
              return null;
            })}

            {/* Freehand strokes */}
            <svg className="ds-strokes" width="100%" height="100%" preserveAspectRatio="none">
              {items.filter(i => i.kind === "stroke").map(s => (
                <path key={s.id} d={"M " + s.points.map(p => p.join(",")).join(" L ")}
                  stroke={s.color} strokeWidth={s.width} />
              ))}
            </svg>
            </div>{/* /ds-world */}

            {/* Zoom HUD */}
            <div className="ds-zoom-hud" onClick={(e) => e.stopPropagation()}>
              <button onClick={() => zoomAt(null, 1/1.2)} title="Zoom out · ⌘−">−</button>
              <button className="ds-zoom-label" onClick={resetView} title="Reset to 100% · ⌘0">{Math.round(zoom * 100)}%</button>
              <button onClick={() => zoomAt(null, 1.2)} title="Zoom in · ⌘+">+</button>
              <span className="sep"></span>
              <button onClick={fitView} title="Fit all items">Fit</button>
            </div>

            {/* Inspector for selected */}
            {selected && (
              <div className="ds-inspector" onClick={(e) => e.stopPropagation()}>
                <h5>{selected.kind}</h5>
                {selected.kind === "text" && (
                  <input type="text" value={selected.text}
                    onChange={(e) => updateSelected({ text: e.target.value })} />
                )}
                {selected.kind === "text" && (
                  <label>Size<input type="range" min="20" max="300" value={selected.size}
                    onChange={(e) => updateSelected({ size: +e.target.value })} /></label>
                )}
                {selected.kind === "stamp" && (
                  <>
                    <label>Width<input type="range" min="20" max="500" value={selected.w ?? 84}
                      onChange={(e) => {
                        const nw = +e.target.value;
                        if (selected.lockAspect) {
                          const ratio = (selected.h ?? 84) / (selected.w ?? 84);
                          updateSelected({ w: nw, h: Math.max(20, Math.min(500, Math.round(nw * ratio))) });
                        } else updateSelected({ w: nw });
                      }} /></label>
                    <label>Height<input type="range" min="20" max="500" value={selected.h ?? 84}
                      onChange={(e) => {
                        const nh = +e.target.value;
                        if (selected.lockAspect) {
                          const ratio = (selected.w ?? 84) / (selected.h ?? 84);
                          updateSelected({ h: nh, w: Math.max(20, Math.min(500, Math.round(nh * ratio))) });
                        } else updateSelected({ h: nh });
                      }} /></label>
                    <label className="ds-check">
                      <input type="checkbox" checked={!!selected.lockAspect}
                        onChange={(e) => updateSelected({ lockAspect: e.target.checked })} />
                      Lock aspect
                    </label>
                  </>
                )}
                <label>Rotate
                  <input type="range" min="-180" max="180" value={selected.rot}
                    onChange={(e) => updateSelected({ rot: +e.target.value })} />
                </label>
                <div className="row">
                  <input type="number" min="-180" max="180" value={Math.round(selected.rot)}
                    onChange={(e) => updateSelected({ rot: +e.target.value })}
                    className="ds-num" />
                  <span className="ds-unit">°</span>
                  <button onClick={() => updateSelected({ rot: 0 })} className="ds-tiny">Reset</button>
                  <button onClick={() => updateSelected({ rot: (selected.rot - 90 + 360) % 360 - (selected.rot - 90 >= 180 ? 360 : 0) })} className="ds-tiny">↺ 90°</button>
                  <button onClick={() => updateSelected({ rot: ((selected.rot + 90) % 360) - (((selected.rot + 90) % 360) > 180 ? 360 : 0) })} className="ds-tiny">↻ 90°</button>
                </div>
                {selected.kind === "stamp" && (
                  <>
                    <div className="row">
                      <label style={{margin: 0}}>Mirror</label>
                      <button className={"ds-tiny" + (selected.flipX ? " on" : "")}
                        onClick={() => updateSelected({ flipX: !selected.flipX })}>Flip H</button>
                      <button className={"ds-tiny" + (selected.flipY ? " on" : "")}
                        onClick={() => updateSelected({ flipY: !selected.flipY })}>Flip V</button>
                    </div>
                    <label>Wobble<input type="range" min="0" max="3" step="0.1" value={selected.wobble}
                      onChange={(e) => updateSelected({ wobble: +e.target.value })} /></label>
                  </>
                )}
                <div className="row">
                  <label style={{margin: 0}}>Color</label>
                  <div className="ds-colors" style={{flex: 1}}>
                    {INK_PALETTE.slice(0, 6).map(p => (
                      <button key={p.name} className={"ds-swatch" + (selected.color === p.color ? " on" : "")}
                        style={{background: p.color, width: 18, height: 18}}
                        onClick={() => updateSelected({ color: p.color })} />
                    ))}
                  </div>
                </div>
                <div className="btn-row">
                  <button onClick={duplicateSelected}>Duplicate</button>
                  <button onClick={deleteSelected}>Delete</button>
                </div>
              </div>
            )}
          </div>
        </div>
      </div>
    );
  }

  // ── Helpers ──────────────────────────────────────────
  function escapeXml(s) {
    return String(s).replace(/[<>&"']/g, c => ({ "<":"&lt;",">":"&gt;","&":"&amp;","\"":"&quot;","'":"&apos;" })[c]);
  }

  // ── Shell-wrapped mount ──────────────────────────────
  const DS_SAVE_FORMATS = [
    { id: "svg", label: "SVG", ext: "svg", mime: "image/svg+xml" },
    { id: "png", label: "PNG", ext: "png", mime: "image/png" },
  ];

  function DoodleStudioShell() {
    return (
      <FullscreenShell
        id="doodle-studio"
        title="Doodle Studio"
        subtitle="whiteboard · stamp · draw · export"
        saveFormats={DS_SAVE_FORMATS}
        onSave={async (formatId) => {
          const gen = window.__dsExport;
          if (!gen) return null;
          if (formatId === "svg") return await gen.svg();
          if (formatId === "png") return await gen.png();
          return null;
        }}
      >
        <DoodleStudio />
      </FullscreenShell>
    );
  }

  ReactDOM.createRoot(document.getElementById("ds-app")).render(<DoodleStudioShell />);
})();
