/* studio.jsx — Chowdaheadz Design Studio.
   A t-shirt-focused canvas editor: text layers (curated print fonts, ink-color
   palette, outline, arch/curve), uploaded-art layers, and an AI panel that asks
   /api/generate-design for a print-ready layered design. Everything is bound to
   the front-chest print zone (3:4) and exports a transparent PNG that feeds the
   existing artwork → mockup → quote pipeline. */

/* ---------------- PRINT CONSTANTS ---------------- */
// Screen-print ink palette. Text colors and AI designs are constrained to these
// so every design stays printable with spot colors.
const CH_INKS = [
  { name: 'White', hex: '#FFFFFF' },
  { name: 'Cream', hex: '#F2E8D5' },
  { name: 'Gray', hex: '#A7A9AC' },
  { name: 'Charcoal', hex: '#3A3D42' },
  { name: 'Black', hex: '#191919' },
  { name: 'Navy', hex: '#18243C' },
  { name: 'Royal', hex: '#0B4DA2' },
  { name: 'Light Blue', hex: '#8ECAE6' },
  { name: 'Kelly Green', hex: '#009048' },
  { name: 'Forest', hex: '#1E4D2B' },
  { name: 'Gold', hex: '#F2A93B' },
  { name: 'Orange', hex: '#E8650D' },
  { name: 'Red', hex: '#B6131A' },
  { name: 'Maroon', hex: '#6B1F2A' },
  { name: 'Purple', hex: '#5A2D82' },
  { name: 'Pink', hex: '#F4A6C0' },
];
// Curated tee-friendly display fonts (loaded via Google Fonts in customizer.html).
const CH_STUDIO_FONTS = [
  'Anton', 'Bebas Neue', 'Archivo Black', 'Alfa Slab One', 'Graduate',
  'Oswald', 'Righteous', 'Bangers', 'Luckiest Guy', 'Permanent Marker',
  'Pacifico', 'Lobster', 'Rock Salt', 'Special Elite',
];
const CH_PRINT_W = 1500;  // export px (12" x 16" front-chest zone @ 125dpi)
const CH_PRINT_H = 2000;

/* ---------------- TEXT LAYOUT (shared by editor + export) ---------------- */
let chMeasureCanvasCtx = null;
function chMeasure() {
  if (!chMeasureCanvasCtx) chMeasureCanvasCtx = document.createElement('canvas').getContext('2d');
  return chMeasureCanvasCtx;
}
/* Lay out a text element's characters around its center. Returns px offsets
   (y down) + per-char rotation in radians, identical for DOM preview and
   canvas export so what you see is what prints. arc: degrees of total sweep,
   positive arches up (collegiate), negative arches down. */
function chLayoutText(el, stageH) {
  const fontPx = Math.max(4, (el.size || 0.1) * stageH);
  const ctx = chMeasure();
  ctx.font = fontPx + 'px "' + el.font + '"';
  const spacing = (el.spacing || 0) * fontPx;
  const chars = Array.from(el.text || '');
  const widths = chars.map(c => ctx.measureText(c).width);
  const total = widths.reduce((a, b) => a + b, 0) + spacing * Math.max(chars.length - 1, 0);
  const arcDeg = el.arc || 0;
  if (Math.abs(arcDeg) < 2 || chars.length < 2 || total <= 0) {
    let cur = -total / 2;
    const out = chars.map((ch, i) => {
      const x = cur + widths[i] / 2; cur += widths[i] + spacing;
      return { ch, x, y: 0, rot: 0 };
    });
    return { fontPx, chars: out, boxW: Math.max(total, fontPx * 0.6), boxH: fontPx * 1.1 };
  }
  const arcRad = (Math.abs(arcDeg) * Math.PI) / 180;
  const r = total / arcRad;
  const up = arcDeg > 0;
  const sag = r * (1 - Math.cos(arcRad / 2));
  let acc = 0;
  const out = chars.map((ch, i) => {
    const mid = acc + widths[i] / 2; acc += widths[i] + spacing;
    const theta = (mid - total / 2) / r;
    const x = r * Math.sin(theta);
    const yOff = r - r * Math.cos(theta);
    // shift so the arc's vertical bounding box stays centered on the element
    return up
      ? { ch, x, y: yOff - sag / 2, rot: theta }
      : { ch, x, y: -yOff + sag / 2, rot: -theta };
  });
  return { fontPx, chars: out, boxW: Math.max(2 * r * Math.sin(arcRad / 2), fontPx * 0.6), boxH: fontPx * 1.1 + sag };
}

function chLoadStudioImg(src) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => resolve(img);
    img.onerror = () => reject(new Error('Could not load image.'));
    img.src = src;
  });
}

async function chEnsureFonts(doc) {
  if (!document.fonts || !document.fonts.load) return;
  const families = [...new Set(doc.elements.filter(e => e.kind === 'text').map(e => e.font))];
  await Promise.all(families.map(f => document.fonts.load('400 64px "' + f + '"').catch(() => {})));
}

/* Crop a canvas to its non-transparent content (alpha scan) plus a small
   margin. The studio stage spans the whole print zone, so without this the
   exported art floats small inside empty padding when it's fit to the shirt. */
function chTrimCanvas(canvas, pad = 16) {
  const W = canvas.width, H = canvas.height;
  const data = canvas.getContext('2d').getImageData(0, 0, W, H).data;
  let minX = W, minY = H, maxX = -1, maxY = -1;
  for (let y = 0; y < H; y++) {
    const row = y * W;
    for (let x = 0; x < W; x++) {
      if (data[(row + x) * 4 + 3] !== 0) {
        if (x < minX) minX = x;
        if (x > maxX) maxX = x;
        if (y < minY) minY = y;
        if (y > maxY) maxY = y;
      }
    }
  }
  if (maxX < 0) return canvas; // nothing drawn
  minX = Math.max(0, minX - pad); minY = Math.max(0, minY - pad);
  maxX = Math.min(W - 1, maxX + pad); maxY = Math.min(H - 1, maxY + pad);
  const out = document.createElement('canvas');
  out.width = maxX - minX + 1; out.height = maxY - minY + 1;
  out.getContext('2d').drawImage(canvas, minX, minY, out.width, out.height, 0, 0, out.width, out.height);
  return out;
}

/* ---------------- EXPORT ---------------- */
async function chExportDesign(doc) {
  await chEnsureFonts(doc);
  const W = CH_PRINT_W, H = CH_PRINT_H;
  const canvas = document.createElement('canvas');
  canvas.width = W; canvas.height = H;
  const ctx = canvas.getContext('2d');
  for (const el of doc.elements) {
    const cx = el.x * W, cy = el.y * H;
    const rot = ((el.rotation || 0) * Math.PI) / 180;
    if (el.kind === 'image') {
      const img = await chLoadStudioImg(el.src);
      const w = el.w * W;
      const h = w * (el.aspect || ((img.naturalHeight || 1) / (img.naturalWidth || 1)));
      ctx.save(); ctx.translate(cx, cy); ctx.rotate(rot);
      ctx.drawImage(img, -w / 2, -h / 2, w, h);
      ctx.restore();
    } else if (el.kind === 'text') {
      const layout = chLayoutText(el, H);
      ctx.save(); ctx.translate(cx, cy); ctx.rotate(rot);
      ctx.font = layout.fontPx + 'px "' + el.font + '"';
      ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
      ctx.lineJoin = 'round';
      for (const c of layout.chars) {
        ctx.save(); ctx.translate(c.x, c.y); ctx.rotate(c.rot);
        if (el.outline) {
          ctx.lineWidth = layout.fontPx * 0.08;
          ctx.strokeStyle = el.outline;
          ctx.strokeText(c.ch, 0, 0);
        }
        ctx.fillStyle = el.color;
        ctx.fillText(c.ch, 0, 0);
        ctx.restore();
      }
      ctx.restore();
    }
  }
  // ship the art at full size: trim the empty print-zone padding so the
  // design fills the exported file edge-to-edge
  return chTrimCanvas(canvas).toDataURL('image/png');
}

/* ---------------- DOC HELPERS ---------------- */
let chLayerSeq = 1;
function chNewId() { return 'el' + (chLayerSeq++) + '-' + Date.now().toString(36); }
function chNewTextEl(text) {
  return { id: chNewId(), kind: 'text', text: text || 'YOUR TEXT', font: 'Anton', color: '#FFFFFF', outline: null, x: 0.5, y: 0.35, size: 0.09, rotation: 0, arc: 0, spacing: 0.02 };
}
function chInksUsed(doc) {
  const set = new Set();
  doc.elements.forEach(el => {
    if (el.kind === 'text') { set.add(el.color); if (el.outline) set.add(el.outline); }
    (el.inks || []).forEach(hex => set.add(hex));
  });
  return set;
}
/* Parse the viewBox aspect (height/width) of an inline SVG; 1 if unreadable. */
function chSvgAspect(svg) {
  const vb = /viewBox\s*=\s*["']\s*[\d.+-]+[\s,]+[\d.+-]+[\s,]+([\d.+-]+)[\s,]+([\d.+-]+)/.exec(svg);
  const w = vb && parseFloat(vb[1]), h = vb && parseFloat(vb[2]);
  return w > 0 && h > 0 ? h / w : 1;
}
/* Prepare an inline SVG to be loaded as an <img> / canvas image. Two fixups,
   both applied to the ROOT tag only (inner <rect> width/height must survive):
   - xmlns is required when SVG loads as a standalone image document — without
     it the browser renders an empty image
   - explicit root width/height, otherwise it rasterizes at a default 300x150
     intrinsic size (blurry + wrong aspect on canvas export) */
function chSvgToDataUrl(svg) {
  const aspect = chSvgAspect(svg);
  const s = svg.replace(/<svg[^>]*>/i, open => {
    let o = open.replace(/\s(width|height)\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, '');
    if (!/xmlns\s*=/.test(o)) o = o.replace(/<svg/i, '<svg xmlns="http://www.w3.org/2000/svg"');
    return o.replace(/<svg/i, '<svg width="1200" height="' + Math.round(1200 * aspect) + '"');
  });
  return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(s);
}

/* Convert a validated /api/generate-design spec into editor layers. */
function chSpecToElements(spec) {
  const els = [];
  (spec.elements || []).forEach(item => {
    if (item.type === 'text' && item.text) {
      els.push({
        id: chNewId(), kind: 'text', text: item.text, font: item.font, color: item.color,
        outline: item.outlineColor || null, x: item.x, y: item.y, size: item.size,
        rotation: item.rotation || 0, arc: item.arc || 0, spacing: item.letterSpacing || 0,
      });
    } else if (item.type === 'graphic' && item.svg) {
      const aspect = chSvgAspect(item.svg);
      els.push({
        id: chNewId(), kind: 'image', src: chSvgToDataUrl(item.svg), x: item.x, y: item.y,
        w: item.width, rotation: item.rotation || 0, aspect, inks: item.inks || [],
      });
    }
  });
  return els;
}

/* Local fallback when /api/generate-design isn't deployed (static preview):
   builds a simple arched-text design from the prompt so the flow stays testable. */
function chDemoDesign(prompt) {
  const words = (prompt || 'WICKED GOOD').toUpperCase().replace(/[^A-Z0-9' ]+/g, ' ').trim().split(/\s+/);
  const top = words.slice(0, Math.ceil(words.length / 2)).join(' ') || 'WICKED';
  const bottom = words.slice(Math.ceil(words.length / 2)).join(' ');
  const els = [{ ...chNewTextEl(top), y: 0.3, size: 0.1, arc: 40, color: '#F2A93B', outline: '#191919' }];
  if (bottom) els.push({ ...chNewTextEl(bottom), y: 0.52, size: 0.08, arc: -32, color: '#FFFFFF' });
  return { concept: 'Demo design (AI endpoint not deployed) — classic arched lockup from your words.', elements: els };
}

/* ---------------- SMALL UI PIECES ---------------- */
function StudioLabel({ children, right }) {
  return (
    <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', margin: '0 0 8px' }}>
      <span style={{ fontFamily: 'Oswald, sans-serif', fontWeight: 700, fontSize: 12.5, textTransform: 'uppercase', letterSpacing: '.05em', color: 'var(--ink)' }}>{children}</span>
      {right && <span style={{ fontFamily: 'Montserrat, sans-serif', fontSize: 11.5, color: 'var(--ink-3)' }}>{right}</span>}
    </div>
  );
}

function StudioSlider({ label, value, min, max, step, onChange, format }) {
  return (
    <div style={{ marginBottom: 14 }}>
      <StudioLabel right={format ? format(value) : value}>{label}</StudioLabel>
      <input type="range" min={min} max={max} step={step} value={value} onChange={e => onChange(parseFloat(e.target.value))} style={{ width: '100%', accentColor: 'var(--chowdahead-red)' }} />
    </div>
  );
}

function InkSwatches({ value, onPick, allowNone, noneLabel }) {
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 7 }}>
      {allowNone && (
        <button onClick={() => onPick(null)} title={noneLabel || 'None'}
          style={{ width: 26, height: 26, borderRadius: 999, cursor: 'pointer', padding: 0, background: '#fff', border: '1px solid var(--line-strong)', position: 'relative', boxShadow: value === null ? '0 0 0 2px #fff, 0 0 0 4px var(--vintage-navy)' : 'none' }}>
          <span style={{ position: 'absolute', left: 3, right: 3, top: '46%', height: 2, background: 'var(--chowdahead-red)', transform: 'rotate(-45deg)' }} />
        </button>
      )}
      {CH_INKS.map(ink => (
        <button key={ink.hex} onClick={() => onPick(ink.hex)} title={ink.name}
          style={{ width: 26, height: 26, borderRadius: 999, cursor: 'pointer', padding: 0, background: ink.hex, border: ink.hex === '#FFFFFF' ? '1px solid var(--line-strong)' : 'none', boxShadow: value === ink.hex ? '0 0 0 2px #fff, 0 0 0 4px var(--vintage-navy)' : 'inset 0 0 0 1px rgba(0,0,0,.1)' }} />
      ))}
    </div>
  );
}

/* ---------------- STAGE ELEMENT RENDERERS ---------------- */
function StageTextEl({ el, stageW, stageH, selected, onPointerDown }) {
  const layout = chLayoutText(el, stageH);
  return (
    <div style={{ position: 'absolute', left: (el.x * 100) + '%', top: (el.y * 100) + '%', width: 0, height: 0, transform: 'rotate(' + (el.rotation || 0) + 'deg)', zIndex: selected ? 5 : 2 }}>
      {layout.chars.map((c, i) => (
        <span key={i} style={{
          position: 'absolute', left: c.x, top: c.y,
          transform: 'translate(-50%, -50%) rotate(' + c.rot + 'rad)',
          fontFamily: '"' + el.font + '"', fontSize: layout.fontPx, lineHeight: 1, whiteSpace: 'pre',
          color: el.color, pointerEvents: 'none', userSelect: 'none',
          WebkitTextStroke: el.outline ? (layout.fontPx * 0.08) + 'px ' + el.outline : undefined,
          paintOrder: 'stroke fill',
        }}>{c.ch}</span>
      ))}
      <div onPointerDown={onPointerDown}
        style={{ position: 'absolute', left: -layout.boxW / 2 - 6, top: -layout.boxH / 2 - 6, width: layout.boxW + 12, height: layout.boxH + 12, cursor: 'move', border: selected ? '1.5px dashed var(--grizzly-gold)' : '1.5px dashed transparent', borderRadius: 4 }} />
      {selected && <StageHandles boxW={layout.boxW} boxH={layout.boxH} elId={el.id} />}
    </div>
  );
}

function StageImageEl({ el, stageW, selected, onPointerDown }) {
  const w = el.w * stageW;
  const h = w * (el.aspect || 1);
  return (
    <div style={{ position: 'absolute', left: (el.x * 100) + '%', top: (el.y * 100) + '%', width: 0, height: 0, transform: 'rotate(' + (el.rotation || 0) + 'deg)', zIndex: selected ? 5 : 2 }}>
      <img src={el.src} alt="" draggable={false} style={{ position: 'absolute', left: -w / 2, top: -h / 2, width: w, height: h, pointerEvents: 'none', userSelect: 'none' }} />
      <div onPointerDown={onPointerDown}
        style={{ position: 'absolute', left: -w / 2 - 6, top: -h / 2 - 6, width: w + 12, height: h + 12, cursor: 'move', border: selected ? '1.5px dashed var(--grizzly-gold)' : '1.5px dashed transparent', borderRadius: 4 }} />
      {selected && <StageHandles boxW={w} boxH={h} elId={el.id} />}
    </div>
  );
}

/* Resize (corner) + rotate (top) handles. They tag the pointer event with a
   mode via data attributes; the stage-level handler reads it. */
function StageHandles({ boxW, boxH, elId }) {
  const dot = { position: 'absolute', width: 16, height: 16, borderRadius: 999, background: '#fff', border: '2px solid var(--vintage-navy)', boxShadow: 'var(--shadow-sm)', touchAction: 'none' };
  return (
    <React.Fragment>
      <div data-ch-handle="resize" data-ch-el={elId} style={{ ...dot, left: boxW / 2 - 2, top: boxH / 2 - 2, cursor: 'nwse-resize' }} />
      <div data-ch-handle="rotate" data-ch-el={elId} style={{ ...dot, left: -8, top: -boxH / 2 - 30, cursor: 'grab', borderRadius: 999 }} />
      <div style={{ position: 'absolute', left: -1, top: -boxH / 2 - 14, width: 2, height: 14, background: 'var(--vintage-navy)', opacity: .5 }} />
    </React.Fragment>
  );
}

/* ---------------- MAIN STUDIO ---------------- */
function DesignStudio({ open, initialDoc, initialTab, garmentColor, onClose, onApply }) {
  const [doc, setDoc] = React.useState(() => initialDoc || { elements: [] });
  const [selectedId, setSelectedId] = React.useState(null);
  const [tab, setTab] = React.useState(initialTab || 'add');
  const [history, setHistory] = React.useState([]);
  const [future, setFuture] = React.useState([]);
  const [aiPrompt, setAiPrompt] = React.useState('');
  const [aiState, setAiState] = React.useState('idle'); // idle | working | error
  const [aiNote, setAiNote] = React.useState('');
  const [applying, setApplying] = React.useState(false);
  const stageRef = React.useRef(null);
  const dragRef = React.useRef(null);
  const uploadRef = React.useRef(null);
  const [stageSize, setStageSize] = React.useState({ w: 440, h: 587 });

  React.useEffect(() => {
    if (!open || !stageRef.current) return;
    const node = stageRef.current;
    const sync = () => setStageSize({ w: node.clientWidth || 440, h: node.clientHeight || 587 });
    sync();
    const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(sync) : null;
    if (ro) ro.observe(node);
    return () => { if (ro) ro.disconnect(); };
  }, [open]);

  React.useEffect(() => {
    if (open) {
      setDoc(initialDoc || { elements: [] });
      setSelectedId(null);
      setTab(initialTab || 'add');
      setHistory([]); setFuture([]);
      setAiState('idle'); setAiNote('');
      chEnsureFonts({ elements: CH_STUDIO_FONTS.map(f => ({ kind: 'text', font: f })) });
    }
  }, [open]);

  if (!open) return null;

  const selected = doc.elements.find(e => e.id === selectedId) || null;
  const inks = chInksUsed(doc);

  const pushHistory = (prevDoc) => {
    setHistory(h => [...h.slice(-29), JSON.stringify(prevDoc)]);
    setFuture([]);
  };
  const commit = (updater, recordHistory) => {
    setDoc(current => {
      const next = typeof updater === 'function' ? updater(current) : updater;
      if (recordHistory) { setHistory(h => [...h.slice(-29), JSON.stringify(current)]); setFuture([]); }
      return next;
    });
  };
  const undo = () => {
    setHistory(h => {
      if (!h.length) return h;
      const prev = h[h.length - 1];
      setDoc(current => { setFuture(f => [...f, JSON.stringify(current)]); return JSON.parse(prev); });
      return h.slice(0, -1);
    });
    setSelectedId(null);
  };
  const redo = () => {
    setFuture(f => {
      if (!f.length) return f;
      const next = f[f.length - 1];
      setDoc(current => { setHistory(h => [...h, JSON.stringify(current)]); return JSON.parse(next); });
      return f.slice(0, -1);
    });
    setSelectedId(null);
  };

  const updateEl = (id, patch, recordHistory) => {
    commit(d => ({ ...d, elements: d.elements.map(e => (e.id === id ? { ...e, ...patch } : e)) }), recordHistory);
  };
  const addElement = (el) => {
    commit(d => ({ ...d, elements: [...d.elements, el] }), true);
    setSelectedId(el.id);
  };
  const removeEl = (id) => {
    commit(d => ({ ...d, elements: d.elements.filter(e => e.id !== id) }), true);
    setSelectedId(null);
  };
  const duplicateEl = (id) => {
    const src = doc.elements.find(e => e.id === id);
    if (!src) return;
    addElement({ ...src, id: chNewId(), x: Math.min(src.x + 0.05, 0.95), y: Math.min(src.y + 0.05, 0.95) });
  };
  const moveLayer = (id, dir) => {
    commit(d => {
      const idx = d.elements.findIndex(e => e.id === id);
      const to = idx + dir;
      if (idx < 0 || to < 0 || to >= d.elements.length) return d;
      const next = [...d.elements];
      const [el] = next.splice(idx, 1);
      next.splice(to, 0, el);
      return { ...d, elements: next };
    }, true);
  };

  /* ---- drag / resize / rotate ---- */
  const beginDrag = (e, el, mode) => {
    e.preventDefault(); e.stopPropagation();
    setSelectedId(el.id);
    const rect = stageRef.current.getBoundingClientRect();
    dragRef.current = {
      mode, id: el.id, rect, orig: { ...el },
      startX: e.clientX, startY: e.clientY,
      moved: false,
      centerX: rect.left + el.x * rect.width,
      centerY: rect.top + el.y * rect.height,
    };
    window.addEventListener('pointermove', onDragMove);
    window.addEventListener('pointerup', onDragEnd, { once: true });
  };
  const onDragMove = (e) => {
    const d = dragRef.current;
    if (!d) return;
    if (!d.moved && Math.abs(e.clientX - d.startX) + Math.abs(e.clientY - d.startY) > 2) {
      d.moved = true;
      // snapshot the pre-drag state once, so undo restores the original position
      setDoc(current => {
        const preDrag = { ...current, elements: current.elements.map(el => (el.id === d.id ? d.orig : el)) };
        setHistory(h => [...h.slice(-29), JSON.stringify(preDrag)]);
        setFuture([]);
        return current;
      });
    }
    if (d.mode === 'move') {
      const nx = d.orig.x + (e.clientX - d.startX) / d.rect.width;
      const ny = d.orig.y + (e.clientY - d.startY) / d.rect.height;
      updateEl(d.id, { x: Math.min(1.05, Math.max(-0.05, nx)), y: Math.min(1.05, Math.max(-0.05, ny)) });
    } else if (d.mode === 'resize') {
      const d0 = Math.hypot(d.startX - d.centerX, d.startY - d.centerY) || 1;
      const d1 = Math.hypot(e.clientX - d.centerX, e.clientY - d.centerY);
      const k = d1 / d0;
      if (d.orig.kind === 'text') updateEl(d.id, { size: Math.min(0.4, Math.max(0.02, d.orig.size * k)) });
      else updateEl(d.id, { w: Math.min(1.2, Math.max(0.05, d.orig.w * k)) });
    } else if (d.mode === 'rotate') {
      const a0 = Math.atan2(d.startY - d.centerY, d.startX - d.centerX);
      const a1 = Math.atan2(e.clientY - d.centerY, e.clientX - d.centerX);
      let deg = (d.orig.rotation || 0) + ((a1 - a0) * 180) / Math.PI;
      const snapped = Math.round(deg / 15) * 15;
      if (Math.abs(deg - snapped) < 4) deg = snapped;
      updateEl(d.id, { rotation: Math.round(deg) });
    }
  };
  const onDragEnd = () => {
    window.removeEventListener('pointermove', onDragMove);
    dragRef.current = null;
  };
  const onStagePointerDown = (e) => {
    const handle = e.target.getAttribute && e.target.getAttribute('data-ch-handle');
    if (handle) {
      const el = doc.elements.find(x => x.id === e.target.getAttribute('data-ch-el'));
      if (el) beginDrag(e, el, handle);
      return;
    }
    if (e.target === e.currentTarget) setSelectedId(null);
  };

  /* ---- uploads ---- */
  const handleUpload = (files) => {
    const f = files && files[0];
    if (!f || !f.type.startsWith('image/')) return;
    const reader = new FileReader();
    reader.onload = async (ev) => {
      try {
        const img = await chLoadStudioImg(ev.target.result);
        const aspect = (img.naturalHeight || 1) / (img.naturalWidth || 1);
        addElement({ id: chNewId(), kind: 'image', src: ev.target.result, x: 0.5, y: 0.45, w: 0.55, rotation: 0, aspect });
      } catch (err) { /* unreadable image — ignore */ }
    };
    reader.readAsDataURL(f);
  };

  /* ---- AI generation ---- */
  const runAi = async () => {
    const prompt = aiPrompt.trim();
    if (!prompt || aiState === 'working') return;
    setAiState('working'); setAiNote('');
    let spec = null;
    try {
      const res = await fetch('/api/generate-design', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          prompt,
          garmentColor: garmentColor && garmentColor.name ? garmentColor.name + ' (' + garmentColor.hex + ')' : null,
        }),
      });
      if (res.status === 404) spec = chDemoDesign(prompt);
      else if (!res.ok) {
        let msg = '';
        try { msg = (await res.json()).error || ''; } catch (e) {}
        throw new Error(msg || ('The design generator returned ' + res.status + '.'));
      } else {
        spec = (await res.json()).design;
      }
    } catch (err) {
      if (err instanceof TypeError) spec = chDemoDesign(prompt); // no endpoint (static preview)
      else { setAiState('error'); setAiNote((err && err.message) || 'Could not generate a design.'); return; }
    }
    try {
      const els = spec.elements && spec.elements[0] && spec.elements[0].kind
        ? spec.elements // demo design is already in editor-layer form
        : chSpecToElements(spec);
      if (!els.length) throw new Error('The generator returned an empty design — try rephrasing your idea.');
      await chEnsureFonts({ elements: els });
      commit(d => ({ ...d, elements: els }), true);
      setSelectedId(null);
      setAiState('idle');
      setAiNote(spec.concept || 'Design generated — every piece is now an editable layer.');
    } catch (err) {
      setAiState('error'); setAiNote((err && err.message) || 'Could not read the generated design.');
    }
  };

  /* ---- apply ---- */
  const apply = async () => {
    if (!doc.elements.length || applying) return;
    setApplying(true);
    try {
      const dataUrl = await chExportDesign(doc);
      onApply({ dataUrl, doc });
    } catch (err) {
      setAiNote((err && err.message) || 'Could not export your design.');
    }
    setApplying(false);
  };

  /* ---- styles ---- */
  const panelBtn = (active) => ({
    flex: 1, padding: '9px 6px', cursor: 'pointer', borderRadius: 6, border: active ? '2px solid var(--vintage-navy)' : '1px solid var(--line)',
    background: active ? 'var(--paper-2)' : '#fff', fontFamily: 'Oswald, sans-serif', fontWeight: 700, fontSize: 12.5, textTransform: 'uppercase', letterSpacing: '.03em', color: 'var(--ink)',
  });
  const fieldStyle = { display: 'block', width: '100%', padding: '10px 12px', borderRadius: 6, border: '1px solid var(--line-strong)', fontFamily: 'Montserrat, sans-serif', fontSize: 14, color: 'var(--ink)', boxSizing: 'border-box', background: '#fff' };
  const iconBtn = { background: '#fff', border: '1px solid var(--line-strong)', borderRadius: 6, cursor: 'pointer', padding: '7px 11px', fontFamily: 'Oswald, sans-serif', fontWeight: 600, fontSize: 12, textTransform: 'uppercase', color: 'var(--ink-2)' };

  const stageBg = garmentColor && garmentColor.hex ? garmentColor.hex : '#18243C';

  return (
    <div className="ch-studio-backdrop" style={{ position: 'fixed', inset: 0, zIndex: 120, background: 'rgba(12,18,48,.6)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 14 }}>
      <div className="ch-studio" style={{ background: 'var(--paper)', borderRadius: 12, border: '2px solid var(--vintage-navy)', boxShadow: 'var(--shadow-lg)', width: 'min(1060px, 100%)', maxHeight: 'calc(100vh - 28px)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>

        {/* header */}
        <div style={{ background: 'var(--vintage-navy)', padding: '13px 20px', display: 'flex', alignItems: 'center', gap: 14 }}>
          <span style={{ fontFamily: 'Anton, sans-serif', fontSize: 21, color: '#fff', textTransform: 'uppercase', letterSpacing: '.01em' }}>Design Studio</span>
          <span className="ch-studio-hint" style={{ fontFamily: 'Montserrat, sans-serif', fontSize: 12, color: 'rgba(255,255,255,.65)' }}>Front-chest print area &middot; bold art with thick lines prints best</span>
          <div style={{ marginLeft: 'auto', display: 'flex', gap: 8, alignItems: 'center' }}>
            <button onClick={undo} disabled={!history.length} style={{ ...iconBtn, opacity: history.length ? 1 : .45 }}>Undo</button>
            <button onClick={redo} disabled={!future.length} style={{ ...iconBtn, opacity: future.length ? 1 : .45 }}>Redo</button>
            <button onClick={onClose} style={{ ...iconBtn, background: 'transparent', border: '1px solid rgba(255,255,255,.4)', color: '#fff' }}>Cancel</button>
            <Button variant="primary" onClick={apply} disabled={!doc.elements.length || applying}>{applying ? 'Saving…' : 'Apply to Shirt'}</Button>
          </div>
        </div>

        <div className="ch-studio-grid" style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) 330px', gap: 0, flex: 1, minHeight: 0 }}>

          {/* ---- stage ---- */}
          <div style={{ padding: 18, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--paper-2)', minHeight: 0, overflow: 'auto' }}>
            <div style={{ width: '100%', maxWidth: 440 }}>
              <div ref={stageRef} onPointerDown={onStagePointerDown}
                style={{ position: 'relative', width: '100%', aspectRatio: '3 / 4', background: stageBg, borderRadius: 8, overflow: 'hidden', boxShadow: 'var(--shadow)', touchAction: 'none', backgroundImage: 'repeating-linear-gradient(135deg, rgba(255,255,255,.03) 0 2px, transparent 2px 18px)' }}>
                {/* center guides */}
                <div style={{ position: 'absolute', left: '50%', top: 0, bottom: 0, width: 1, background: 'rgba(255,255,255,.18)', pointerEvents: 'none' }} />
                <div style={{ position: 'absolute', top: '50%', left: 0, right: 0, height: 1, background: 'rgba(255,255,255,.18)', pointerEvents: 'none' }} />
                <div style={{ position: 'absolute', inset: '4%', border: '1px dashed rgba(255,255,255,.28)', borderRadius: 6, pointerEvents: 'none' }} />
                {doc.elements.map(el => el.kind === 'text'
                  ? <StageTextEl key={el.id} el={el} stageW={stageSize.w} stageH={stageSize.h} selected={el.id === selectedId} onPointerDown={e => beginDrag(e, el, 'move')} />
                  : <StageImageEl key={el.id} el={el} stageW={stageSize.w} selected={el.id === selectedId} onPointerDown={e => beginDrag(e, el, 'move')} />)}
                {!doc.elements.length && (
                  <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none' }}>
                    <span style={{ fontFamily: 'Oswald, sans-serif', fontWeight: 600, fontSize: 14, textTransform: 'uppercase', letterSpacing: '.08em', color: 'rgba(255,255,255,.45)', textAlign: 'center', padding: '0 30px', lineHeight: 1.6 }}>Add text, upload art,<br />or generate a design with AI →</span>
                  </div>
                )}
              </div>
              <div style={{ marginTop: 10, display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontFamily: 'Montserrat, sans-serif', fontSize: 11.5, color: 'var(--ink-3)' }}>
                <span>Shown on {garmentColor && garmentColor.name ? garmentColor.name : 'your garment color'} — dashed line is the safe print area.</span>
                {inks.size > 0 && (
                  <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, whiteSpace: 'nowrap' }}>
                    Colors: {[...inks].slice(0, 8).map(hex => <span key={hex} style={{ width: 12, height: 12, borderRadius: 999, background: hex, border: '1px solid rgba(0,0,0,.15)', display: 'inline-block' }} />)}
                  </span>
                )}
              </div>
            </div>
          </div>

          {/* ---- right panel ---- */}
          <div style={{ borderLeft: '1px solid var(--line)', background: '#fff', padding: 16, overflowY: 'auto', minHeight: 0 }}>
            <div style={{ display: 'flex', gap: 6, marginBottom: 16 }}>
              <button style={panelBtn(tab === 'add')} onClick={() => setTab('add')}>Add</button>
              <button style={panelBtn(tab === 'ai')} onClick={() => setTab('ai')}>AI Design</button>
              <button style={panelBtn(tab === 'layers')} onClick={() => setTab('layers')}>Layers ({doc.elements.length})</button>
            </div>

            {tab === 'add' && (
              <div>
                <Button variant="dark" full onClick={() => addElement(chNewTextEl())}>+ Add Text</Button>
                <div style={{ height: 10 }} />
                <Button variant="outline" full onClick={() => uploadRef.current && uploadRef.current.click()}>Upload Art (PNG / JPG / SVG)</Button>
                <input ref={uploadRef} type="file" accept="image/*" style={{ display: 'none' }} onChange={e => { handleUpload(e.target.files); e.target.value = ''; }} />
                <p style={{ margin: '14px 0 0', fontFamily: 'Montserrat, sans-serif', fontSize: 12, lineHeight: 1.55, color: 'var(--ink-3)' }}>
                  Tip: keep designs bold and simple — they print better and read from across the room. Transparent PNGs work best for uploads.
                </p>
              </div>
            )}

            {tab === 'ai' && (
              <div>
                <StudioLabel>Describe your shirt idea</StudioLabel>
                <textarea rows={4} value={aiPrompt} onChange={e => setAiPrompt(e.target.value)} maxLength={400}
                  placeholder={'e.g. "Vintage Woburn little league championship shirt with a baseball and arched text" or "Lobster fishing crew design, nautical style"'}
                  style={{ ...fieldStyle, resize: 'vertical', marginBottom: 10 }} />
                <Button variant="primary" full disabled={!aiPrompt.trim() || aiState === 'working'} onClick={runAi}>
                  {aiState === 'working' ? (
                    <React.Fragment>
                      <span aria-hidden="true" style={{ width: 15, height: 15, flex: 'none', borderRadius: 999, border: '2.5px solid rgba(255,255,255,.35)', borderTopColor: '#fff', display: 'inline-block', animation: 'ch-spin .8s linear infinite' }} />
                      Designing…
                    </React.Fragment>
                  ) : 'Generate Design'}
                </Button>
                {aiState === 'working' && (
                  <p role="status" style={{ margin: '10px 0 0', display: 'flex', alignItems: 'center', gap: 8, fontFamily: 'Montserrat, sans-serif', fontSize: 12, lineHeight: 1.5, color: 'var(--ink-2)' }}>
                    Our AI designer is sketching your shirt — this usually takes 15–30 seconds. Hang tight!
                  </p>
                )}
                {aiNote && (
                  <p style={{ margin: '12px 0 0', padding: '10px 12px', borderRadius: 6, background: aiState === 'error' ? 'rgba(182,19,26,.07)' : 'var(--paper-2)', border: '1px solid var(--line)', fontFamily: 'Montserrat, sans-serif', fontSize: 12.5, lineHeight: 1.55, color: aiState === 'error' ? 'var(--chowdahead-red)' : 'var(--ink-2)' }}>{aiNote}</p>
                )}
                <p style={{ margin: '12px 0 0', fontFamily: 'Montserrat, sans-serif', fontSize: 11.5, lineHeight: 1.55, color: 'var(--ink-3)' }}>
                  The AI designs for the way we print: bold, illustrated artwork with thick lines and flat colors — never photo-realistic — in our stocked print fonts. Generating replaces the canvas, but everything stays editable and Undo brings your old design back.
                </p>
              </div>
            )}

            {tab === 'layers' && (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                {[...doc.elements].reverse().map(el => (
                  <div key={el.id} onClick={() => setSelectedId(el.id)}
                    style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '9px 11px', borderRadius: 6, cursor: 'pointer', border: el.id === selectedId ? '2px solid var(--vintage-navy)' : '1px solid var(--line)', background: el.id === selectedId ? 'var(--paper-2)' : '#fff' }}>
                    {el.kind === 'text'
                      ? <span style={{ fontFamily: '"' + el.font + '"', fontSize: 15, color: 'var(--ink)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', flex: 1 }}>{el.text || 'Text'}</span>
                      : <React.Fragment><img src={el.src} alt="" style={{ width: 26, height: 26, objectFit: 'contain' }} /><span style={{ fontFamily: 'Montserrat, sans-serif', fontSize: 12.5, color: 'var(--ink-2)', flex: 1 }}>Artwork</span></React.Fragment>}
                    <button title="Move up" onClick={e => { e.stopPropagation(); moveLayer(el.id, +1); }} style={{ ...iconBtn, padding: '4px 7px' }}>↑</button>
                    <button title="Move down" onClick={e => { e.stopPropagation(); moveLayer(el.id, -1); }} style={{ ...iconBtn, padding: '4px 7px' }}>↓</button>
                  </div>
                ))}
                {!doc.elements.length && <p style={{ margin: 0, fontFamily: 'Montserrat, sans-serif', fontSize: 12.5, color: 'var(--ink-3)' }}>No layers yet — add text or art to get started.</p>}
              </div>
            )}

            {/* selected element editor */}
            {selected && (
              <div style={{ marginTop: 18, paddingTop: 16, borderTop: '2px solid var(--vintage-navy)' }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
                  <span style={{ fontFamily: 'Oswald, sans-serif', fontWeight: 700, fontSize: 14, textTransform: 'uppercase', color: 'var(--ink)' }}>{selected.kind === 'text' ? 'Edit Text' : 'Edit Artwork'}</span>
                  <div style={{ display: 'flex', gap: 6 }}>
                    <button onClick={() => duplicateEl(selected.id)} style={{ ...iconBtn, padding: '5px 9px' }}>Copy</button>
                    <button onClick={() => removeEl(selected.id)} style={{ ...iconBtn, padding: '5px 9px', color: 'var(--chowdahead-red)', borderColor: 'var(--chowdahead-red)' }}>Delete</button>
                  </div>
                </div>

                {selected.kind === 'text' && (
                  <div>
                    <input value={selected.text} onChange={e => updateEl(selected.id, { text: e.target.value }, false)} onBlur={() => pushHistory(doc)} style={{ ...fieldStyle, marginBottom: 12, fontFamily: '"' + selected.font + '"', fontSize: 17 }} />
                    <StudioLabel>Font</StudioLabel>
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, marginBottom: 14 }}>
                      {CH_STUDIO_FONTS.map(f => (
                        <button key={f} onClick={() => updateEl(selected.id, { font: f }, true)}
                          style={{ padding: '7px 8px', cursor: 'pointer', borderRadius: 6, border: selected.font === f ? '2px solid var(--vintage-navy)' : '1px solid var(--line)', background: selected.font === f ? 'var(--paper-2)' : '#fff', fontFamily: '"' + f + '"', fontSize: 14, color: 'var(--ink)', whiteSpace: 'nowrap', overflow: 'hidden' }}>{f}</button>
                      ))}
                    </div>
                    <StudioLabel>Ink color</StudioLabel>
                    <div style={{ marginBottom: 14 }}><InkSwatches value={selected.color} onPick={hex => updateEl(selected.id, { color: hex || '#FFFFFF' }, true)} /></div>
                    <StudioLabel>Outline</StudioLabel>
                    <div style={{ marginBottom: 14 }}><InkSwatches value={selected.outline} allowNone noneLabel="No outline" onPick={hex => updateEl(selected.id, { outline: hex }, true)} /></div>
                    <StudioSlider label="Size" value={selected.size} min={0.02} max={0.3} step={0.005} onChange={v => updateEl(selected.id, { size: v }, false)} format={v => Math.round(v * 200) + '%'} />
                    <StudioSlider label="Arch / Curve" value={selected.arc || 0} min={-150} max={150} step={2} onChange={v => updateEl(selected.id, { arc: v }, false)} format={v => (v > 0 ? 'Arch up ' : v < 0 ? 'Arch down ' : 'Straight ') + Math.abs(v) + '°'} />
                    <StudioSlider label="Letter spacing" value={selected.spacing || 0} min={-0.05} max={0.4} step={0.01} onChange={v => updateEl(selected.id, { spacing: v }, false)} format={v => Math.round(v * 100)} />
                    <StudioSlider label="Rotation" value={selected.rotation || 0} min={-45} max={45} step={1} onChange={v => updateEl(selected.id, { rotation: v }, false)} format={v => v + '°'} />
                  </div>
                )}

                {selected.kind === 'image' && (
                  <div>
                    <StudioSlider label="Width" value={selected.w} min={0.05} max={1.2} step={0.01} onChange={v => updateEl(selected.id, { w: v }, false)} format={v => Math.round(v * 100) + '%'} />
                    <StudioSlider label="Rotation" value={selected.rotation || 0} min={-45} max={45} step={1} onChange={v => updateEl(selected.id, { rotation: v }, false)} format={v => v + '°'} />
                    <p style={{ margin: '4px 0 0', fontFamily: 'Montserrat, sans-serif', fontSize: 11.5, lineHeight: 1.5, color: 'var(--ink-3)' }}>Drag the corner handle to resize, the top handle to rotate. Full-color artwork is reviewed by our team to confirm the best print method.</p>
                  </div>
                )}
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { DesignStudio, CH_INKS, CH_STUDIO_FONTS, chExportDesign });
