// Creatives section — ad creative generator

const { useState: CR_useState, useEffect: CR_useEffect } = React;

const CREATIVE_TOPICS = [
  "Health problems",
  "Digital Threats",
  "Parent–Child Relationship",
  "Behavior & Personality Changes",
  "Brain & Cognitive Development",
  "Single parents + parents with night shifts",
  "summer holidays",
  "Kidden Features Showcase",
  "Threat from AI",
  "money scarcity",
  "Countries + Privilidged families",
];

const CREATIVE_ANGLES = [
  "Worried parent",
  "Hopeful / reassured parent",
  "Child's perspective",
  "Before & after",
  "Real family moment",
  "Statistics & data",
  "Expert tip",
  "Humor / lighthearted",
  "Emotional appeal",
  "Problem → solution",
];

const CREATIVE_STATUSES = [
  { id: 'draft',    label: 'Draft'    },
  { id: 'ready',    label: 'Ready'    },
  { id: 'in_test',  label: 'In test'  },
  { id: 'tested',   label: 'Tested'   },
  { id: 'winner',   label: 'Winner'   },
  { id: 'archived', label: 'Archived' },
];

const normalizeStatus = (s) => (!s || s === 'active') ? 'draft' : s;

const CREATIVE_PRODUCERS = [
  { abbr: 'AHB', name: 'Anhelina Halbul' },
  { abbr: 'ASM', name: 'Antonina Samoliuk' },
  { abbr: 'ASR', name: 'Artem Sierov' },
  { abbr: 'DDT', name: 'Diana Drobotey' },
  { abbr: 'DKR', name: 'Danylo Kyrylov' },
  { abbr: 'KIS', name: 'Kseniia Ilienko' },
  { abbr: 'KZA', name: 'Kseniia Zadoia' },
  { abbr: 'NBL', name: 'Nataliia Bielousova' },
  { abbr: 'RSK', name: 'Romana Skrabut' },
  { abbr: 'SMV', name: 'Sofiia Matviikiv' },
  { abbr: 'TMK', name: 'Tetiana Melnyk' },
  { abbr: 'VTL', name: 'Vladyslava Tsymbal' },
  { abbr: 'YKH', name: 'Yulia Khomukha' },
];

function CPSelect({ value, onChange, disabled }) {
  const [open, setOpen] = CR_useState(false);
  const [search, setSearch] = CR_useState('');
  const triggerRef = React.useRef(null);
  const searchRef = React.useRef(null);
  const wrapRef = React.useRef(null);
  const [pos, setPos] = CR_useState({ top: 0, left: 0 });

  const filtered = CREATIVE_PRODUCERS.filter(p =>
    p.abbr.toLowerCase().includes(search.toLowerCase()) ||
    p.name.toLowerCase().includes(search.toLowerCase())
  );
  const selected = CREATIVE_PRODUCERS.find(p => p.abbr === value);

  CR_useEffect(() => {
    if (open && triggerRef.current) {
      const r = triggerRef.current.getBoundingClientRect();
      setPos({ top: r.bottom + 4, left: r.left });
    }
  }, [open]);

  CR_useEffect(() => {
    if (open) setTimeout(() => searchRef.current?.focus(), 10);
  }, [open]);

  CR_useEffect(() => {
    const onDoc = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target) &&
          !(e.target.closest && e.target.closest('.cp-dropdown-menu'))) setOpen(false);
    };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, []);

  const handleSelect = (abbr) => { onChange(abbr); setOpen(false); setSearch(''); };

  return (
    <div ref={wrapRef} className="cp-select-wrap">
      <button
        ref={triggerRef}
        className="cp-select-trigger"
        onClick={() => { if (!disabled) setOpen(o => !o); }}
        disabled={disabled}
        type="button"
      >
        {selected
          ? <span>{selected.abbr}</span>
          : <span className="cp-placeholder">—</span>
        }
        <Icon name="chev-down" size={10} />
      </button>
      {open && ReactDOM.createPortal(
        <div className="cp-dropdown-menu" style={{ top: pos.top, left: pos.left }} onMouseDown={e => e.stopPropagation()}>
          <div className="cp-search-wrap">
            <input
              ref={searchRef}
              className="cp-search-input"
              placeholder="Search name or code…"
              value={search}
              onChange={e => setSearch(e.target.value)}
            />
          </div>
          <div className="cp-options">
            {value && (
              <div className="cp-option cp-option-clear" onClick={() => handleSelect('')}>— None —</div>
            )}
            {filtered.map(p => (
              <div key={p.abbr} className={"cp-option" + (p.abbr === value ? ' active' : '')} onClick={() => handleSelect(p.abbr)}>
                <span className="cp-abbr">{p.abbr}</span>
                <span className="cp-name">— {p.name}</span>
              </div>
            ))}
            {filtered.length === 0 && <div className="cp-option cp-option-clear">No results</div>}
          </div>
        </div>,
        document.body
      )}
    </div>
  );
}

function parseCreativeError(raw) {
  if (!raw) return '';
  try {
    // Strip "API 5xx: " prefix to get the JSON body
    const jsonStr = raw.replace(/^API \d+:\s*/, '');
    const outer = JSON.parse(jsonStr);
    if (outer?.error) {
      // The error value may be "ProviderName: <json>" — strip the prefix
      const inner = String(outer.error).replace(/^[^:]+:\s*/, '');
      try {
        const parsed = JSON.parse(inner);
        const msg = parsed?.error?.message || parsed?.message;
        if (msg) return `Generation failed — ${msg}`;
      } catch {}
      // If inner isn't JSON, show it directly
      if (inner) return `Generation failed — ${inner}`;
    }
  } catch {}
  return 'Generation failed. Please try again.';
}

function buildCreativeName(number, variantA, variantB, cpCode) {
  const num = String(number || '').padStart(3, '0');
  const parts = [variantA, variantB].filter(v => v && v.trim());
  const varPart = parts.length > 0 ? '_' + parts.join('_') : '';
  const cp = cpCode || 'ASM';
  return `KD_S_${num}${varPart}_${cp}_EN`;
}

function creativeFormatName(baseName, format) {
  const displayFmt = format === '191x1' ? '1.91x1' : format;
  if (baseName) {
    const match = baseName.match(/^(.*?)_([A-Z]+)_EN$/);
    if (match) return `${match[1]}_${displayFmt}_${match[2]}_EN`;
  }
  return `${baseName || 'creative'}_${displayFmt}`;
}

const CREATIVE_TOPIC_COLORS = {
  "Health problems":                            { bg: '#FAEEDA', color: '#7A4B0C' },
  "Digital Threats":                            { bg: '#FDEBEB', color: '#A32D2D' },
  "Parent–Child Relationship":                  { bg: '#E6F1FB', color: '#1A5FA5' },
  "Behavior & Personality Changes":             { bg: '#FDE8EE', color: '#B5295A' },
  "Brain & Cognitive Development":              { bg: '#E4F6EE', color: '#176B47' },
  "Single parents + parents with night shifts": { bg: '#FFF4D6', color: '#8B6914' },
  "summer holidays":                            { bg: '#E8F8F0', color: '#1a7a56' },
  "Kidden Features Showcase":                   { bg: '#EEEDFE', color: '#534AB7' },
  "Threat from AI":                             { bg: '#FEF0EB', color: '#B84318' },
  "money scarcity":                             { bg: '#FFF4D6', color: '#8B6914' },
  "Countries + Privilidged families":           { bg: '#E6F1FB', color: '#1A5FA5' },
};

function CreativeStatusSelect({ value, onChange }) {
  const [open, setOpen] = CR_useState(false);
  const triggerRef = React.useRef(null);
  const wrapRef = React.useRef(null);
  const [pos, setPos] = CR_useState({ top: 0, left: 0 });
  CR_useEffect(() => {
    if (open && triggerRef.current) {
      const r = triggerRef.current.getBoundingClientRect();
      setPos({ top: r.bottom + 5, left: r.left });
    }
  }, [open]);
  CR_useEffect(() => {
    const onDoc = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target) &&
          !(e.target.closest && e.target.closest('.inline-fixed-menu'))) setOpen(false);
    };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, []);
  const norm = normalizeStatus(value);
  const current = CREATIVE_STATUSES.find(s => s.id === norm) || CREATIVE_STATUSES[0];
  return (
    <div ref={wrapRef} className="inline-sel-wrap" onClick={e => e.stopPropagation()}>
      <button ref={triggerRef} className="inline-sel-trigger" onClick={e => { e.stopPropagation(); setOpen(o => !o); }}>
        <span className={"status cs-" + norm}><span className="status-dot" />{current.label}</span>
        <Icon name="chev-down" size={10} />
      </button>
      {open && ReactDOM.createPortal(
        <div className="inline-fixed-menu" style={{ top: pos.top, left: pos.left }} onMouseDown={e => e.stopPropagation()}>
          {CREATIVE_STATUSES.map(s => (
            <div key={s.id} className={"inline-sel-item" + (s.id === norm ? ' active' : '')}
              onClick={e => { e.stopPropagation(); onChange(s.id); setOpen(false); }}>
              <span className={"status cs-" + s.id}><span className="status-dot" /></span>
              <span>{s.label}</span>
            </div>
          ))}
        </div>,
        document.body
      )}
    </div>
  );
}

function CreativeTopicSelect({ value, onChange }) {
  const [open, setOpen] = CR_useState(false);
  const triggerRef = React.useRef(null);
  const wrapRef = React.useRef(null);
  const [pos, setPos] = CR_useState({ top: 0, left: 0 });
  CR_useEffect(() => {
    if (open && triggerRef.current) {
      const r = triggerRef.current.getBoundingClientRect();
      setPos({ top: r.bottom + 5, left: r.left });
    }
  }, [open]);
  CR_useEffect(() => {
    const onDoc = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target) &&
          !(e.target.closest && e.target.closest('.inline-fixed-menu'))) setOpen(false);
    };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, []);
  const tc = CREATIVE_TOPIC_COLORS[value] || { bg: '#f0efed', color: '#6b6680' };
  return (
    <div ref={wrapRef} className="inline-sel-wrap" onClick={e => e.stopPropagation()}>
      <button ref={triggerRef} className="inline-sel-trigger" onClick={e => { e.stopPropagation(); setOpen(o => !o); }}>
        <span className="cat" style={{ background: tc.bg, color: tc.color }}>{value || 'Select topic'}</span>
        <Icon name="chev-down" size={10} />
      </button>
      {open && ReactDOM.createPortal(
        <div className="inline-fixed-menu" style={{ top: pos.top, left: pos.left, minWidth: 240 }} onMouseDown={e => e.stopPropagation()}>
          {CREATIVE_TOPICS.map(t => {
            const c = CREATIVE_TOPIC_COLORS[t] || { bg: '#f0efed', color: '#6b6680' };
            return (
              <div key={t} className={"inline-sel-item" + (t === value ? ' active' : '')}
                onClick={e => { e.stopPropagation(); onChange(t); setOpen(false); }}>
                <span className="cat" style={{ background: c.bg, color: c.color }}>{t}</span>
              </div>
            );
          })}
        </div>,
        document.body
      )}
    </div>
  );
}

const LOGO_VARIANTS = [
  { id: 'aic',    src: 'assets/logos/Logo_Kidden_AIC.png', isSquare: true,  needsBgPad: false, label: 'App icon' },
  { id: 'logo01', src: 'assets/logos/Logo01.png',          isSquare: false, needsBgPad: true,  label: 'Mascot + white text' },
  { id: 'logo02', src: 'assets/logos/Logo02.png',          isSquare: false, needsBgPad: true,  label: 'Logo + white text' },
  { id: 'logo03', src: 'assets/logos/Logo03.png',          isSquare: false, needsBgPad: true,  label: 'Blue text with mascot' },
  { id: 'logo04', src: 'assets/logos/Logo04.png',          isSquare: false, needsBgPad: true,  label: 'Orange text with mascot' },
  { id: 'logo05', src: 'assets/logos/Logo05.png',          isSquare: false, needsBgPad: true,  label: 'White text with mascot' },
  { id: 'text',   src: null,                               isSquare: false, needsBgPad: false, label: 'Text only' },
];

function loadImg(src) {
  return new Promise((res, rej) => {
    const img = new Image();
    img.onload = () => res(img);
    img.onerror = rej;
    img.src = src;
  });
}

// Sample pixel brightness variance in a rectangular region (downsampled for speed)
function zoneStats(data, iw, x, y, w, h) {
  const stride = Math.max(1, Math.floor(Math.sqrt(w * h / 500)));
  let sum = 0, count = 0;
  const samples = [];
  for (let py = y; py < y + h; py += stride) {
    for (let px = x; px < x + w; px += stride) {
      const i = (py * iw + px) * 4;
      const b = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
      samples.push(b); sum += b; count++;
    }
  }
  const mean = sum / count;
  const variance = samples.reduce((s, b) => s + (b - mean) * (b - mean), 0) / count;
  return { variance, mean };
}

// Pick the least-busy zone from 6 candidates (4 corners + center-left + center-right)
function pickBestZone(data, iw, ih, lw, lh, pad) {
  const candidates = [
    { x: pad,            y: pad            }, // top-left
    { x: iw - lw - pad, y: pad            }, // top-right
    { x: pad,            y: ih - lh - pad }, // bottom-left
    { x: iw - lw - pad, y: ih - lh - pad }, // bottom-right
    { x: pad,            y: (ih - lh) >> 1 }, // center-left
    { x: iw - lw - pad, y: (ih - lh) >> 1 }, // center-right
  ].filter(c => c.x >= 0 && c.y >= 0 && c.x + lw <= iw && c.y + lh <= ih);

  let best = candidates[3] || candidates[0]; // fallback: bottom-right
  let bestVariance = Infinity;
  for (const c of candidates) {
    const { variance, mean } = zoneStats(data, iw, c.x, c.y, lw, lh);
    if (variance < bestVariance) { bestVariance = variance; best = { ...c, brightness: mean }; }
  }
  return best;
}

function drawRoundRect(ctx, x, y, w, h, r) {
  ctx.beginPath();
  ctx.moveTo(x + r, y);
  ctx.lineTo(x + w - r, y);
  ctx.arcTo(x + w, y, x + w, y + r, r);
  ctx.lineTo(x + w, y + h - r);
  ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
  ctx.lineTo(x + r, y + h);
  ctx.arcTo(x, y + h, x, y + h - r, r);
  ctx.lineTo(x, y + r);
  ctx.arcTo(x, y, x + r, y, r);
  ctx.closePath();
}

function compositeLogoBlob(imageBase64, logoImg, isSquare, needsBgPad) {
  return new Promise((res, rej) => {
    const img = new Image();
    img.onload = () => {
      const canvas = document.createElement('canvas');
      canvas.width = img.width; canvas.height = img.height;
      const ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0);

      const PAD = Math.max(24, Math.round(Math.min(img.width, img.height) * 0.022));
      const maxW = Math.round(img.width * 0.20);

      let lw, lh;
      if (isSquare) {
        const sz = Math.min(Math.round(Math.min(img.width, img.height) * 0.14), maxW);
        lw = sz; lh = sz;
      } else {
        const scale = Math.min(maxW / logoImg.width, (img.height * 0.10) / logoImg.height);
        lw = Math.round(logoImg.width * scale);
        lh = Math.round(logoImg.height * scale);
      }

      const pixelData = ctx.getImageData(0, 0, img.width, img.height).data;
      const { x, y, brightness } = pickBestZone(pixelData, img.width, img.height, lw, lh, PAD);

      // Add rounded dark bg pad for logos that need contrast (light zone detection)
      if (needsBgPad && brightness > 110) {
        const bp = Math.round(Math.min(lw, lh) * 0.18);
        ctx.save();
        ctx.globalAlpha = 0.52;
        ctx.fillStyle = '#000000';
        drawRoundRect(ctx, x - bp, y - bp, lw + bp * 2, lh + bp * 2, Math.round((lw + bp * 2) * 0.1));
        ctx.fill();
        ctx.restore();
      }

      ctx.drawImage(logoImg, x, y, lw, lh);
      canvas.toBlob(b => b ? res(b) : rej(new Error('toBlob failed')), 'image/png');
    };
    img.onerror = rej;
    img.src = `data:image/png;base64,${imageBase64}`;
  });
}

function compositeTextBlob(imageBase64) {
  return new Promise((res, rej) => {
    const img = new Image();
    img.onload = () => {
      const canvas = document.createElement('canvas');
      canvas.width = img.width; canvas.height = img.height;
      const ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0);

      const PAD = Math.max(24, Math.round(Math.min(img.width, img.height) * 0.022));
      const fontSize = Math.round(img.width * 0.038);
      ctx.font = `bold ${fontSize}px "Inter", "Helvetica Neue", Arial, sans-serif`;
      const tw = Math.ceil(ctx.measureText('Kidden').width);
      const th = Math.ceil(fontSize * 1.25);

      const pixelData = ctx.getImageData(0, 0, img.width, img.height).data;
      const { x, y, brightness } = pickBestZone(pixelData, img.width, img.height, tw, th, PAD);

      ctx.textAlign = 'left';
      ctx.textBaseline = 'top';
      ctx.shadowColor = brightness > 128 ? 'rgba(0,0,0,0.7)' : 'rgba(0,0,0,0.4)';
      ctx.shadowBlur = Math.round(fontSize * 0.3);
      ctx.fillStyle = brightness > 128 ? '#ffffff' : '#ffffff';
      ctx.fillText('Kidden', x, y);
      canvas.toBlob(b => b ? res(b) : rej(new Error('toBlob failed')), 'image/png');
    };
    img.onerror = rej;
    img.src = `data:image/png;base64,${imageBase64}`;
  });
}

function base64ToBlob(b64, type) {
  const bytes = atob(b64);
  const arr = new Uint8Array(bytes.length);
  for (let i = 0; i < bytes.length; i++) arr[i] = bytes.charCodeAt(i);
  return new Blob([arr], { type });
}

const ASPECT_RATIOS = [
  { label: '1:1',    ratio: '1 / 1',    rW: 1,    rH: 1,  format: '1x1'   },
  { label: '1.91:1', ratio: '1.91 / 1', rW: 1.91, rH: 1,  format: '191x1' },
  { label: '4:5',    ratio: '4 / 5',    rW: 4,    rH: 5,  format: '4x5'   },
  { label: '9:16',   ratio: '9 / 16',   rW: 9,    rH: 16, format: '9x16'  },
];

function downloadBlob(blob, filename) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}

async function fetchBlob(url) {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`Failed to fetch ${url}`);
  return res.blob();
}

// Standard social export sizes per format
const EXPORT_SIZES = {
  '1x1':   { w: 1080, h: 1080 },
  '9x16':  { w: 1080, h: 1920 },
  '4x5':   { w: 1536, h: 1920 },
  '191x1': { w: 1080, h:  566 },
};

// Cover-crop source to target dimensions, cropping from center — clean full-bleed, no padding.
function resizeForExport(blob, format) {
  const target = EXPORT_SIZES[format];
  if (!target) return Promise.resolve(blob);
  return new Promise((resolve, reject) => {
    const img = new Image();
    const blobUrl = URL.createObjectURL(blob);
    img.onload = () => {
      URL.revokeObjectURL(blobUrl);
      try {
        const canvas = document.createElement('canvas');
        canvas.width = target.w; canvas.height = target.h;
        const ctx = canvas.getContext('2d');

        // Cover: scale so image fills the entire canvas, crop overflow from center
        const coverScale = Math.max(target.w / img.width, target.h / img.height);
        const srcW = target.w / coverScale;
        const srcH = target.h / coverScale;
        const srcX = (img.width - srcW) / 2;
        const srcY = (img.height - srcH) / 2;
        ctx.drawImage(img, srcX, srcY, srcW, srcH, 0, 0, target.w, target.h);

        canvas.toBlob(b => b ? resolve(b) : reject(new Error('toBlob failed')), 'image/png');
      } catch (e) { reject(e); }
    };
    img.onerror = () => { URL.revokeObjectURL(blobUrl); reject(new Error('Image load failed')); };
    img.src = blobUrl;
  });
}

// Center-crop a base64 PNG to the given aspect ratio using Canvas
function cropToAspectRatio(base64, rW, rH) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => {
      try {
        const srcAspect = img.width / img.height;
        const dstAspect = rW / rH;
        let srcX, srcY, srcW, srcH;
        if (srcAspect > dstAspect) {
          srcH = img.height;
          srcW = img.height * dstAspect;
          srcX = (img.width - srcW) / 2;
          srcY = 0;
        } else {
          srcW = img.width;
          srcH = img.width / dstAspect;
          srcX = 0;
          srcY = (img.height - srcH) / 2;
        }
        const canvas = document.createElement('canvas');
        canvas.width = Math.round(srcW);
        canvas.height = Math.round(srcH);
        canvas.getContext('2d').drawImage(img, srcX, srcY, srcW, srcH, 0, 0, canvas.width, canvas.height);
        canvas.toBlob(b => b ? resolve(b) : reject(new Error('toBlob failed')), 'image/png');
      } catch (e) { reject(e); }
    };
    img.onerror = reject;
    img.src = `data:image/png;base64,${base64}`;
  });
}

async function markExported(name) {
  try {
    await dbFetch(`creatives?name=eq.${encodeURIComponent(name)}`, 'PATCH',
      { exported: true, exported_at: new Date().toISOString() }, 'return=minimal');
  } catch (e) {
    console.error('markExported failed:', e);
  }
}

async function downloadCreativeZip(name, formats) {
  const zip = new JSZip();
  for (const { format } of ASPECT_RATIOS) {
    const url = formats[format];
    if (!url) continue;
    try {
      const raw = await fetchBlob(url);
      const blob = await resizeForExport(raw, format);
      zip.file(`${creativeFormatName(name, format)}.png`, blob);
    } catch (e) {
      console.error(`ZIP: failed to fetch ${format}`, e);
    }
  }
  const zipBlob = await zip.generateAsync({ type: 'blob' });
  downloadBlob(zipBlob, `${name}.zip`);
  markExported(name);
}

async function downloadBulkZip(creatives) {
  const zip = new JSZip();
  for (const c of creatives) {
    const folder = zip.folder(c.name);
    for (const { format } of ASPECT_RATIOS) {
      const url = c.formats[format];
      if (!url) continue;
      try {
        const raw = await fetchBlob(url);
        const blob = await resizeForExport(raw, format);
        folder.file(`${creativeFormatName(c.name, format)}.png`, blob);
      } catch (e) {
        console.error(`ZIP: failed ${c.name}/${format}`, e);
      }
    }
  }
  const zipBlob = await zip.generateAsync({ type: 'blob' });
  downloadBlob(zipBlob, 'kidden_creatives_export.zip');
  creatives.forEach(c => markExported(c.name));
}

const CG_STAGES = [
  "Crafting your creative brief…",
  "Writing the image prompt…",
  "Generating your ad image…",
  "Almost there…",
  "Finalising formats…",
];

function CreativeGenerationLoader({ topic, title = 'Generating creative' }) {
  const [progress, setProgress] = CR_useState(0);
  const [stageIdx, setStageIdx] = CR_useState(0);
  const [visible, setVisible] = CR_useState(true);
  const startRef = React.useRef(Date.now());

  CR_useEffect(() => {
    const iv = setInterval(() => {
      setProgress(Math.min((Date.now() - startRef.current) / 28000, 0.95));
    }, 80);
    return () => clearInterval(iv);
  }, []);

  CR_useEffect(() => {
    const cycle = setInterval(() => {
      setVisible(false);
      setTimeout(() => { setStageIdx(i => (i + 1) % CG_STAGES.length); setVisible(true); }, 300);
    }, 2800);
    return () => clearInterval(cycle);
  }, []);

  return (
    <div className="gen-overlay">
      <div className="gen-card">
        <div className="gen-icon"><Icon name="img-gen" size={20} /></div>
        <div className="gen-title">{title}</div>
        <div className="gen-topic">{topic}</div>
        <div className="gen-progress-wrap">
          <div className="gen-progress-bar" style={{ width: `${progress * 100}%` }} />
        </div>
        <div className={"gen-stage" + (visible ? ' visible' : '')}>{CG_STAGES[stageIdx]}</div>
      </div>
    </div>
  );
}

function CreativePreviewModal({ imageUrls, creativeName, initialIdx, onClose }) {
  const [idx, setIdx] = CR_useState(initialIdx || 0);
  const current = ASPECT_RATIOS[idx];
  const currentUrl = imageUrls[current.format];
  const prev = () => setIdx(i => (i - 1 + ASPECT_RATIOS.length) % ASPECT_RATIOS.length);
  const next = () => setIdx(i => (i + 1) % ASPECT_RATIOS.length);

  CR_useEffect(() => {
    const onKey = (e) => {
      if (e.key === 'Escape') onClose();
      if (e.key === 'ArrowLeft') prev();
      if (e.key === 'ArrowRight') next();
    };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose]);

  return ReactDOM.createPortal(
    <div className="cp-overlay" onClick={onClose}>
      <div className="cp-modal" onClick={e => e.stopPropagation()}>
        <button className="cp-close" onClick={onClose}><Icon name="x" size={16} /></button>

        <div className="cp-stage">
          <button className="cp-arrow cp-arrow-prev" onClick={prev}>
            <span style={{ display: 'inline-flex', transform: 'rotate(180deg)' }}><Icon name="chev-right" size={18} /></span>
          </button>
          <div className="cp-frame-wrap">
            <div className="cp-frame" style={{ aspectRatio: current.ratio }}>
              {currentUrl
                ? <img src={currentUrl} alt={current.label} style={{ width: '100%', height: '100%', objectFit: 'contain', display: 'block' }} />
                : <div className="cg-card-empty"><Icon name="image" size={20} /></div>
              }
            </div>
          </div>
          <button className="cp-arrow cp-arrow-next" onClick={next}>
            <Icon name="chev-right" size={18} />
          </button>
        </div>

        <div className="cp-footer">
          <div className="cp-tabs">
            {ASPECT_RATIOS.map((r, i) => (
              <button key={r.label} className={"cp-tab" + (i === idx ? ' active' : '')} onClick={() => setIdx(i)}>
                {r.label}
              </button>
            ))}
          </div>
          {currentUrl && (
            <button className="btn btn-sm btn-primary" onClick={() => fetchBlob(currentUrl).then(b => resizeForExport(b, current.format)).then(b => { downloadBlob(b, `${creativeFormatName(creativeName, current.format)}.png`); markExported(creativeName); })}>
              <Icon name="download" size={12} /> Download {current.label}
            </button>
          )}
        </div>
      </div>
    </div>,
    document.body
  );
}

// Group flat DB rows by creative name; per name, collect all versions and expose the latest
function groupCreatives(rows) {
  const map = {};
  rows.forEach(r => {
    const v = r.version || 1;
    if (!map[r.name]) {
      map[r.name] = { name: r.name, topic: r.topic, angle: r.angle, created_at: r.created_at, maxVersion: 0, vData: {} };
    }
    const c = map[r.name];
    if (v > c.maxVersion) { c.maxVersion = v; c.topic = r.topic; c.created_at = r.created_at; }
    if (!c.vData[v]) c.vData[v] = { formats: {} };
    const vd = c.vData[v];
    if (r.format && r.image_url) vd.formats[r.format] = r.image_url;
    else if (!r.format && r.image_url && !vd.formats['1x1']) vd.formats['1x1'] = r.image_url;
  });
  return Object.values(map).map(c => ({
    name: c.name, topic: c.topic, angle: c.angle, created_at: c.created_at,
    formats: (c.vData[c.maxVersion] || {}).formats || {},
    version: c.maxVersion,
    versionCount: Object.keys(c.vData).length,
  })).sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
}

function RefImageLightbox({ src, onClose }) {
  CR_useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose]);
  return ReactDOM.createPortal(
    <div className="lightbox-overlay" onMouseDown={onClose}>
      <button className="lightbox-close" onMouseDown={(e) => e.stopPropagation()} onClick={onClose}>
        <Icon name="x" size={16} />
      </button>
      <div className="lightbox-content" onMouseDown={(e) => e.stopPropagation()}>
        <div className="lightbox-img-wrap">
          <img className="lightbox-img" src={src} alt="Reference" />
        </div>
      </div>
    </div>,
    document.body
  );
}

function Creatives({ pendingGenerate, onClearPending, statusFilter, onCreativesUpdated }) {
  const [topic, setTopic] = CR_useState(CREATIVE_TOPICS[0]);
  const [customTopic, setCustomTopic] = CR_useState('');
  const [angle, setAngle] = CR_useState('__none__');
  const [customAngle, setCustomAngle] = CR_useState('');
  const [creativeNumber, setCreativeNumber] = CR_useState('');
  const [variantA, setVariantA] = CR_useState('');
  const [variantB, setVariantB] = CR_useState('');
  const [creativeCP, setCreativeCP] = CR_useState('');
  const effectiveTopic = topic === '__custom__' ? customTopic.trim() : topic;
  const effectiveAngle = angle === '__none__' ? null : angle === '__custom__' ? customAngle.trim() || null : angle;
  const validNumber = /^\d{3}$/.test(creativeNumber);
  const [numberTaken, setNumberTaken] = CR_useState(false);
  const [numberCheckName, setNumberCheckName] = CR_useState('');
  const [generating, setGenerating] = CR_useState(false);
  const [regenerating, setRegenerating] = CR_useState(false);
  const [genError, setGenError] = CR_useState('');
  const [result, setResult] = CR_useState(null); // { creativeName, topic, angle, prompt, imageUrls, version, status }
  const [editablePrompt, setEditablePrompt] = CR_useState('');
  const [versions, setVersions] = CR_useState([]); // all known versions for current creative
  const [recents, setRecents] = CR_useState([]);
  const [loadingRecents, setLoadingRecents] = CR_useState(true);
  const [preview, setPreview] = CR_useState(null);
  const [selected, setSelected] = CR_useState(new Set());
  const [bulkDownloading, setBulkDownloading] = CR_useState(false);
  const [downloadingNames, setDownloadingNames] = CR_useState(new Set());
  const [creativeView, setCreativeViewState] = CR_useState(() => localStorage.getItem('creatives-view') || 'grid');
  const setCreativeView = (mode) => { setCreativeViewState(mode); localStorage.setItem('creatives-view', mode); };
  const [versionIdx, setVersionIdx] = CR_useState(0);
  const [autoGenTrigger, setAutoGenTrigger] = CR_useState(0);
  const [crSearch, setCrSearch] = CR_useState('');
  const [crStatusFilter, setCrStatusFilter] = CR_useState('all');
  const [crSort, setCrSort] = CR_useState('newest');
  const [refImage, setRefImage] = CR_useState(null); // { base64, mediaType, preview }
  const [refComment, setRefComment] = CR_useState('');
  const [refSourceUrl, setRefSourceUrl] = CR_useState(null);
  const [refLightbox, setRefLightbox] = CR_useState(null); // src string when open
  const [foreplayUrl, setForeplayUrl] = CR_useState('');
  const [foreplayLoading, setForeplayLoading] = CR_useState(false);
  const [selectedLogo, setSelectedLogo] = CR_useState(null); // null = no logo; logo id string = add logo
  const foreplayAdId = foreplayUrl.match(/foreplay\.co\/share\/ads\/([^/?#\s]+)/)?.[1] || null;
  const foreplayPreview = foreplayAdId ? `https://api.foreplay.co/preview/og/ads/${foreplayAdId}` : null;
  const resultRef = React.useRef(null);
  const promptTextareaRef = React.useRef(null);
  const numberPadRef = React.useRef(null);
  const variantBRef = React.useRef(null);

  // Check if creative name is already taken (debounced)
  CR_useEffect(() => {
    if (!validNumber) { setNumberTaken(false); setNumberCheckName(''); return; }
    const name = buildCreativeName(creativeNumber, variantA, variantB, creativeCP);
    setNumberCheckName(name);
    const timer = setTimeout(async () => {
      try {
        const rows = await dbFetch(`creatives?name=eq.${encodeURIComponent(name)}&select=name&limit=1`, 'GET');
        setNumberTaken(Array.isArray(rows) && rows.length > 0);
      } catch { setNumberTaken(false); }
    }, 400);
    return () => clearTimeout(timer);
  }, [creativeNumber, variantA, variantB, creativeCP, validNumber]);

  // When a valid Foreplay adId is detected, fetch via proxy + Canvas crop to strip black borders/header
  CR_useEffect(() => {
    if (!foreplayAdId) return;
    let cancelled = false;
    setForeplayLoading(true);
    const ogUrl = `https://api.foreplay.co/preview/og/ads/${foreplayAdId}`;
    const proxyUrl = `${window.CONFIG.WORKER_URL}/api/proxy-image?url=${encodeURIComponent(ogUrl)}`;
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => {
      if (cancelled) return;
      try {
        const canvas = document.createElement('canvas');
        canvas.width = img.width;
        canvas.height = img.height;
        const ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0);
        const { data } = ctx.getImageData(0, 0, img.width, img.height);
        const w = img.width, h = img.height;

        const findBounds = (threshold) => {
          let minX = w, maxX = -1, minY = h, maxY = -1;
          for (let y = 0; y < h; y++) {
            for (let x = 0; x < w; x++) {
              const i = (y * w + x) * 4;
              if ((data[i] + data[i + 1] + data[i + 2]) / 3 > threshold) {
                if (x < minX) minX = x;
                if (x > maxX) maxX = x;
                if (y < minY) minY = y;
                if (y > maxY) maxY = y;
              }
            }
          }
          return maxX >= minX ? { minX, maxX, minY, maxY } : null;
        };

        // Use high threshold to skip dark header + black border; fall back for dark ads
        const bounds = findBounds(100) || findBounds(20);
        if (bounds) {
          const { minX, maxX, minY, maxY } = bounds;
          const cropW = maxX - minX + 1;
          const cropH = maxY - minY + 1;
          if (cropW > 50 && cropH > 50) {
            const crop = document.createElement('canvas');
            crop.width = cropW;
            crop.height = cropH;
            crop.getContext('2d').drawImage(canvas, minX, minY, cropW, cropH, 0, 0, cropW, cropH);
            const dataUrl = crop.toDataURL('image/jpeg', 0.92);
            setRefImage({ preview: dataUrl, base64: dataUrl.split(',')[1], mediaType: 'image/jpeg' });
            setRefSourceUrl(foreplayUrl);
            setForeplayUrl('');
          }
        }
      } catch (e) { /* CORS or canvas error — leave UI as-is */ }
      setForeplayLoading(false);
    };
    img.onerror = () => { if (!cancelled) setForeplayLoading(false); };
    img.src = proxyUrl;
    return () => { cancelled = true; };
  }, [foreplayAdId]);

  const refInputRef = React.useRef(null);

  CR_useEffect(() => {
    const el = promptTextareaRef.current;
    if (!el) return;
    el.style.height = 'auto';
    el.style.height = el.scrollHeight + 'px';
  }, [editablePrompt]);

  // Reset to latest version whenever the versions list changes
  CR_useEffect(() => {
    setVersionIdx(versions.length > 0 ? versions.length - 1 : 0);
  }, [versions.length]);

  // Auto-generate when triggered from "+ New creative" modal
  CR_useEffect(() => {
    if (!pendingGenerate?.topic) return;
    setTopic(pendingGenerate.topic);
    setAngle(pendingGenerate.angle || '__none__');
    if (pendingGenerate.creativeNumber) setCreativeNumber(pendingGenerate.creativeNumber);
    if (pendingGenerate.variantA !== undefined) setVariantA(pendingGenerate.variantA || '');
    if (pendingGenerate.variantB !== undefined) setVariantB(pendingGenerate.variantB || '');
    if (pendingGenerate.creativeCP !== undefined) setCreativeCP(pendingGenerate.creativeCP || '');
    if (pendingGenerate.refImage !== undefined) setRefImage(pendingGenerate.refImage || null);
    if (pendingGenerate.refComment !== undefined) setRefComment(pendingGenerate.refComment || '');
    onClearPending?.();
    setAutoGenTrigger(t => t + 1);
  }, [pendingGenerate?.topic]);

  CR_useEffect(() => {
    if (autoGenTrigger === 0) return;
    handleGenerate();
  }, [autoGenTrigger]);

  const displayedVersion = versions.length > 0 ? versions[versionIdx] : null;
  const displayedImageUrls = displayedVersion?.imageUrls || result?.imageUrls || {};

  const navigateVersionTo = (idx) => {
    setVersionIdx(idx);
    if (versions[idx]) setEditablePrompt(versions[idx].prompt || '');
  };

  const handleUseVersion = async () => {
    const vToPromote = versions[versionIdx];
    if (!vToPromote) return;
    const maxVersion = Math.max(...versions.map(v => v.version));
    const nextVersion = maxVersion + 1;
    try {
      await Promise.all(
        Object.entries(vToPromote.imageUrls).map(([format, url]) =>
          dbFetch('creatives', 'POST', {
            name: result.creativeName,
            topic: result.topic,
            angle: result.angle,
            prompt: vToPromote.prompt,
            image_url: url,
            format,
            version: nextVersion,
          }, 'return=minimal')
        )
      );
      const { versions: updatedVersions } = await loadVersions(result.creativeName);
      setVersions(updatedVersions);
      setResult(r => ({ ...r, imageUrls: vToPromote.imageUrls, version: nextVersion, prompt: vToPromote.prompt }));
      setEditablePrompt(vToPromote.prompt || '');
      loadRecents();
    } catch (e) {
      console.error('Use version failed:', e);
    }
  };

  const handleDeleteVersion = async () => {
    if (versions.length <= 1) return;
    const vToDelete = versions[versionIdx];
    if (!window.confirm(`Delete version v${vToDelete.version}? This cannot be undone.`)) return;
    try {
      await dbFetch(`creatives?name=eq.${encodeURIComponent(result.creativeName)}&version=eq.${vToDelete.version}`, 'DELETE', null, 'return=minimal');
      const { versions: updatedVersions } = await loadVersions(result.creativeName);
      setVersions(updatedVersions);
      if (updatedVersions.length > 0) {
        const newLatest = updatedVersions[updatedVersions.length - 1];
        setVersionIdx(updatedVersions.length - 1);
        setResult(r => ({ ...r, imageUrls: newLatest.imageUrls, version: newLatest.version, prompt: newLatest.prompt }));
        setEditablePrompt(newLatest.prompt || '');
      }
      loadRecents();
    } catch (e) {
      console.error('Delete version failed:', e);
    }
  };

  const displayedRecents = (() => {
    let list = [...recents];
    if (statusFilter) {
      list = list.filter(c => normalizeStatus(c.status) === statusFilter);
    } else if (crStatusFilter === 'all') {
      list = list.filter(c => normalizeStatus(c.status) !== 'archived');
    } else {
      list = list.filter(c => normalizeStatus(c.status) === crStatusFilter);
    }
    if (crSearch.trim()) {
      const q = crSearch.trim().toLowerCase();
      list = list.filter(c =>
        (c.name || '').toLowerCase().includes(q) ||
        (c.topic || '').toLowerCase().includes(q) ||
        (c.angle || '').toLowerCase().includes(q)
      );
    }
    if (crSort === 'newest') list.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
    else if (crSort === 'oldest') list.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
    else if (crSort === 'topic') list.sort((a, b) => (a.topic || '').localeCompare(b.topic || ''));
    else if (crSort === 'status') {
      const ord = ['winner', 'tested', 'in_test', 'ready', 'draft', 'archived'];
      list.sort((a, b) => ord.indexOf(normalizeStatus(a.status)) - ord.indexOf(normalizeStatus(b.status)));
    }
    return list;
  })();

  const statusFilterLabel = {
    ready: 'Ready for test', in_test: 'In test', tested: 'Tested', winner: 'Winners',
  }[statusFilter];

  const toggleSelect = (name) => {
    setSelected(prev => {
      const next = new Set(prev);
      if (next.has(name)) next.delete(name); else next.add(name);
      return next;
    });
  };
  const toggleSelectAll = () => {
    const allNames = displayedRecents.map(c => c.name);
    const allSel = allNames.length > 0 && allNames.every(n => selected.has(n));
    setSelected(allSel ? new Set() : new Set(allNames));
  };
  const allChecked = displayedRecents.length > 0 && displayedRecents.every(c => selected.has(c.name));
  const someChecked = selected.size > 0 && !allChecked;

  const handleCardDownload = async (name, formats) => {
    setDownloadingNames(prev => new Set([...prev, name]));
    try {
      await downloadCreativeZip(name, formats);
    } finally {
      setDownloadingNames(prev => { const s = new Set(prev); s.delete(name); return s; });
    }
  };

  const handleBulkDownload = async () => {
    setBulkDownloading(true);
    try {
      const selectedCreatives = recents.filter(c => selected.has(c.name));
      await downloadBulkZip(selectedCreatives);
    } finally {
      setBulkDownloading(false);
    }
  };

  const handleBulkArchive = async () => {
    const names = [...selected];
    setRecents(prev => prev.map(c => names.includes(c.name) ? { ...c, status: 'archived' } : c));
    setSelected(new Set());
    try {
      await Promise.all(
        names.map(name =>
          dbFetch(`creatives?name=eq.${encodeURIComponent(name)}`, 'PATCH', { status: 'archived' }, 'return=minimal')
        )
      );
    } catch (e) {
      console.error('Bulk archive failed:', e);
      setRecents(prev => prev.map(c => names.includes(c.name) ? { ...c, status: 'draft' } : c));
      setSelected(new Set(names));
    }
  };

  const handleUpdateField = async (name, field, value) => {
    setRecents(prev => prev.map(c => c.name === name ? { ...c, [field]: value } : c));
    try {
      await dbFetch(`creatives?name=eq.${encodeURIComponent(name)}`, 'PATCH', { [field]: value }, 'return=minimal');
    } catch (e) {
      console.error('Update field failed:', e);
      loadRecents();
    }
  };

  const handleArchive = async (name, archive = true) => {
    const newStatus = archive ? 'archived' : 'draft';
    const prevStatus = archive ? 'draft' : 'archived';
    // Optimistic update — update UI immediately
    setRecents(prev => prev.map(c => c.name === name ? { ...c, status: newStatus } : c));
    if (result?.creativeName === name) setResult(r => ({ ...r, status: newStatus }));
    try {
      await dbFetch(`creatives?name=eq.${encodeURIComponent(name)}`, 'PATCH', { status: newStatus }, 'return=minimal');
    } catch (e) {
      console.error('Archive failed:', e);
      // Rollback on failure
      setRecents(prev => prev.map(c => c.name === name ? { ...c, status: prevStatus } : c));
      if (result?.creativeName === name) setResult(r => ({ ...r, status: prevStatus }));
    }
  };

  CR_useEffect(() => { loadRecents(); }, []);

  CR_useEffect(() => {
    history.replaceState(null, '', '#creatives');
  }, []);

  const loadRecents = async () => {
    setLoadingRecents(true);
    try {
      const res = await wFetch('/api/creatives-summary');
      const grouped = res.ok ? await res.json() : [];
      setRecents(grouped || []);
    } catch (e) {
      console.error('Failed to load creatives:', e);
    } finally {
      setLoadingRecents(false);
    }
  };

  const handleRefUpload = (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    e.target.value = '';
    const reader = new FileReader();
    reader.onload = (ev) => {
      const img = new Image();
      img.onload = () => {
        const MAX = 1024;
        const scale = Math.min(1, MAX / Math.max(img.width, img.height));
        const w = Math.round(img.width * scale);
        const h = Math.round(img.height * scale);
        const canvas = document.createElement('canvas');
        canvas.width = w; canvas.height = h;
        canvas.getContext('2d').drawImage(img, 0, 0, w, h);
        const resized = canvas.toDataURL('image/jpeg', 0.85);
        setRefImage({ base64: resized.split(',')[1], mediaType: 'image/jpeg', preview: resized });
      };
      img.src = ev.target.result;
    };
    reader.readAsDataURL(file);
  };

  const handleGenerate = async () => {
    setGenerating(true);
    setGenError('');
    setResult(null);
    try {
      const res = await wFetch('/api/generate-creative', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          topic: effectiveTopic,
          angle: effectiveAngle,
          ...(validNumber ? { creativeName: buildCreativeName(creativeNumber, variantA, variantB, creativeCP) } : {}),
          ...(refImage ? { referenceImageBase64: refImage.base64, referenceImageMediaType: refImage.mediaType } : {}),
          ...(!refImage && foreplayUrl ? { foreplayUrl } : {}),
          ...(refComment.trim() ? { referenceComment: refComment.trim() } : {}),
          logoChoice: selectedLogo,
        }),
      });
      if (!res.ok) {
        const errText = await res.text();
        throw new Error(`API ${res.status}: ${errText}`);
      }
      const data = await res.json();

      const ts = Date.now();
      const imageUrls = {};

      let refImageUrl = null;
      if (refImage) {
        const refChars = atob(refImage.base64);
        const refBytes = new Uint8Array(refChars.length);
        for (let i = 0; i < refChars.length; i++) refBytes[i] = refChars.charCodeAt(i);
        const refBlob = new Blob([refBytes], { type: 'image/jpeg' });
        const { publicUrl: refPubUrl } = await storageUpload(`creatives/refs/${data.creativeName.toLowerCase()}-ref-${ts}.jpg`, refBlob, 'image/jpeg');
        refImageUrl = refPubUrl;
      } else if (foreplayPreview) {
        refImageUrl = foreplayPreview;
      }

      const logoMeta = (selectedLogo && selectedLogo !== 'text') ? LOGO_VARIANTS.find(v => v.id === selectedLogo) : null;
      const logoImg = logoMeta ? await loadImg(logoMeta.src) : null;

      await Promise.all(
        data.images.map(async ({ format, imageBase64 }) => {
          const blob = !selectedLogo
            ? base64ToBlob(imageBase64, 'image/png')
            : selectedLogo === 'text'
              ? await compositeTextBlob(imageBase64)
              : await compositeLogoBlob(imageBase64, logoImg, logoMeta.isSquare, logoMeta.needsBgPad);

          const storagePath = `creatives/${data.creativeName.toLowerCase()}-${format}-${ts}.png`;
          const { publicUrl } = await storageUpload(storagePath, blob, 'image/png');
          imageUrls[format] = publicUrl;

          await dbFetch('creatives', 'POST', {
            name: data.creativeName,
            topic: effectiveTopic,
            angle: effectiveAngle,
            prompt: data.prompt,
            image_url: publicUrl,
            format,
            version: 1,
            ref_comment: refComment.trim() || null,
            ref_image_url: refImageUrl,
            ref_source_url: refSourceUrl || null,
          }, 'return=minimal');
        })
      );

      const v1 = { version: 1, prompt: data.prompt, topic: effectiveTopic, angle: effectiveAngle, imageUrls, created_at: new Date().toISOString() };
      setVersions([v1]);
      setEditablePrompt(data.prompt);
      const usedRefPreview = refImage?.preview || foreplayPreview || null;
      const usedRefComment = refComment.trim();
      const usedSourceUrl = refSourceUrl || null;
      setResult({ creativeName: data.creativeName, topic: effectiveTopic, angle: effectiveAngle, prompt: data.prompt, imageUrls, version: 1, status: 'active', refPreview: usedRefPreview, refComment: usedRefComment, refSourceUrl: usedSourceUrl });
      // Clear all inputs ready for the next creative
      setTopic(CREATIVE_TOPICS[0] || '');
      setAngle('__none__');
      setCustomTopic('');
      setCustomAngle('');
      setCreativeNumber('');
      setVariantA('');
      setVariantB('');
      setCreativeCP('');
      setRefImage(null);
      setRefComment('');
      setRefSourceUrl(null);
      setForeplayUrl('');
      history.replaceState(null, '', '#creatives/' + encodeURIComponent(data.creativeName));
      loadRecents();
      onCreativesUpdated?.();
    } catch (e) {
      console.error('Generate creative failed:', e);
      setGenError(e.message || 'Generation failed. Try again.');
    } finally {
      setGenerating(false);
    }
  };

  const loadVersions = async (name) => {
    try {
      const rows = await dbFetch(`creatives?name=eq.${encodeURIComponent(name)}&order=created_at.asc`, 'GET') || [];
      const vMap = {};
      let creativeStatus = 'active';
      rows.forEach(r => {
        const v = r.version || 1;
        if (!vMap[v]) vMap[v] = { version: v, prompt: r.prompt, topic: r.topic, angle: r.angle, imageUrls: {}, created_at: r.created_at };
        if (r.format && r.image_url) vMap[v].imageUrls[r.format] = r.image_url;
        else if (!r.format && r.image_url && !vMap[v].imageUrls['1x1']) vMap[v].imageUrls['1x1'] = r.image_url;
        if (v === 1) {
          if (!vMap[v].refComment && r.ref_comment) vMap[v].refComment = r.ref_comment;
          if (!vMap[v].refImageUrl && r.ref_image_url) vMap[v].refImageUrl = r.ref_image_url;
          if (!vMap[v].refSourceUrl && r.ref_source_url) vMap[v].refSourceUrl = r.ref_source_url;
        }
        if (r.status === 'archived') creativeStatus = 'archived';
      });
      return { versions: Object.values(vMap).sort((a, b) => a.version - b.version), status: creativeStatus };
    } catch (e) {
      console.error('Failed to load versions:', e);
      return { versions: [], status: 'active' };
    }
  };

  const handleRegenerate = async () => {
    if (!result) return;
    setRegenerating(true);
    setGenError('');
    try {
      const { versions: allVersions } = await loadVersions(result.creativeName);
      const maxVersion = allVersions.length > 0 ? Math.max(...allVersions.map(v => v.version)) : (result.version || 1);
      const nextVersion = maxVersion + 1;

      const res = await wFetch('/api/generate-creative-from-prompt', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ prompt: editablePrompt, creativeName: result.creativeName }),
      });
      if (!res.ok) {
        const errText = await res.text();
        throw new Error(`API ${res.status}: ${errText}`);
      }
      const data = await res.json();
      const ts = Date.now();
      const imageUrls = {};

      const logoMetaR = (selectedLogo && selectedLogo !== 'text') ? LOGO_VARIANTS.find(v => v.id === selectedLogo) : null;
      const logoImgR = logoMetaR ? await loadImg(logoMetaR.src) : null;

      await Promise.all(
        data.images.map(async ({ format, imageBase64 }) => {
          const blob = !selectedLogo
            ? base64ToBlob(imageBase64, 'image/png')
            : selectedLogo === 'text'
              ? await compositeTextBlob(imageBase64)
              : await compositeLogoBlob(imageBase64, logoImgR, logoMetaR.isSquare, logoMetaR.needsBgPad);
          const storagePath = `creatives/${result.creativeName.toLowerCase()}-${format}-v${nextVersion}-${ts}.png`;
          const { publicUrl } = await storageUpload(storagePath, blob, 'image/png');
          imageUrls[format] = publicUrl;
          await dbFetch('creatives', 'POST', {
            name: result.creativeName,
            topic: result.topic || effectiveTopic,
            angle: result.angle !== undefined ? result.angle : effectiveAngle,
            prompt: editablePrompt,
            image_url: publicUrl,
            format,
            version: nextVersion,
          }, 'return=minimal');
        })
      );

      const { versions: updatedVersions } = await loadVersions(result.creativeName);
      setVersions(updatedVersions);
      setResult(r => ({ ...r, prompt: editablePrompt, imageUrls, version: nextVersion }));
      loadRecents();
    } catch (e) {
      console.error('Regenerate failed:', e);
      setGenError(e.message || 'Regeneration failed. Try again.');
    } finally {
      setRegenerating(false);
    }
  };

  const handleRegenerateFromTopic = async () => {
    if (!result) return;
    setRegenerating(true);
    setGenError('');
    try {
      const { versions: allVersions } = await loadVersions(result.creativeName);
      const maxVersion = allVersions.length > 0 ? Math.max(...allVersions.map(v => v.version)) : (result.version || 1);
      const nextVersion = maxVersion + 1;

      const res = await wFetch('/api/generate-creative', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          topic: result.topic || effectiveTopic,
          angle: result.angle !== undefined ? result.angle : effectiveAngle,
          creativeName: result.creativeName,
        }),
      });
      if (!res.ok) throw new Error(`API ${res.status}: ${await res.text()}`);
      const data = await res.json();

      const ts = Date.now();
      const imageUrls = {};
      const logoMetaT = (selectedLogo && selectedLogo !== 'text') ? LOGO_VARIANTS.find(v => v.id === selectedLogo) : null;
      const logoImgT = logoMetaT ? await loadImg(logoMetaT.src) : null;
      await Promise.all(
        data.images.map(async ({ format, imageBase64 }) => {
          const blob = !selectedLogo
            ? base64ToBlob(imageBase64, 'image/png')
            : selectedLogo === 'text'
              ? await compositeTextBlob(imageBase64)
              : await compositeLogoBlob(imageBase64, logoImgT, logoMetaT.isSquare, logoMetaT.needsBgPad);
          const storagePath = `creatives/${result.creativeName.toLowerCase()}-${format}-v${nextVersion}-${ts}.png`;
          const { publicUrl } = await storageUpload(storagePath, blob, 'image/png');
          imageUrls[format] = publicUrl;
          await dbFetch('creatives', 'POST', {
            name: result.creativeName,
            topic: result.topic || effectiveTopic,
            angle: result.angle !== undefined ? result.angle : effectiveAngle,
            prompt: data.prompt,
            image_url: publicUrl,
            format,
            version: nextVersion,
          }, 'return=minimal');
        })
      );

      const { versions: updatedVersions } = await loadVersions(result.creativeName);
      setVersions(updatedVersions);
      setResult(r => ({ ...r, prompt: data.prompt, imageUrls, version: nextVersion }));
      setEditablePrompt(data.prompt);
      loadRecents();
    } catch (e) {
      console.error('Regenerate from topic failed:', e);
      setGenError(e.message || 'Regeneration failed. Try again.');
    } finally {
      setRegenerating(false);
    }
  };

  const openCreativeDetail = async (c) => {
    history.replaceState(null, '', '#creatives/' + encodeURIComponent(c.name));
    setResult({ creativeName: c.name, topic: c.topic || '', angle: c.angle || null, prompt: '', imageUrls: c.formats || {}, version: c.version || 1, status: c.status || 'active' });
    setVersions([]);
    setEditablePrompt('');
    setTimeout(() => resultRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 50);
    const { versions: allVersions, status } = await loadVersions(c.name);
    if (allVersions.length === 0) {
      setResult(null);
      history.replaceState(null, '', '#creatives');
      return;
    }
    const latest = allVersions[allVersions.length - 1];
    const v1 = allVersions.find(v => v.version === 1) || allVersions[0];
    setResult({ creativeName: c.name, topic: c.topic || latest.topic || '', angle: latest.angle || null, prompt: latest.prompt, imageUrls: latest.imageUrls, version: latest.version, status, refComment: v1?.refComment || '', refPreview: v1?.refImageUrl || null, refSourceUrl: v1?.refSourceUrl || null });
    setVersions(allVersions);
    setEditablePrompt(latest.prompt || '');
  };

  const openPreview = (imageUrls, creativeName, initialIdx = 0) => {
    setPreview({ imageUrls, creativeName, initialIdx });
  };

  return (
    <div className="main-inner">
      <div className="main-top">
        <div>
          <div className="page-title">{statusFilterLabel ? statusFilterLabel : 'Creatives'}</div>
          <div className="page-sub">{statusFilterLabel ? 'Filtered view · all creatives' : 'Generate ad images for your campaigns'}</div>
        </div>
      </div>

      <div className="creatives-generator">
        <div className="cg-form">
          <div className="field" style={{ flex: 1, minWidth: 180 }}>
            <label className="field-label">Topic <span className="field-required">*</span></label>
            <select className="field-select" value={topic} onChange={e => { setTopic(e.target.value); setCustomTopic(''); setGenError(''); }} disabled={generating}>
              {CREATIVE_TOPICS.map(t => <option key={t} value={t}>{t}</option>)}
              <option value="__custom__">Custom…</option>
            </select>
          </div>
          <div className="field" style={{ flex: 1, minWidth: 180 }}>
            <label className="field-label">
              Creative angle
              <span className="cg-optional"> — opt.</span>
            </label>
            <select className="field-select" value={angle} onChange={e => { setAngle(e.target.value); setCustomAngle(''); setGenError(''); }} disabled={generating}>
              <option value="__none__">— None —</option>
              {CREATIVE_ANGLES.map(a => <option key={a} value={a}>{a}</option>)}
              <option value="__custom__">Custom…</option>
            </select>
          </div>
          <button
            className="btn btn-primary cg-generate-btn"
            onClick={handleGenerate}
            disabled={generating || !validNumber || (topic === '__custom__' && !customTopic.trim())}
          >
            {generating
              ? <><span className="cg-spinner" /><span>Generating…</span></>
              : <><Icon name="img-gen" size={13} /><span>Generate</span></>
            }
          </button>
        </div>

        {(topic === '__custom__' || angle === '__custom__') && (
          <div className="cg-custom-row">
            {topic === '__custom__' && (
              <div className="field" style={{ flex: 1, marginBottom: 0 }}>
                <label className="field-label">Custom topic</label>
                <input
                  className="field-input"
                  autoFocus
                  placeholder="e.g. Online gaming addiction…"
                  value={customTopic}
                  onChange={e => setCustomTopic(e.target.value)}
                  disabled={generating}
                />
              </div>
            )}
            {angle === '__custom__' && (
              <div className="field" style={{ flex: 1, marginBottom: 0 }}>
                <label className="field-label">Custom angle</label>
                <input
                  className="field-input"
                  autoFocus={topic !== '__custom__'}
                  placeholder="e.g. first-time parent, overwhelmed dad…"
                  value={customAngle}
                  onChange={e => setCustomAngle(e.target.value)}
                  disabled={generating}
                />
              </div>
            )}
          </div>
        )}

        <div className="cg-naming-row">
          <div className="field">
            <label className="field-label">Number <span className="field-required">*</span></label>
            <input
              className="field-input"
              style={{ width: 80 }}
              placeholder="001"
              maxLength={3}
              value={creativeNumber}
              onChange={e => {
                const v = e.target.value.replace(/\D/g, '').slice(0, 3);
                setCreativeNumber(v);
                clearTimeout(numberPadRef.current);
                if (v.length > 0 && v.length < 3) {
                  numberPadRef.current = setTimeout(() => setCreativeNumber(p => p.length > 0 && p.length < 3 ? p.padStart(3, '0') : p), 1500);
                }
              }}
              onBlur={() => { clearTimeout(numberPadRef.current); setCreativeNumber(p => p.length > 0 && p.length < 3 ? p.padStart(3, '0') : p); }}
              disabled={generating}
              style={numberTaken ? { borderColor: '#e53e3e' } : undefined}
            />
            {numberTaken && <div className="field-error">Already used</div>}
          </div>
          <div className="field">
            <label className="field-label">Variant <span className="cg-optional">— opt.</span></label>
            <div className="variant-inputs">
              <span className="variant-sep">_</span>
              <input
                className="variant-char-input"
                maxLength={1}
                placeholder="a"
                value={variantA}
                onChange={e => {
                  const v = e.target.value.replace(/[^a-zA-Z]/g, '').toLowerCase().slice(0, 1);
                  setVariantA(v);
                  if (v) variantBRef.current?.focus();
                }}
                disabled={generating}
              />
              <span className="variant-sep">_</span>
              <input
                className="variant-char-input"
                ref={variantBRef}
                maxLength={1}
                placeholder="b"
                value={variantB}
                onChange={e => setVariantB(e.target.value.replace(/[^a-zA-Z]/g, '').toLowerCase().slice(0, 1))}
                onKeyDown={e => { if (e.key === 'Backspace' && !variantB) { e.preventDefault(); setVariantA(''); e.target.previousElementSibling?.previousElementSibling?.focus(); } }}
                disabled={generating}
              />
              <span className="variant-sep">_</span>
            </div>
          </div>
          <div className="field">
            <label className="field-label">CP <span className="cg-optional">— opt.</span></label>
            <CPSelect value={creativeCP} onChange={setCreativeCP} disabled={generating} />
          </div>
          <div className={"cg-naming-preview" + (validNumber ? ' ready' : '')}>
            {creativeNumber ? buildCreativeName(creativeNumber.padStart(3, '0').slice(0, 3), variantA, variantB, creativeCP) : 'KD_S_???_ASM_EN'}
          </div>
        </div>

        <div className="cg-reference-row">
          <div className="field cg-ref-upload-field">
            <label className="field-label">Reference image <span className="cg-optional">— opt.</span></label>
            {foreplayLoading ? (
              <div className="cg-ref-loading">
                <div className="cg-ref-loading-spinner" />
                <span>Processing image…</span>
              </div>
            ) : (refImage || foreplayPreview) ? (
              <div className="cg-ref-preview">
                <img src={refImage ? refImage.preview : foreplayPreview} alt="Reference" style={{ cursor: 'zoom-in' }} onClick={() => setRefLightbox(refImage ? refImage.preview : foreplayPreview)} />
                <button className="cg-ref-remove" type="button" onClick={() => { setRefImage(null); setRefSourceUrl(null); setForeplayUrl(''); }} title="Remove">×</button>
              </div>
            ) : (
              <>
                <div className="cg-upload-area" onClick={() => !generating && refInputRef.current?.click()}>
                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
                  <span>Upload reference image</span>
                  <input ref={refInputRef} type="file" accept="image/jpeg,image/png,image/webp" style={{ display: 'none' }} onChange={handleRefUpload} disabled={generating} />
                </div>
                <div className="cg-foreplay-or">Or</div>
                <input
                  type="text"
                  className="cg-foreplay-input"
                  placeholder="Paste a link"
                  value={foreplayUrl}
                  onChange={e => setForeplayUrl(e.target.value)}
                  disabled={generating}
                />
              </>
            )}
          </div>
          <div className="field" style={{ flex: 1, display: 'flex', flexDirection: 'column', marginBottom: 0 }}>
            <label className="field-label">Creative direction <span className="cg-optional">— opt.</span></label>
            <div style={{ position: 'relative', flex: 1, display: 'flex', flexDirection: 'column' }}>
              <textarea
                className="cg-ref-comment"
                placeholder="Describe what to copy, change, or use as inspiration — e.g. 'same split-screen layout but warmer colors'"
                value={refComment}
                onChange={e => setRefComment(e.target.value.slice(0, 3000))}
                disabled={generating}
              />
              <span className="cg-ref-counter">{refComment.length}/3000</span>
            </div>
          </div>
        </div>

        <div className="cg-logo-toggle-row">
          <label className="cg-logo-toggle-label">
            <div className={`cg-toggle${selectedLogo ? ' on' : ''}`} onClick={() => !generating && setSelectedLogo(v => v ? null : 'aic')}>
              <div className="cg-toggle-thumb" />
            </div>
            <span>Add Kidden logo</span>
          </label>
        </div>

        {selectedLogo && (
          <div className="cg-logo-picker">
            {LOGO_VARIANTS.map(v => (
              <button
                key={v.id}
                type="button"
                className={`cg-logo-option${selectedLogo === v.id ? ' selected' : ''}`}
                onClick={() => !generating && setSelectedLogo(v.id)}
                title={v.label}
                disabled={generating}
              >
                {v.src
                  ? <img src={v.src} alt={v.label} />
                  : <span className="cg-logo-text-preview">Kidden</span>
                }
                <span className="cg-logo-option-label">{v.label}</span>
              </button>
            ))}
          </div>
        )}

        {genError && (
          <div className="cg-error">
            <Icon name="alert-triangle" size={15} />
            <span>{parseCreativeError(genError)}</span>
          </div>
        )}
      </div>

      {result && (
        <div className="cg-result" ref={resultRef}>
          <div className="cg-result-header">
            <span className="cg-result-name">{result.creativeName}</span>
            {versions.length > 1 && (
              <div className="txt-version-pill">
                <button className="tvp-arrow" disabled={versionIdx === 0}
                  onClick={() => navigateVersionTo(Math.max(0, versionIdx - 1))}>‹</button>
                <span className="tvp-label">{versionIdx + 1}/{versions.length}</span>
                <button className="tvp-arrow" disabled={versionIdx === versions.length - 1}
                  onClick={() => navigateVersionTo(Math.min(versions.length - 1, versionIdx + 1))}>›</button>
                <button className="tvp-delete" title="Delete this version" onClick={handleDeleteVersion}>
                  <Icon name="trash" size={11} />
                </button>
              </div>
            )}
            <span className="cg-result-topic">{result.topic || effectiveTopic}</span>
            <div className="cg-result-actions">
              <button
                className="btn btn-sm"
                onClick={handleRegenerateFromTopic}
                disabled={regenerating}
                title="Generate new version with the same topic and angle"
              >
                <Icon name="img-gen" size={12} /> Regenerate
              </button>
              <button
                className="btn btn-sm"
                onClick={() => handleArchive(result.creativeName, result.status !== 'archived')}
                title={result.status === 'archived' ? 'Unarchive this creative' : 'Archive this creative'}
              >
                <Icon name={result.status === 'archived' ? 'unarchive' : 'archive'} size={12} />
                {result.status === 'archived' ? 'Unarchive' : 'Archive'}
              </button>
            </div>
          </div>
          <div className="cg-cards">
            {ASPECT_RATIOS.map(({ label, ratio, format }, i) => {
              const url = displayedImageUrls[format];
              return (
                <div key={label} className="cg-card">
                  <div className="cg-card-frame" style={{ aspectRatio: ratio }} onClick={() => openPreview(displayedImageUrls, result.creativeName, i)}>
                    {url
                      ? <><img src={url} alt={label} style={{ width: '100%', height: '100%', objectFit: 'contain' }} /><div className="cg-card-zoom"><Icon name="eye" size={16} /></div></>
                      : <div className="cg-card-empty"><Icon name="image" size={16} /></div>
                    }
                  </div>
                  <div className="cg-card-foot">
                    <span className="cg-card-label">{label}</span>
                    {url && (
                      <button className="btn btn-sm" onClick={() => fetchBlob(url).then(b => resizeForExport(b, format)).then(b => { downloadBlob(b, `${creativeFormatName(result.creativeName, format)}.png`); markExported(result.creativeName); })}>
                        <Icon name="download" size={12} /> Download
                      </button>
                    )}
                  </div>
                </div>
              );
            })}
          </div>
          {versions.length > 1 && versionIdx < versions.length - 1 && (
            <button className="txt-use-version-btn" onClick={handleUseVersion}>
              ↩ Use this version
            </button>
          )}
          <div className="cg-prompt-row">
            <div className="cg-prompt-header">
              <span className="cg-prompt-label">Prompt used</span>
              <button
                className="btn btn-sm"
                onClick={handleRegenerate}
                disabled={regenerating || !editablePrompt.trim()}
              >
                {regenerating
                  ? <><span className="cg-spinner" /><span>Regenerating…</span></>
                  : <><Icon name="refresh" size={12} /><span>Regenerate from prompt</span></>
                }
              </button>
            </div>
            <textarea
              ref={promptTextareaRef}
              className="cg-prompt-textarea"
              value={editablePrompt}
              onChange={e => setEditablePrompt(e.target.value)}
              disabled={regenerating}
              rows={1}
            />
            {(result?.refPreview || result?.refComment || result?.refSourceUrl) && (
              <div className="cg-prompt-ref-used">
                {result.refPreview && (
                  <img src={result.refPreview} className="cg-prompt-ref-thumb" alt="Reference used" style={{ cursor: 'zoom-in' }} onClick={() => setRefLightbox(result.refPreview)} />
                )}
                <div className="cg-prompt-ref-text">
                  {result.refSourceUrl && (
                    <a href={result.refSourceUrl} target="_blank" rel="noopener noreferrer" className="cg-prompt-ref-link">{result.refSourceUrl}</a>
                  )}
                  {result.refComment && (
                    <p className="cg-prompt-ref-comment-used">{result.refComment}</p>
                  )}
                </div>
              </div>
            )}
          </div>
        </div>
      )}

      <div className="filter-bar" style={{ marginTop: 32 }}>
        {!statusFilter && <>
          <Pill active={crStatusFilter === 'all'} onClick={() => setCrStatusFilter('all')}>All</Pill>
          {CREATIVE_STATUSES.map(s => (
            <Pill key={s.id} active={crStatusFilter === s.id} onClick={() => setCrStatusFilter(crStatusFilter === s.id ? 'all' : s.id)}>{s.label}</Pill>
          ))}
          <span className="filter-sep">|</span>
        </>}
        <input className="search-input" placeholder="Search by name, topic…" value={crSearch} onChange={e => setCrSearch(e.target.value)} />
        <Dropdown
          value={crSort}
          onChange={setCrSort}
          width={150}
          options={[
            { value: 'newest', label: 'Newest first' },
            { value: 'oldest', label: 'Oldest first' },
            { value: 'topic',  label: 'Topic A–Z' },
            { value: 'status', label: 'Status' },
          ]}
        />
      </div>

      <div className="home-section-head" style={{ marginTop: 16 }}>
        <div className="home-section-title">{statusFilterLabel ? statusFilterLabel : 'Recent creatives'}</div>
        {displayedRecents.length > 0 && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }} onClick={toggleSelectAll}>
              <Checkbox checked={allChecked} indeterminate={someChecked} onChange={toggleSelectAll} />
              <span style={{ fontSize: 13, color: 'var(--text-secondary)' }}>Select all</span>
            </div>
            <div className="view-toggle">
              <button className={"view-toggle-btn" + (creativeView === 'grid' ? ' active' : '')} title="Grid view" onClick={() => setCreativeView('grid')}><Icon name="grid" size={14} /></button>
              <button className={"view-toggle-btn" + (creativeView === 'table' ? ' active' : '')} title="Table view" onClick={() => setCreativeView('table')}><Icon name="list" size={14} /></button>
            </div>
          </div>
        )}
      </div>

      {loadingRecents ? (
        <div className="table-empty">Loading…</div>
      ) : displayedRecents.length === 0 ? (
        <div className="empty-state">
          <div className="glyph"><Icon name="image" size={22} /></div>
          <h3>No creatives yet</h3>
          <p>{recents.length === 0 ? 'Generate your first ad creative above.' : 'All creatives are archived — view them in Archive.'}</p>
        </div>
      ) : creativeView === 'table' ? (
        <div className="table-wrap">
          <div className="table-grid ct-grid table-header">
            <span><Checkbox checked={allChecked} indeterminate={someChecked} onChange={toggleSelectAll} /></span>
            <span></span>
            <span>Name</span>
            <span>Topic</span>
            <span>Angle</span>
            <span>Status</span>
            <span>Formats</span>
            <span>Date</span>
            <span></span>
          </div>
          {displayedRecents.map(c => {
            const thumb = c.formats['1x1'] || Object.values(c.formats)[0] || null;
            const formatCount = Object.keys(c.formats).length;
            const isSel = selected.has(c.name);
            return (
              <div key={c.name} className={"table-grid ct-grid table-row" + (isSel ? ' row-selected' : '')} onClick={() => openCreativeDetail(c)}>
                <span className="cb-cell" onClick={e => { e.stopPropagation(); toggleSelect(c.name); }}>
                  <Checkbox checked={isSel} onChange={() => toggleSelect(c.name)} />
                </span>
                <div className="row-thumb" onClick={e => e.stopPropagation()}>
                  {thumb
                    ? <img src={thumb} alt={c.name} />
                    : <div className="row-thumb-empty"><Icon name="image" size={14} /></div>
                  }
                </div>
                <div className="row-title-cell" onClick={() => openCreativeDetail(c)} style={{ cursor: 'pointer' }}>
                  <span className="row-title">{c.name}</span>
                </div>
                <span><CreativeTopicSelect value={c.topic || ''} onChange={v => handleUpdateField(c.name, 'topic', v)} /></span>
                <span className="updated">{c.angle || '—'}</span>
                <span><CreativeStatusSelect value={c.status} onChange={v => handleUpdateField(c.name, 'status', v)} /></span>
                <span className="updated">{formatCount > 0 ? formatCount : '—'}</span>
                <span className="updated">{new Date(c.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}</span>
                <span className="row-actions" onClick={e => e.stopPropagation()}>
                  {thumb && (
                    <button className="row-action-btn" disabled={downloadingNames.has(c.name)} title="Download" onClick={() => handleCardDownload(c.name, c.formats)}>
                      {downloadingNames.has(c.name) ? <span className="cg-spinner" style={{ width: 13, height: 13 }} /> : <Icon name="download" size={13} />}
                    </button>
                  )}
                  <button className="row-action-btn" title={c.status === 'archived' ? 'Unarchive' : 'Archive'} onClick={() => handleArchive(c.name, c.status !== 'archived')}>
                    <Icon name={c.status === 'archived' ? 'unarchive' : 'archive'} size={13} />
                  </button>
                </span>
              </div>
            );
          })}
        </div>
      ) : (
        <div className="creatives-grid">
          {displayedRecents.map(c => {
            const thumb = c.formats['1x1'] || Object.values(c.formats)[0] || null;
            const formatCount = Object.keys(c.formats).length;
            const isSel = selected.has(c.name);
            const isArchived = c.status === 'archived';
            return (
              <div key={c.name} className={`creative-card${isSel ? ' selected' : ''}${isArchived ? ' archived' : ''}`} onClick={() => openCreativeDetail(c)}>
                <div className="creative-thumb">
                  {thumb
                    ? <>
                        <img src={thumb} alt={c.name} />
                        <div className="cg-card-zoom" onClick={e => { e.stopPropagation(); openPreview(c.formats, c.name, 0); }}>
                          <Icon name="eye" size={16} />
                        </div>
                      </>
                    : <div className="creative-thumb-empty"><Icon name="image" size={20} /></div>
                  }
                  <span className="creative-card-cb" onClick={e => { e.stopPropagation(); toggleSelect(c.name); }}>
                    <Checkbox checked={isSel} onChange={() => toggleSelect(c.name)} />
                  </span>
                </div>
                <div className="creative-info">
                  <div className="creative-name">{c.name}</div>
                  <CreativeTopicSelect value={c.topic || ''} onChange={v => handleUpdateField(c.name, 'topic', v)} />
                  {c.angle && <div className="cc-angle">{c.angle}</div>}
                  <CreativeStatusSelect value={c.status} onChange={v => handleUpdateField(c.name, 'status', v)} />
                  <div className="creative-meta">
                    {new Date(c.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
                    {c.versionCount > 1
                      ? ` · ${c.versionCount} versions`
                      : c.formatCount > 0 ? ` · ${c.formatCount} format${c.formatCount !== 1 ? 's' : ''}` : ''}
                  </div>
                </div>
                <div className="creative-actions" onClick={e => e.stopPropagation()}>
                  {thumb && (
                    <button className="btn btn-sm" disabled={downloadingNames.has(c.name)} onClick={() => handleCardDownload(c.name, c.formats)}>
                      {downloadingNames.has(c.name)
                        ? <><span className="btn-spinner" /> Downloading…</>
                        : <><Icon name="download" size={12} /> Download</>
                      }
                    </button>
                  )}
                  <button
                    className="btn btn-sm creative-archive-btn"
                    title={isArchived ? 'Unarchive' : 'Archive'}
                    onClick={() => handleArchive(c.name, !isArchived)}
                  >
                    <Icon name={isArchived ? 'unarchive' : 'archive'} size={12} />
                  </button>
                </div>
              </div>
            );
          })}
        </div>
      )}

      {preview && (
        <CreativePreviewModal
          imageUrls={preview.imageUrls}
          creativeName={preview.creativeName}
          initialIdx={preview.initialIdx}
          onClose={() => setPreview(null)}
        />
      )}
      {(generating || regenerating) && (
        <CreativeGenerationLoader
          topic={regenerating ? (result?.topic || effectiveTopic) : effectiveTopic}
          title={regenerating ? 'Regenerating creative' : 'Generating creative'}
        />
      )}
      {selected.size > 0 && (
        <div className="float-bar">
          <span className="float-bar-count">{selected.size} selected</span>
          <button className="btn-floatfilled" onClick={handleBulkDownload} disabled={bulkDownloading}>
            <Icon name="download" size={12} /> {bulkDownloading ? 'Downloading…' : 'Download ZIP'}
          </button>
          <button className="btn-floatfilled" onClick={handleBulkArchive}>
            <Icon name="archive" size={12} /> Archive
          </button>
          <span className="clear-link" onClick={() => setSelected(new Set())}>Clear</span>
        </div>
      )}
      {refLightbox && <RefImageLightbox src={refLightbox} onClose={() => setRefLightbox(null)} />}
    </div>
  );
}

window.Creatives = Creatives;
window.downloadCreativeZip = downloadCreativeZip;
window.buildCreativeName = buildCreativeName;
window.creativeFormatName = creativeFormatName;
window.CPSelect = CPSelect;
window.CREATIVE_PRODUCERS = CREATIVE_PRODUCERS;
