// PiBead — Pattern Creator (real photo-to-bead workflow)
//
// Pipeline (see src/lib/color.jsx for helpers):
//   1. user drops/selects an image  -> File
//   2. generatePatternFromImage()   -> GeneratedPattern { grid, beadCounts, ... }
//   3. UI lets the user edit individual cells (pencil/eraser/eyedrop/replace)
//   4. addToCart() pushes a CartItem with the full pattern payload

const { useState, useEffect, useRef, useMemo, useCallback } = React;

function CreatePage({ onNavigate, addToCart }) {
  // ── Settings ─────────────────────────────────────────────────────────
  const [brand, setBrand] = useState('Mard');
  const [gridSize, setGridSize] = useState(50);
  const [colorLimit, setColorLimit] = useState(null); // null = use brand max
  const [removeBg, setRemoveBg] = useState(false);
  const [denoise, setDenoise] = useState(true);

  // ── Image + pattern state ────────────────────────────────────────────
  const [imageFile, setImageFile] = useState(null);
  const [imageUrl, setImageUrl] = useState(null);
  const [pattern, setPattern] = useState(() => window.buildDemoPattern('Mard', 50));
  const [generating, setGenerating] = useState(false);
  const [error, setError] = useState(null);
  const [autoRegen, setAutoRegen] = useState(true);

  // ── Editing tools ────────────────────────────────────────────────────
  const [tool, setTool] = useState('pencil');
  const [zoom, setZoom] = useState(1);
  const [selectedBeadId, setSelectedBeadId] = useState(null);
  const [history, setHistory] = useState({ past: [], future: [] });
  const [hoverCell, setHoverCell] = useState(null);
  const [isDragging, setIsDragging] = useState(false);

  // ── Cart feedback ────────────────────────────────────────────────────
  const [showAdded, setShowAdded] = useState(false);
  const [showExport, setShowExport] = useState(false);

  const palette = window.BEAD_BRANDS[brand].palette;
  const paletteHex = useMemo(() => {
    const m = {}; for (const p of palette) m[p.id] = p.hex; return m;
  }, [palette]);

  // Counts derived from current pattern grid
  const counts = useMemo(() => {
    if (!pattern) return [];
    return Object.entries(pattern.beadCounts)
      .map(([id, count]) => ({ ...window.BEAD_BY_ID[id], count }))
      .filter(b => b && b.count > 0)
      .sort((a, b) => b.count - a.count);
  }, [pattern]);

  // Re-pick the default selected bead when brand changes
  useEffect(() => {
    if (!selectedBeadId || !window.BEAD_BY_ID[selectedBeadId] || window.BEAD_BY_ID[selectedBeadId].brand !== brand) {
      // pick the most-used color of the current pattern, else palette[0]
      if (counts.length) setSelectedBeadId(counts[0].id);
      else setSelectedBeadId(palette[0].id);
    }
  }, [brand, counts, palette]);

  // ── File handling ────────────────────────────────────────────────────
  async function handleFile(file) {
    if (!file) return;
    setError(null);
    if (!file.type.startsWith('image/')) { setError('Please upload an image file (jpg, png, webp).'); return; }
    if (file.size > 20 * 1024 * 1024) { setError('Image is too large. Max 20 MB.'); return; }
    setImageFile(file);
    // Preview URL
    if (imageUrl) URL.revokeObjectURL(imageUrl);
    setImageUrl(URL.createObjectURL(file));
    if (autoRegen) await runGenerate(file);
  }

  async function runGenerate(fileOverride) {
    const file = fileOverride || imageFile;
    if (!file) return;
    setGenerating(true);
    setError(null);
    try {
      // Small yield so the spinner renders before heavy work
      await new Promise(r => setTimeout(r, 50));
      const p = await window.generatePatternFromImage(file, {
        brand, gridSize, colorLimit, removeBg, denoise,
      });
      setPattern(p);
      setHistory({ past: [], future: [] });
    } catch (e) {
      console.error(e);
      setError(e.message || 'Pattern generation failed.');
    } finally {
      setGenerating(false);
    }
  }

  // When grid size or brand changes, auto-regen if we have a real source image
  useEffect(() => {
    if (imageFile && autoRegen) runGenerate();
    // eslint-disable-next-line
  }, [gridSize, brand, removeBg, denoise, colorLimit]);

  // Drop image directly onto the canvas / page
  useEffect(() => {
    const onDragOver = (e) => { e.preventDefault(); };
    const onDrop = (e) => {
      e.preventDefault();
      const f = e.dataTransfer?.files?.[0];
      if (f) handleFile(f);
    };
    window.addEventListener('dragover', onDragOver);
    window.addEventListener('drop', onDrop);
    return () => { window.removeEventListener('dragover', onDragOver); window.removeEventListener('drop', onDrop); };
  });

  // ── Editing helpers ──────────────────────────────────────────────────
  function pushHistory() {
    setHistory(h => ({ past: [...h.past, pattern].slice(-30), future: [] }));
  }
  function undo() {
    if (!history.past.length) return;
    const prev = history.past[history.past.length - 1];
    setHistory(h => ({ past: h.past.slice(0, -1), future: [pattern, ...h.future] }));
    setPattern(prev);
  }
  function redo() {
    if (!history.future.length) return;
    const nxt = history.future[0];
    setHistory(h => ({ past: [...h.past, pattern], future: h.future.slice(1) }));
    setPattern(nxt);
  }

  function applyTool(x, y) {
    setPattern(p => {
      if (!p) return p;
      const grid = p.grid;
      const current = grid[y][x];
      let next = grid;
      if (tool === 'pencil') {
        if (!selectedBeadId || current === selectedBeadId) return p;
        next = grid.map(r => r.slice());
        next[y][x] = selectedBeadId;
      } else if (tool === 'eraser') {
        const lightest = palette.reduce((acc, b) => (b.rgb.r + b.rgb.g + b.rgb.b) > (acc.rgb.r + acc.rgb.g + acc.rgb.b) ? b : acc, palette[0]);
        next = grid.map(r => r.slice());
        next[y][x] = lightest.id;
      } else if (tool === 'eyedrop') {
        setSelectedBeadId(current);
        setTool('pencil');
        return p;
      } else if (tool === 'replace') {
        if (!selectedBeadId || current === selectedBeadId) return p;
        next = grid.map(r => r.map(c => c === current ? selectedBeadId : c));
      } else {
        return p;
      }
      return rebuildPattern(p, next);
    });
  }
  function rebuildPattern(p, newGrid) {
    const beadCounts = window.countBeadsFromGrid(newGrid);
    const totalBeads = Object.values(beadCounts).reduce((s, n) => s + n, 0);
    const colorCount = Object.keys(beadCounts).length;
    const pal = window.BEAD_BRANDS[p.brand].palette;
    return {
      ...p,
      grid: newGrid,
      beadCounts,
      totalBeads,
      colorCount,
      estimatedPrice: window.calculateEstimatedKitPrice(beadCounts, pal),
      previewImageUrl: window.gridToDataURL(newGrid, 8),
    };
  }

  function onCellDown(x, y) {
    if (tool === 'hand') return;
    pushHistory();
    setIsDragging(true);
    applyTool(x, y);
  }
  function onCellEnter(x, y) {
    setHoverCell({ x, y });
    if (isDragging && (tool === 'pencil' || tool === 'eraser')) applyTool(x, y);
  }
  useEffect(() => {
    const up = () => setIsDragging(false);
    window.addEventListener('mouseup', up);
    window.addEventListener('touchend', up);
    return () => { window.removeEventListener('mouseup', up); window.removeEventListener('touchend', up); };
  }, []);

  // ── Add to cart ──────────────────────────────────────────────────────
  function addToCartHandler() {
    if (!pattern || pattern.isDemo) return;
    const item = {
      id: 'cart-' + pattern.id,
      type: 'custom-kit',
      title: 'Custom Pixel Kit',
      patternId: pattern.id,
      previewImageUrl: pattern.previewImageUrl,
      brand: pattern.brand,
      gridSize: pattern.gridSize,
      totalBeads: pattern.totalBeads,
      colorCount: pattern.colorCount,
      beadCounts: pattern.beadCounts,
      estimatedPrice: pattern.estimatedPrice,
      qty: 1,
    };
    addToCart && addToCart(item);
    setShowAdded(true);
    setTimeout(() => setShowAdded(false), 2400);
    setTimeout(() => window.dispatchEvent(new CustomEvent('pibead:open-cart')), 400);
  }

  function downloadPattern() {
    if (!pattern) return;
    const url = window.gridToDataURL(pattern.grid, 16);
    const a = document.createElement('a');
    a.href = url;
    a.download = `pibead-${pattern.id}.png`;
    a.click();
  }
  function savePattern() {
    if (!pattern) return;
    try {
      const saved = JSON.parse(localStorage.getItem('pibead.saved') || '[]');
      saved.push({ ...pattern, savedAt: new Date().toISOString() });
      localStorage.setItem('pibead.saved', JSON.stringify(saved.slice(-10)));
      setShowAdded(true); setTimeout(() => setShowAdded(false), 1500);
    } catch (e) {}
  }

  const canAddToCart = pattern && !pattern.isDemo && !generating;

  return (
    <div data-screen-label="02 Create" className="creator-root" style={{ height: '100dvh', minHeight: 720, display: 'flex', flexDirection: 'column', background: 'var(--lt)' }}>
      <CreatorToolbar
        tool={tool} setTool={setTool}
        zoom={zoom} setZoom={setZoom}
        undo={undo} redo={redo}
        canUndo={history.past.length > 0}
        canRedo={history.future.length > 0}
        onSave={savePattern}
        onExport={() => setShowExport(true)}
        onAdd={addToCartHandler}
        canAddToCart={canAddToCart}
        pattern={pattern}
        onNavigate={onNavigate}
      />

      <div className="creator-grid" style={{ flex: 1, display: 'grid', gridTemplateColumns: '320px 1fr 380px', gap: 12, padding: 12, overflow: 'hidden' }}>
        <CreatorSettingsPanel
          brand={brand} setBrand={setBrand}
          gridSize={gridSize} setGridSize={setGridSize}
          colorLimit={colorLimit} setColorLimit={setColorLimit}
          removeBg={removeBg} setRemoveBg={setRemoveBg}
          denoise={denoise} setDenoise={setDenoise}
          autoRegen={autoRegen} setAutoRegen={setAutoRegen}
          imageFile={imageFile}
          imageUrl={imageUrl}
          pattern={pattern}
          onFile={handleFile}
          onClearImage={() => { setImageFile(null); if (imageUrl) URL.revokeObjectURL(imageUrl); setImageUrl(null); setPattern(window.buildDemoPattern(brand, gridSize)); }}
          onGenerate={() => runGenerate()}
          generating={generating}
          error={error}
        />

        <div className="panel canvas-panel" style={{ position: 'relative', overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
          <CanvasHeader brand={brand} gridSize={gridSize} hoverCell={hoverCell} grid={pattern?.grid}/>
          <CanvasArea
            pattern={pattern}
            paletteHex={paletteHex}
            generating={generating}
            zoom={zoom}
            tool={tool}
            onCellDown={onCellDown}
            onCellEnter={onCellEnter}
            onCellLeave={() => setHoverCell(null)}
            onFileDrop={handleFile}
          />
          <PaletteBar
            palette={palette}
            selected={selectedBeadId}
            setSelected={setSelectedBeadId}
            brand={brand}
          />
        </div>

        <CreatorOrderSummary
          pattern={pattern}
          counts={counts}
          brand={brand}
          onAdd={addToCartHandler}
          canAdd={canAddToCart}
        />
      </div>

      <CreatorStatusBar
        pattern={pattern}
        brand={brand}
        gridSize={gridSize}
        selectedBead={selectedBeadId ? window.BEAD_BY_ID[selectedBeadId] : null}
        hoverCell={hoverCell}
        zoom={zoom}
        generating={generating}
      />

      {showExport && window.ExportModal && (
        <window.ExportModal
          pattern={pattern}
          counts={counts}
          brand={brand}
          onClose={() => setShowExport(false)}
        />
      )}

      {showAdded && (
        <div className="card" style={{ position: 'fixed', bottom: 24, right: 24, padding: '14px 18px', display: 'flex', alignItems: 'center', gap: 12, borderRadius: 14, boxShadow: 'var(--shadow-lg)', zIndex: 50 }}>
          <div style={{ width: 32, height: 32, background: 'var(--green)', borderRadius: 999, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <window.Icon.Check size={18}/>
          </div>
          <div>
            <div className="font-display" style={{ fontSize: 14 }}>Added to cart</div>
            <div className="font-mono" style={{ fontSize: 11, color: 'var(--muted)' }}>
              {pattern?.totalBeads?.toLocaleString()} beads · {pattern?.colorCount} colors · ${pattern?.estimatedPrice?.toFixed(2)}
            </div>
          </div>
        </div>
      )}

      <style>{`
        @media (max-width: 1100px) {
          .creator-grid { grid-template-columns: 260px 1fr 300px !important; }
        }
        @media (max-width: 900px) {
          /* Stack into one scrolling column. Let the whole page scroll instead of
             nesting fixed-height panels — the nested flex collapsed the editing
             canvas to ~48px tall on phones. */
          .creator-root { height: auto !important; min-height: 100dvh; }
          .creator-grid { grid-template-columns: 1fr !important; overflow: visible !important; }
          .creator-grid > .panel { overflow: visible !important; }
          /* bring the editor to the top and give it a real, usable height */
          .canvas-panel { order: -1; }
          .canvas-panel .pixel-bg-tight { min-height: 60vh; overflow: visible !important; }
          /* status bar: wrap to multiple lines on small screens instead of clipping */
          .creator-status { height: auto !important; min-height: 34px !important; padding-top: 6px !important; padding-bottom: 6px !important; }
        }
      `}</style>
    </div>
  );
}
window.CreatePage = CreatePage;

// ── Top Toolbar ────────────────────────────────────────────────────────
function CreatorToolbar({ tool, setTool, zoom, setZoom, undo, redo, canUndo, canRedo, onSave, onExport, onAdd, canAddToCart, pattern, onNavigate, cartCount }) {
  const tools = [
    { id: 'hand',    icon: 'Hand',     label: 'Pan' },
    { id: 'pencil',  icon: 'Pencil',   label: 'Pencil' },
    { id: 'eraser',  icon: 'Eraser',   label: 'Eraser' },
    { id: 'eyedrop', icon: 'Eyedrop',  label: 'Eyedropper' },
    { id: 'replace', icon: 'Replace',  label: 'Replace all' },
  ];
  return (
    <div className="flex items-center justify-between" style={{ padding: '10px 16px', background: 'var(--lt2)', borderBottom: '2px solid var(--ink)', flexWrap: 'wrap', gap: 8 }}>
      <div className="flex items-center gap-2">
        <ToolbarMenu onNavigate={onNavigate} cartCount={cartCount}/>
        <a href="#/" onClick={(e) => { e.preventDefault(); onNavigate && onNavigate('home'); }} style={{ display: 'flex', alignItems: 'center' }} title="Tobeads home">
          <window.TobeadsNav size={13} onLight/>
        </a>
        <span className="font-mono" style={{ fontSize: 9, color: 'var(--muted)', letterSpacing: '0.14em', textTransform: 'uppercase', borderLeft: '1px solid var(--line)', paddingLeft: 10, marginLeft: 4 }}>Creator</span>
      </div>
      <div className="flex items-center gap-1">
        {tools.map(t => {
          const Icon = window.Icon[t.icon];
          return (
            <button key={t.id} title={t.label} onClick={() => setTool(t.id)} className={`tool-btn ${tool === t.id ? 'on' : ''}`} style={{ width: 40, height: 40 }}>
              <Icon size={18}/>
            </button>
          );
        })}
        <span style={{ width: 1, height: 20, background: 'var(--line)', margin: '0 6px' }}/>
        <button title="Undo" className="tool-btn" disabled={!canUndo} onClick={undo} style={{ opacity: canUndo ? 1 : 0.3, width: 40, height: 40 }}><window.Icon.Undo size={18}/></button>
        <button title="Redo" className="tool-btn" disabled={!canRedo} onClick={redo} style={{ opacity: canRedo ? 1 : 0.3, width: 40, height: 40 }}><window.Icon.Redo size={18}/></button>
        <span style={{ width: 1, height: 20, background: 'var(--line)', margin: '0 6px' }}/>
        <button title="Zoom out" className="tool-btn" onClick={() => setZoom(z => Math.max(0.6, z - 0.2))}><window.Icon.ZoomOut size={18}/></button>
        <span className="font-mono" style={{ fontSize: 12, color: 'var(--muted)', width: 40, textAlign: 'center' }}>{Math.round(zoom*100)}%</span>
        <button title="Zoom in" className="tool-btn" onClick={() => setZoom(z => Math.min(2.4, z + 0.2))}><window.Icon.ZoomIn size={18}/></button>
      </div>
      <div className="flex items-center gap-2">
        <button onClick={onSave} className="btn btn-ghost btn-sm"><window.Icon.Save size={14}/>Save</button>
        <button onClick={onExport} className="btn btn-ghost btn-sm"><window.Icon.Download size={14}/>Export</button>
        <button onClick={onAdd} disabled={!canAddToCart} className="btn btn-yellow btn-sm" style={{ opacity: canAddToCart ? 1 : 0.4, cursor: canAddToCart ? 'pointer' : 'not-allowed' }}>
          <window.Icon.Cart size={14}/> Add to Cart {pattern && !pattern.isDemo && `· $${pattern.estimatedPrice.toFixed(2)}`}
        </button>
      </div>
    </div>
  );
}

// Click-based compact nav menu (replaces hover HUD inside the editor)
function ToolbarMenu({ onNavigate, cartCount }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    function onDoc(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }
    if (open) document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [open]);
  const links = [
    ['Home', 'home'], ['Quick Start', 'quickstart'], ['Preset World', 'presets'],
    ['Creator Mode', 'create'], ['Gallery', 'gallery'],
  ];
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button className={`tool-btn ${open ? 'on' : ''}`} style={{ width: 40, height: 40 }} title="Menu" onClick={() => setOpen(o => !o)}>
        <window.Icon.Menu size={18}/>
      </button>
      {open && (
        <div className="card" style={{ position: 'absolute', top: 46, left: 0, zIndex: 60, width: 200, padding: 8, borderRadius: 12, boxShadow: 'var(--shadow-lg)' }}>
          {links.map(([label, route]) => (
            <button key={route} onClick={() => { setOpen(false); onNavigate && onNavigate(route); }}
              className="flex items-center w-full text-left" style={{ padding: '9px 10px', borderRadius: 8, fontSize: 13, background: 'transparent', border: 0, cursor: 'pointer' }}
              onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(17,17,17,0.05)'}
              onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
              {label}
            </button>
          ))}
          <div style={{ height: 1, background: 'var(--line)', margin: '6px 4px' }}/>
          <button onClick={() => { setOpen(false); window.dispatchEvent(new CustomEvent('pibead:open-cart')); }}
            className="flex items-center w-full text-left gap-2" style={{ padding: '9px 10px', borderRadius: 8, fontSize: 13, background: 'transparent', border: 0, cursor: 'pointer' }}
            onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(17,17,17,0.05)'}
            onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
            <window.Icon.Cart size={14}/> Cart {cartCount > 0 ? `· ${cartCount}` : ''}
          </button>
        </div>
      )}
    </div>
  );
}

// ── Left Settings Panel ──────────────────────────────────────────────
function CreatorSettingsPanel({ brand, setBrand, gridSize, setGridSize, colorLimit, setColorLimit, removeBg, setRemoveBg, denoise, setDenoise, autoRegen, setAutoRegen, imageFile, imageUrl, pattern, onFile, onClearImage, onGenerate, generating, error }) {
  return (
    <div className="panel" style={{ padding: 18, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 22 }}>
      {/* Source */}
      <Group title="Source Photo">
        <ImageUploader imageFile={imageFile} imageUrl={imageUrl} onFile={onFile} onClear={onClearImage}/>
        {error && (
          <div style={{ background: 'rgba(214,53,61,0.08)', border: '1px solid rgba(214,53,61,0.2)', borderRadius: 8, padding: '8px 10px', fontSize: 12, color: 'var(--ink)', marginTop: 8 }}>
            {error}
          </div>
        )}
      </Group>

      {/* Brand */}
      <Group title="Bead Brand" hint="We'll match every pixel to a real bead from this brand.">
        <div className="space-y-2">
          {Object.values(window.BEAD_BRANDS).map(b => (
            <button
              key={b.id}
              onClick={() => setBrand(b.id)}
              style={{
                width: '100%', textAlign: 'left',
                padding: 12, borderRadius: 10,
                border: '1.5px solid ' + (b.id === brand ? 'var(--ink)' : 'var(--line)'),
                background: b.id === brand ? 'rgba(17,17,17,0.04)' : 'transparent',
                display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer',
              }}
            >
              <div>
                <div className="font-display flex items-center gap-2" style={{ fontSize: 14 }}>
                  {b.label}
                  {b.id === brand && <window.Icon.Check size={14} stroke="var(--green)" sw={2.5}/>}
                </div>
                <div className="font-mono" style={{ fontSize: 10, color: 'var(--muted)', marginTop: 1 }}>{b.count} colors · ${b.basePrice}/1k</div>
              </div>
              <div className="flex ml-auto" style={{ flexShrink: 0 }}>
                {b.palette.slice(0, 8).map((c, i) => (
                  <div key={i} style={{ width: 12, height: 12, background: c.hex, borderRadius: 2, border: '1px solid rgba(17,17,17,0.1)', marginLeft: i === 0 ? 0 : -2 }}/>
                ))}
              </div>
            </button>
          ))}
        </div>
      </Group>

      {/* Grid */}
      <Group title="Grid Size" hint="Bigger grids capture more detail, but mean more beads.">
        <div className="seg" style={{ display: 'flex', width: '100%' }}>
          {[30, 50, 80].map(s => (
            <button key={s} onClick={() => setGridSize(s)} className={s === gridSize ? 'on' : ''} style={{ flex: 1 }}>{s}×{s}</button>
          ))}
        </div>
        <div className="grid grid-cols-3 gap-2 mt-3 font-mono" style={{ fontSize: 10, color: 'var(--muted)' }}>
          <Stat label="Cells" value={(gridSize*gridSize).toLocaleString()}/>
          <Stat label="Time" value={`~${Math.round(gridSize*gridSize/700)}h`}/>
          <Stat label="Size" value={`${(gridSize*0.5).toFixed(0)}cm`}/>
        </div>
      </Group>

      {/* Color limit */}
      <Group title="Color Limit" hint="Cap how many unique colors the pattern can use.">
        <ColorLimitSlider value={colorLimit} setValue={setColorLimit} max={window.BEAD_BRANDS[brand].count}/>
      </Group>

      {/* Optimization */}
      <Group title="Optimization">
        <ToggleRowControl label="Color clean-up" sub="Smooth single-pixel noise" on={denoise} onChange={() => setDenoise(!denoise)}/>
        <ToggleRowControl label="Remove background" sub="Heuristic edge fill" on={removeBg} onChange={() => setRemoveBg(!removeBg)}/>
        <ToggleRowControl label="Auto-regenerate" sub="Re-run on every setting change" on={autoRegen} onChange={() => setAutoRegen(!autoRegen)}/>
      </Group>

      <button onClick={onGenerate} disabled={generating || !imageFile} className="btn btn-primary" style={{ width: '100%', opacity: generating || !imageFile ? 0.5 : 1 }}>
        {generating ? <><window.Icon.Sparkle size={14}/>Generating…</> : <><window.Icon.Wand size={14}/>Generate Pattern</>}
      </button>
    </div>
  );
}

function ColorLimitSlider({ value, setValue, max }) {
  const v = value ?? max;
  return (
    <div>
      <div className="flex items-center justify-between mb-2">
        <span style={{ fontSize: 12 }}>{v === max ? 'No limit' : v + ' colors max'}</span>
        <span className="font-mono" style={{ fontSize: 11, color: 'var(--muted)' }}>{v} / {max}</span>
      </div>
      <input
        type="range"
        min={4}
        max={max}
        step={1}
        value={v}
        onChange={(e) => {
          const n = Number(e.target.value);
          setValue(n === max ? null : n);
        }}
      />
    </div>
  );
}

function ImageUploader({ imageFile, imageUrl, onFile, onClear }) {
  const ref = useRef(null);
  const [hover, setHover] = useState(false);
  function onDrop(e) {
    e.preventDefault(); setHover(false);
    const f = e.dataTransfer?.files?.[0]; if (f) onFile(f);
  }
  if (imageFile && imageUrl) {
    return (
      <div style={{ border: '1.5px solid var(--line)', borderRadius: 12, padding: 12, background: 'rgba(17,17,17,0.02)' }}>
        <div className="flex items-center gap-3">
          <div style={{ width: 56, height: 56, borderRadius: 8, overflow: 'hidden', flexShrink: 0, border: '1px solid var(--line)' }}>
            <img src={imageUrl} style={{ width: '100%', height: '100%', objectFit: 'cover' }}/>
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="truncate" style={{ fontSize: 13, fontWeight: 600 }}>{imageFile.name}</div>
            <div className="font-mono" style={{ fontSize: 10, color: 'var(--muted)' }}>{(imageFile.size/1024).toFixed(0)} kb · {imageFile.type.split('/')[1].toUpperCase()}</div>
          </div>
          <button onClick={onClear} className="tool-btn" title="Remove image"><window.Icon.Close size={14}/></button>
        </div>
        <button onClick={() => ref.current?.click()} className="btn btn-soft btn-sm mt-3" style={{ width: '100%' }}>
          <window.Icon.Image size={14}/> Replace image
        </button>
        <input ref={ref} type="file" accept="image/*" hidden onChange={e => onFile(e.target.files?.[0])}/>
      </div>
    );
  }
  return (
    <div
      onDragEnter={() => setHover(true)}
      onDragLeave={() => setHover(false)}
      onDragOver={(e) => e.preventDefault()}
      onDrop={onDrop}
      onClick={() => ref.current?.click()}
      style={{
        border: '1.5px dashed ' + (hover ? 'var(--ink)' : 'var(--line-2)'),
        background: hover ? 'rgba(255,216,61,0.12)' : 'rgba(17,17,17,0.02)',
        borderRadius: 12, padding: 22, textAlign: 'center', cursor: 'pointer',
        transition: 'all .15s ease',
      }}
    >
      <div style={{ width: 44, height: 44, margin: '0 auto', background: 'var(--yellow)', border: '1.5px solid var(--ink)', borderRadius: 10, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <window.Icon.Upload size={20}/>
      </div>
      <div className="font-display mt-3" style={{ fontSize: 15, letterSpacing: '-0.02em' }}>Drop a photo</div>
      <div className="font-mono mt-1" style={{ fontSize: 11, color: 'var(--muted)' }}>or click to browse · JPG · PNG · WEBP · ≤20 MB</div>
      <input ref={ref} type="file" accept="image/*" hidden onChange={e => onFile(e.target.files?.[0])}/>
    </div>
  );
}

// ── Helper subcomponents ─────────────────────────────────────────────
function Group({ title, hint, children }) {
  return (
    <div>
      <div className="font-mono mb-3" style={{ fontSize: 10, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--muted)' }}>{title}</div>
      {children}
      {hint && <div style={{ fontSize: 11, color: 'var(--muted-2)', marginTop: 8, lineHeight: 1.4 }}>{hint}</div>}
    </div>
  );
}
function Stat({ label, value }) {
  return (
    <div style={{ background: 'rgba(17,17,17,0.04)', padding: '8px 10px', borderRadius: 8 }}>
      <div style={{ textTransform: 'uppercase', letterSpacing: '0.1em' }}>{label}</div>
      <div className="font-display" style={{ fontSize: 13, color: 'var(--ink)', marginTop: 2 }}>{value}</div>
    </div>
  );
}
function ToggleRowControl({ label, on, onChange, sub }) {
  return (
    <button onClick={onChange} className="flex items-center w-full text-left" style={{ padding: '8px 0', justifyContent: 'space-between', background: 'transparent', border: 0 }}>
      <div>
        <div style={{ fontSize: 13 }}>{label}</div>
        {sub && <div style={{ fontSize: 11, color: 'var(--muted-2)' }}>{sub}</div>}
      </div>
      <div style={{ width: 34, height: 20, background: on ? 'var(--ink)' : 'rgba(17,17,17,0.15)', borderRadius: 999, position: 'relative', flexShrink: 0 }}>
        <div style={{ position: 'absolute', top: 2, left: on ? 16 : 2, width: 16, height: 16, background: on ? 'var(--yellow)' : '#fff', borderRadius: 999, transition: 'all .15s' }}/>
      </div>
    </button>
  );
}

// ── Bottom Status Bar (professional editor status) ──────────────────────
function CreatorStatusBar({ pattern, brand, gridSize, selectedBead, hoverCell, zoom, generating }) {
  const isDemo = pattern?.isDemo;
  const stateLabel = generating ? 'Generating…' : (isDemo ? 'Demo Pattern · Upload to begin' : 'Pattern ready · Auto-saved');
  const dotColor = generating ? 'var(--yellow)' : (isDemo ? 'var(--muted)' : 'var(--green)');
  const cur = hoverCell ? `${hoverCell.x}, ${hoverCell.y}` : '—, —';
  return (
    <div className="creator-status font-mono" style={{
      display: 'flex', alignItems: 'center', gap: 0, flexWrap: 'wrap',
      padding: '0 14px', height: 34, minHeight: 34,
      background: 'var(--ink)', color: 'rgba(255,255,255,0.82)',
      fontSize: 11, letterSpacing: '0.02em',
    }}>
      <StatusSeg first>
        <span style={{ width: 7, height: 7, borderRadius: 999, background: dotColor, display: 'inline-block' }}/>
        <span style={{ color: '#fff' }}>{stateLabel}</span>
      </StatusSeg>
      <StatusSeg>Brand <b style={{ color: '#fff' }}>{brand}</b></StatusSeg>
      <StatusSeg>Grid <b style={{ color: '#fff' }}>{gridSize}×{gridSize}</b></StatusSeg>
      <StatusSeg>
        Color
        {selectedBead ? (
          <span className="flex items-center gap-1.5">
            <span style={{ width: 11, height: 11, background: selectedBead.hex, border: '1px solid rgba(255,255,255,0.4)', display: 'inline-block' }}/>
            <b style={{ color: '#fff' }}>{selectedBead.code}</b>
            <span style={{ opacity: 0.7 }}>{selectedBead.name}</span>
          </span>
        ) : <b style={{ color: '#fff' }}>—</b>}
      </StatusSeg>
      <StatusSeg>Cursor <b style={{ color: '#fff' }}>{cur}</b></StatusSeg>
      <div style={{ flex: 1 }}/>
      {!isDemo && pattern && (
        <StatusSeg>Beads <b style={{ color: '#fff' }}>{pattern.totalBeads.toLocaleString()}</b> · {pattern.colorCount} colors</StatusSeg>
      )}
      <StatusSeg>Zoom <b style={{ color: '#fff' }}>{Math.round(zoom*100)}%</b></StatusSeg>
    </div>
  );
}
function StatusSeg({ children, first }) {
  return (
    <span className="flex items-center gap-1.5" style={{
      padding: '0 14px', height: '100%',
      borderLeft: first ? 'none' : '1px solid rgba(255,255,255,0.14)',
      display: 'inline-flex', whiteSpace: 'nowrap',
    }}>{children}</span>
  );
}

// ── Canvas Header ────────────────────────────────────────────────────
function CanvasHeader({ brand, gridSize, hoverCell, grid }) {
  const hoverId = grid && hoverCell ? grid[hoverCell.y]?.[hoverCell.x] : null;
  const bead = hoverId ? window.BEAD_BY_ID[hoverId] : null;
  return (
    <div className="flex items-center justify-between" style={{ padding: '10px 16px', borderBottom: '1px solid var(--line)' }}>
      <div className="flex items-center gap-3 font-mono" style={{ fontSize: 11, color: 'var(--muted)' }}>
        <span>Canvas</span>
        <span style={{ background: 'rgba(17,17,17,0.06)', padding: '2px 8px', borderRadius: 999 }}>{gridSize} × {gridSize}</span>
        <span>·</span><span>{brand}</span>
      </div>
      <div className="flex items-center gap-3 font-mono" style={{ fontSize: 11, color: 'var(--muted)' }}>
        {bead ? (
          <>
            <div style={{ width: 12, height: 12, background: bead.hex, borderRadius: 2, border: '1px solid rgba(17,17,17,0.2)' }}/>
            <span>{bead.code} · {bead.name}</span>
            <span>· ({hoverCell.x}, {hoverCell.y})</span>
          </>
        ) : (
          <span>Hover a cell</span>
        )}
      </div>
    </div>
  );
}

// ── Canvas Area ──────────────────────────────────────────────────────
function CanvasArea({ pattern, paletteHex, generating, zoom, tool, onCellDown, onCellEnter, onCellLeave, onFileDrop }) {
  if (!pattern) return null;
  const { grid, gridSize } = pattern;
  return (
    <div className="pixel-bg-tight" style={{ flex: 1, overflow: 'auto', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24, position: 'relative' }}
      onDragOver={(e) => e.preventDefault()}
      onDrop={(e) => { e.preventDefault(); const f = e.dataTransfer?.files?.[0]; if (f) onFileDrop(f); }}
    >
      {generating && <GeneratingOverlay/>}
      <div
        style={{
          width: `min(${600 * zoom}px, ${90 * zoom}%)`,
          aspectRatio: '1/1',
          background: '#fff',
          border: '1px solid var(--line)',
          borderRadius: 4,
          boxShadow: 'var(--shadow-md)',
          overflow: 'hidden',
          position: 'relative',
          cursor: tool === 'hand' ? 'grab' : (tool === 'eyedrop' ? 'crosshair' : 'cell'),
          // let pencil/eraser drag-paint on touch without the page scrolling away
          touchAction: tool === 'hand' ? 'auto' : 'none',
        }}
        onMouseLeave={onCellLeave}
      >
        <div
          style={{
            display: 'grid',
            gridTemplateColumns: `repeat(${gridSize}, 1fr)`,
            gridAutoRows: '1fr',
            width: '100%',
            height: '100%',
            userSelect: 'none',
          }}
          onTouchMove={(e) => {
            // touch dragging doesn't fire mouseenter — hit-test the finger position
            // and paint the cell underneath so drag-to-draw works on phones.
            const t = e.touches[0];
            if (!t) return;
            const el = document.elementFromPoint(t.clientX, t.clientY);
            if (el && el.dataset && el.dataset.cx !== undefined) {
              onCellEnter(Number(el.dataset.cx), Number(el.dataset.cy));
            }
          }}
        >
          {grid.flatMap((row, y) => row.map((id, x) => (
            <div
              key={`${x},${y}`}
              className="pixel-cell"
              data-cx={x}
              data-cy={y}
              style={{ background: paletteHex[id] || '#FFF6D8' }}
              onMouseDown={() => onCellDown(x, y)}
              onMouseEnter={() => onCellEnter(x, y)}
              onTouchStart={(e) => { e.preventDefault(); onCellDown(x, y); }}
            />
          )))}
        </div>
        {gridSize <= 50 && (
          <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none', backgroundImage: `linear-gradient(to right, transparent calc(50% - 0.5px), rgba(17,17,17,0.06) calc(50% - 0.5px), rgba(17,17,17,0.06) calc(50% + 0.5px), transparent calc(50% + 0.5px)), linear-gradient(to bottom, transparent calc(50% - 0.5px), rgba(17,17,17,0.06) calc(50% - 0.5px), rgba(17,17,17,0.06) calc(50% + 0.5px), transparent calc(50% + 0.5px))` }}/>
        )}
      </div>
    </div>
  );
}

function GeneratingOverlay() {
  return (
    <div style={{ position: 'absolute', inset: 0, background: 'rgba(247,242,234,0.85)', backdropFilter: 'blur(4px)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 5 }}>
      <div className="text-center">
        <div style={{ width: 56, height: 56, background: 'var(--yellow)', border: '1.5px solid var(--ink)', borderRadius: 14, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', animation: 'wobble 1s ease-in-out infinite' }}>
          <window.Icon.Wand size={24}/>
        </div>
        <div className="font-display mt-4" style={{ fontSize: 18 }}>Mapping pixels to beads…</div>
        <div className="font-mono mt-1" style={{ fontSize: 11, color: 'var(--muted)' }}>Snapping each pixel to the nearest bead color</div>
        <div style={{ width: 200, height: 4, background: 'rgba(17,17,17,0.1)', borderRadius: 999, marginTop: 14, overflow: 'hidden', display: 'inline-block' }}>
          <div style={{ height: '100%', width: '50%', background: 'var(--ink)', animation: 'load 1.2s ease-in-out infinite' }}/>
        </div>
      </div>
    </div>
  );
}

// ── Bottom Palette Bar ───────────────────────────────────────────────
function PaletteBar({ palette, selected, setSelected, brand }) {
  const bead = palette.find(p => p.id === selected) || palette[0];
  return (
    <div style={{ padding: '12px 16px', borderTop: '2px solid var(--ink)', background: 'var(--lt2)' }}>
      <div className="flex items-center justify-between mb-2">
        <div className="flex items-center gap-3">
          <div className="font-mono" style={{ fontSize: 10, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--muted)' }}>Color</div>
          <div className="flex items-center gap-2" style={{ padding: '4px 10px', background: 'rgba(17,17,17,0.04)', borderRadius: 999 }}>
            <div style={{ width: 14, height: 14, background: bead.hex, borderRadius: 3, border: '1px solid var(--ink)' }}/>
            <span className="font-mono" style={{ fontSize: 11 }}>{bead.code}</span>
            <span style={{ fontSize: 12 }}>{bead.name}</span>
            <span className="font-mono" style={{ fontSize: 11, color: 'var(--muted)' }}>{bead.hex.toUpperCase()}</span>
          </div>
        </div>
        <div className="flex items-center gap-2 font-mono" style={{ fontSize: 11, color: 'var(--muted)' }}>
          {brand} · {palette.length} colors
        </div>
      </div>
      <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
        {palette.map(c => (
          <window.ColorSwatch key={c.id} color={c} selected={c.id === selected} onClick={() => setSelected(c.id)} size={22}/>
        ))}
      </div>
    </div>
  );
}

// ── Right Order Summary ──────────────────────────────────────────────
function CreatorOrderSummary({ pattern, counts, brand, onAdd, canAdd }) {
  if (!pattern) return null;
  const brandDef = window.BEAD_BRANDS[brand];
  return (
    <div className="panel" style={{ overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
      <div style={{ padding: 20, borderBottom: '1px solid var(--line)' }}>
        <div className="flex items-center justify-between">
          <span className="tag tag-yellow">YOU DESIGN</span>
          {pattern.isDemo && <span className="tag" style={{ background: '#fff' }}>DEMO</span>}
        </div>
        <h2 className="font-display mt-3" style={{ fontSize: 22, letterSpacing: '-0.02em' }}>Custom Pixel Kit</h2>
        <div className="flex items-baseline gap-2 mt-2">
          <span className="font-display" style={{ fontSize: 36, letterSpacing: '-0.03em' }}>${pattern.estimatedPrice.toFixed(2)}</span>
          <span style={{ fontSize: 12, color: 'var(--muted)' }}>estimated</span>
        </div>
        <div className="font-mono mt-1" style={{ fontSize: 11, color: 'var(--muted)' }}>
          {brand} · {pattern.gridSize}×{pattern.gridSize} · ships in 2 business days
        </div>
        <PriceBreakdown pattern={pattern} brandDef={brandDef}/>
      </div>

      <div style={{ padding: 20, borderBottom: '1px solid var(--line)' }}>
        <div className="font-mono mb-3" style={{ fontSize: 10, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--muted)' }}>Summary</div>
        <div className="grid grid-cols-2 gap-2">
          <SummaryStat label="Total beads"    value={pattern.totalBeads.toLocaleString()}/>
          <SummaryStat label="Unique colors" value={pattern.colorCount}/>
          <SummaryStat label="Grid"          value={`${pattern.gridSize}×${pattern.gridSize}`}/>
          <SummaryStat label="Build time"    value={`~${Math.round(pattern.gridSize*pattern.gridSize/700)}h`}/>
        </div>
      </div>

      <div style={{ flex: 1, overflowY: 'auto', padding: 20 }}>
        <window.BeadCountList counts={counts} max={null} dense/>
      </div>

      <div style={{ padding: 20, borderTop: '2px solid var(--ink)', background: 'var(--lt2)' }}>
        <button onClick={onAdd} disabled={!canAdd} className="btn btn-yellow" style={{ width: '100%', height: 52, opacity: canAdd ? 1 : 0.4, cursor: canAdd ? 'pointer' : 'not-allowed' }}>
          <window.Icon.Cart size={16}/> Add Custom Kit · ${pattern.estimatedPrice.toFixed(2)}
        </button>
        {!canAdd && pattern.isDemo && (
          <div className="font-mono mt-2 text-center" style={{ fontSize: 11, color: 'var(--muted)' }}>Upload a photo to enable checkout</div>
        )}
        <div className="flex items-center justify-between mt-3 font-mono" style={{ fontSize: 10, color: 'var(--muted)' }}>
          <span className="flex items-center gap-1.5"><window.Icon.Truck size={11}/>Free shipping</span>
          <span className="flex items-center gap-1.5"><window.Icon.Check size={11}/>30-day returns</span>
          <span className="flex items-center gap-1.5"><window.Icon.Leaf size={11}/>Bio plastic</span>
        </div>
      </div>
    </div>
  );
}

function PriceBreakdown({ pattern, brandDef }) {
  const beadCost = (pattern.totalBeads / 1000) * brandDef.basePrice;
  const handling = pattern.colorCount * 0.15;
  return (
    <div className="mt-4" style={{ borderTop: '1px dashed var(--line-2)', paddingTop: 12 }}>
      <PriceRow label="Base kit fee" value="$9.99"/>
      <PriceRow label={`Beads · ${pattern.totalBeads.toLocaleString()} × $${brandDef.basePrice.toFixed(2)}/1k`} value={`$${beadCost.toFixed(2)}`}/>
      <PriceRow label={`Color handling · ${pattern.colorCount} × $0.15`} value={`$${handling.toFixed(2)}`}/>
    </div>
  );
}
function PriceRow({ label, value }) {
  return (
    <div className="flex items-center justify-between" style={{ padding: '4px 0' }}>
      <span className="font-mono" style={{ fontSize: 11, color: 'var(--muted)' }}>{label}</span>
      <span className="font-mono" style={{ fontSize: 11 }}>{value}</span>
    </div>
  );
}
function SummaryStat({ label, value }) {
  return (
    <div style={{ background: 'rgba(17,17,17,0.04)', padding: '10px 12px', borderRadius: 10 }}>
      <div className="font-mono" style={{ fontSize: 10, color: 'var(--muted)', textTransform: 'uppercase', letterSpacing: '0.1em' }}>{label}</div>
      <div className="font-display" style={{ fontSize: 16, marginTop: 2, letterSpacing: '-0.02em' }}>{value}</div>
    </div>
  );
}
