/* global React, Icon, StatusPill */
const { useState, useEffect, useRef } = React;

/* ============================================================
   CASE STUDIES — Phase 1: List + Brief editor
   Phase 2 (image library + drag-drop) and Phase 3 (renderer + PDF)
   land on the Images / Preview / Render tabs.
   ============================================================ */

function CaseStudies({ goto }) {
  const [items, setItems] = useState(null);
  const [creating, setCreating] = useState(false);

  const reload = () => fetch('/api/case-studies', { credentials: 'include' })
    .then((r) => r.json())
    .then((d) => setItems(d.items || []))
    .catch(() => setItems([]));
  useEffect(() => { reload(); }, []);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
      <div className="page-header">
        <div className="page-header-left">
          <div className="page-eyebrow">Outreach assets</div>
          <h1 className="page-title">Case Studies</h1>
          <div className="page-subtitle">{items ? `${items.length} case ${items.length === 1 ? 'study' : 'studies'}` : '—'}</div>
        </div>
        <div className="page-header-right">
          <button className="btn btn-primary" onClick={() => setCreating(true)}>
            <Icon name="plus" size={12} /> New case study
          </button>
        </div>
      </div>

      <div style={{ flex: 1, overflow: 'auto', padding: 20 }}>
        {items === null && <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-dim)' }}>Loading…</div>}
        {items && items.length === 0 && (
          <div style={{ padding: 60, textAlign: 'center', color: 'var(--text-dim)', fontSize: 13 }}>
            No case studies yet. Click <strong>+ New case study</strong> to start one.
          </div>
        )}
        {items && items.length > 0 && (
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 12 }}>
            {items.map((cs) => (
              <CaseStudyCard key={cs.id} cs={cs} onOpen={() => goto('case-study-editor', cs.id)} />
            ))}
          </div>
        )}
      </div>

      {creating && (
        <NewCaseStudyModal
          onClose={() => setCreating(false)}
          onCreated={(cs) => { setCreating(false); goto('case-study-editor', cs.id); }}
        />
      )}
    </div>
  );
}

function CaseStudyCard({ cs, onOpen }) {
  const headlineComplete = !!(cs.headline_before && cs.headline_after && cs.headline_time);
  const kpiCount = (cs.kpi_cards || []).length;
  const stackCount = (cs.stack_pillars || []).length;
  return (
    <div className="card" style={{ padding: 16, cursor: 'pointer' }} onClick={onOpen}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
        <div style={{ width: 36, height: 36, background: 'var(--raised)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <Icon name="fileText" size={16} style={{ color: 'var(--accent)' }} />
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13, fontWeight: 600 }}>{cs.client_name}</div>
          <div style={{ fontSize: 10, color: 'var(--text-dim)' }} className="mono">{cs.client_slug}</div>
        </div>
        <StatusPill status={cs.status} />
      </div>
      {headlineComplete && (
        <div style={{ fontSize: 12, color: 'var(--text-secondary)', lineHeight: 1.5, marginBottom: 8 }}>
          From <span style={{ color: 'var(--accent)' }}>{cs.headline_before}</span> to <span style={{ color: 'var(--accent)' }}>{cs.headline_after}</span> in <span style={{ color: 'var(--accent)' }}>{cs.headline_time}</span>.
        </div>
      )}
      <div style={{ display: 'flex', gap: 12, fontSize: 11, color: 'var(--text-dim)' }}>
        <span><span className="mono" style={{ color: 'var(--text)' }}>{kpiCount}</span> KPIs</span>
        <span><span className="mono" style={{ color: 'var(--text)' }}>{stackCount}</span> pillars</span>
        {cs.last_rendered_at && <span><Icon name="check" size={9} /> rendered</span>}
      </div>
    </div>
  );
}

function NewCaseStudyModal({ onClose, onCreated }) {
  const [name, setName] = useState('');
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState(null);

  const submit = async () => {
    if (!name.trim()) { setError('Client name is required'); return; }
    setBusy(true);
    setError(null);
    try {
      const r = await fetch('/api/case-studies', {
        method: 'POST', credentials: 'include',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ client_name: name.trim() }),
      });
      if (!r.ok) { const d = await r.json().catch(() => ({})); throw new Error(d.detail || 'create failed'); }
      const data = await r.json();
      onCreated(data);
    } catch (e) { setError(e.message); } finally { setBusy(false); }
  };

  return (
    <div className="overlay" onClick={onClose}>
      <div onClick={(e) => e.stopPropagation()} style={{ background: 'var(--surface)', border: '1px solid var(--divider)', width: 480, padding: 28 }}>
        <div className="label" style={{ marginBottom: 6 }}>Outreach assets</div>
        <h2 style={{ fontFamily: 'var(--font-display)', fontStretch: '75%', fontWeight: 800, fontSize: 22, margin: '0 0 16px' }}>New case study</h2>
        <label className="label">Client name</label>
        <input
          className="input"
          autoFocus
          value={name}
          onChange={(e) => setName(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && submit()}
          placeholder="e.g. Stride · Hintime · EnduraXcel"
          style={{ width: '100%', marginBottom: 16 }}
        />
        {error && <div style={{ color: 'var(--red)', fontSize: 12, marginBottom: 10 }}>{error}</div>}
        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" onClick={submit} disabled={busy}>{busy ? 'Creating…' : 'Create'}</button>
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   EDITOR
   ============================================================ */

function CaseStudyEditor({ caseStudyId, goto }) {
  const [cs, setCs] = useState(null);
  const [tab, setTab] = useState('brief');
  const [savedAt, setSavedAt] = useState(null);

  const reload = async () => {
    const d = await fetch(`/api/case-studies/${caseStudyId}`, { credentials: 'include' }).then((r) => r.json());
    setCs(d);
  };
  useEffect(() => { reload(); }, [caseStudyId]);

  // Auto-save with a small debounce on field blur (handled per-field in BriefEditor)
  const patch = async (data) => {
    const r = await fetch(`/api/case-studies/${caseStudyId}`, {
      method: 'PATCH', credentials: 'include',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(data),
    });
    if (r.ok) {
      const updated = await r.json();
      setCs(updated);
      setSavedAt(new Date());
    }
  };

  if (!cs) return <div style={{ padding: 60, color: 'var(--text-dim)' }}>Loading…</div>;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
      <div className="page-header">
        <div className="page-header-left">
          <button className="btn btn-ghost btn-sm" style={{ padding: '4px 6px', marginBottom: 6 }} onClick={() => goto('case-studies')}>
            <Icon name="chevLeft" size={12} /> Case Studies
          </button>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <input
              className="input"
              value={cs.client_name}
              onChange={(e) => setCs({ ...cs, client_name: e.target.value })}
              onBlur={() => patch({ client_name: cs.client_name })}
              style={{ fontFamily: 'var(--font-display)', fontStretch: '75%', fontWeight: 800, fontSize: 22, padding: '4px 8px', minWidth: 320 }}
            />
            <StatusPill status={cs.status} />
            {savedAt && <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>Saved {savedAt.toLocaleTimeString()}</span>}
          </div>
        </div>
      </div>

      <div className="tabs">
        {[['brief','Brief'],['images','Images'],['preview','Preview'],['render','Render']].map(([k,l]) => (
          <div key={k} className={`tab ${tab===k?'active':''}`} onClick={() => setTab(k)}>{l}</div>
        ))}
      </div>

      <div style={{ flex: 1, overflow: 'hidden', padding: 0 }}>
        {tab === 'brief' && <div style={{ height: '100%', overflow: 'auto', padding: 28 }}><BriefEditor cs={cs} setCs={setCs} patch={patch} /></div>}
        {tab === 'images' && <ImagesTab cs={cs} reload={reload} />}
        {tab === 'preview' && <PreviewTab cs={cs} />}
        {tab === 'render' && <RenderTab cs={cs} reload={reload} />}
      </div>
    </div>
  );
}

function ComingSoon({ msg }) {
  return (
    <div style={{ maxWidth: 560, padding: 32, textAlign: 'center', color: 'var(--text-dim)', fontSize: 13, margin: '40px auto', border: '1px dashed var(--divider)' }}>
      {msg}
    </div>
  );
}

/* ============================================================
   BRIEF EDITOR
   ============================================================ */

function BriefEditor({ cs, setCs, patch }) {
  // Helpers — local set updates state, blur sends a PATCH
  const set = (field, value) => setCs({ ...cs, [field]: value });
  const save = (field, value) => patch({ [field]: value });

  return (
    <div style={{ maxWidth: 880, display: 'grid', gap: 24 }}>
      {/* Identity + dates */}
      <Section title="Identity" eyebrow="Step 1">
        <Row>
          <CSField label="Client name">
            <input className="input" value={cs.client_name || ''} onChange={(e) => set('client_name', e.target.value)} onBlur={(e) => save('client_name', e.target.value)} style={{ width: '100%' }} />
          </CSField>
          <CSField label="Slug (used in filenames)">
            <input className="input" value={cs.client_slug || ''} onChange={(e) => set('client_slug', e.target.value)} onBlur={(e) => save('client_slug', e.target.value)} style={{ width: '100%' }} />
          </CSField>
        </Row>
        <Row>
          <CSField label="Engagement start date">
            <input className="input" type="date" value={cs.engagement_start_date || ''} onChange={(e) => set('engagement_start_date', e.target.value)} onBlur={(e) => save('engagement_start_date', e.target.value)} style={{ width: '100%' }} />
          </CSField>
          <CSField label="Current state">
            <select className="input" value={cs.current_state || 'ongoing'} onChange={(e) => { set('current_state', e.target.value); save('current_state', e.target.value); }} style={{ width: '100%' }}>
              <option value="ongoing">Ongoing</option>
              <option value="completed">Completed</option>
            </select>
          </CSField>
        </Row>
      </Section>

      {/* Headline */}
      <Section title="Headline (locked pattern)" eyebrow="Step 2">
        <div style={{ fontSize: 12, color: 'var(--text-dim)', marginBottom: 12, lineHeight: 1.55 }}>
          The cover headline always reads:{' '}
          <span style={{ color: 'var(--text)' }}>From <strong>{cs.headline_before || '[BEFORE]'}</strong> to <strong>{cs.headline_after || '[AFTER]'}</strong> in just <strong>{cs.headline_time || '[TIME]'}</strong>.</span>
        </div>
        <Row>
          <CSField label="Before state">
            <input className="input" value={cs.headline_before || ''} onChange={(e) => set('headline_before', e.target.value)} onBlur={(e) => save('headline_before', e.target.value)} placeholder="e.g. manual reorders" style={{ width: '100%' }} />
          </CSField>
          <CSField label="After (quantified result)">
            <input className="input" value={cs.headline_after || ''} onChange={(e) => set('headline_after', e.target.value)} onBlur={(e) => save('headline_after', e.target.value)} placeholder="e.g. 200+ new subscribers" style={{ width: '100%' }} />
          </CSField>
          <CSField label="Time period">
            <input className="input" value={cs.headline_time || ''} onChange={(e) => set('headline_time', e.target.value)} onBlur={(e) => save('headline_time', e.target.value)} placeholder="e.g. three months" style={{ width: '100%' }} />
          </CSField>
        </Row>
        <CSField label="Subhead paragraph (cover sub copy)">
          <textarea className="textarea" rows={3} value={cs.subhead_paragraph || ''} onChange={(e) => set('subhead_paragraph', e.target.value)} onBlur={(e) => save('subhead_paragraph', e.target.value)} placeholder="What was missing + what we built. Keep brand-recognition lines here, never numbers." style={{ width: '100%' }} />
        </CSField>
      </Section>

      {/* KPI cards */}
      <Section title="Cover KPI cards" eyebrow="Step 3" hint="4 cards. Use ROAS / counts / growth %. Never absolute revenue.">
        <ListEditor
          items={cs.kpi_cards}
          onChange={(items) => { set('kpi_cards', items); save('kpi_cards', items); }}
          newItem={() => ({ label: '', value: '', unit: '', sub_text: '' })}
          maxItems={4}
          renderItem={(item, update) => (
            <Row>
              <CSField label="Label" small>
                <input className="input" value={item.label || ''} onChange={(e) => update({ label: e.target.value })} placeholder="e.g. CUSTOMERS" style={{ width: '100%' }} />
              </CSField>
              <CSField label="Value" small>
                <input className="input" value={item.value || ''} onChange={(e) => update({ value: e.target.value })} placeholder="e.g. 15,000" style={{ width: '100%' }} />
              </CSField>
              <CSField label="Unit" small hint="+ / x / % / AED / x+">
                <input className="input" value={item.unit || ''} onChange={(e) => update({ unit: e.target.value })} placeholder="e.g. +" style={{ width: '100%' }} />
              </CSField>
              <CSField label="Sub" small>
                <input className="input" value={item.sub_text || ''} onChange={(e) => update({ sub_text: e.target.value })} placeholder="e.g. orders to date" style={{ width: '100%' }} />
              </CSField>
            </Row>
          )}
        />
      </Section>

      {/* Before block */}
      <Section title="Before — what they had / didn't have" eyebrow="Step 4">
        <Row>
          <CSField label="What they had (5-6 items)">
            <BulletList items={cs.before_had} onChange={(items) => { set('before_had', items); save('before_had', items); }} placeholder="e.g. Strong B2B network with 80+ hotels" />
          </CSField>
          <CSField label="What they didn't have (5-6 items)">
            <BulletList items={cs.before_didnt} onChange={(items) => { set('before_didnt', items); save('before_didnt', items); }} placeholder="e.g. No D2C channel" />
          </CSField>
        </Row>
        <CSField label="Optional callout" hint="Use 'problem' for a red-bordered statement, 'logos' for a 5×2 hotel logo strip.">
          <select className="input" value={cs.before_callout?.type || 'none'} onChange={(e) => {
            const t = e.target.value;
            const v = t === 'none' ? null : { type: t, content: cs.before_callout?.content || '' };
            set('before_callout', v); save('before_callout', v);
          }} style={{ width: '100%', marginBottom: 8 }}>
            <option value="none">None</option>
            <option value="problem">Problem callout (red border)</option>
            <option value="logos">B2B logo strip placeholder (10 cells)</option>
          </select>
          {cs.before_callout?.type === 'problem' && (
            <textarea className="textarea" rows={2} value={cs.before_callout?.content || ''} onChange={(e) => {
              const v = { type: 'problem', content: e.target.value };
              set('before_callout', v);
            }} onBlur={(e) => save('before_callout', { type: 'problem', content: e.target.value })} placeholder="One-line statement of the problem" style={{ width: '100%' }} />
          )}
        </CSField>
      </Section>

      {/* Stack pillars */}
      <Section title="What we built (the stack)" eyebrow="Step 5" hint="6 numbered pillars. Each: short title + 2-3 sentence body.">
        <ListEditor
          items={cs.stack_pillars}
          onChange={(items) => { set('stack_pillars', items); save('stack_pillars', items); }}
          newItem={(idx) => ({ number: String(idx + 1).padStart(2, '0'), title: '', body: '' })}
          maxItems={6}
          renderItem={(item, update, idx) => (
            <Row>
              <CSField label="#" small>
                <input className="input" value={item.number || String(idx + 1).padStart(2, '0')} onChange={(e) => update({ number: e.target.value })} style={{ width: 60 }} />
              </CSField>
              <CSField label="Title" small>
                <input className="input" value={item.title || ''} onChange={(e) => update({ title: e.target.value })} placeholder="e.g. SUBSCRIPTION ENGINE" style={{ width: '100%' }} />
              </CSField>
              <CSField label="Body" small>
                <textarea className="textarea" rows={2} value={item.body || ''} onChange={(e) => update({ body: e.target.value })} placeholder="Short description of the pillar." style={{ width: '100%', fontSize: 12 }} />
              </CSField>
            </Row>
          )}
        />
        <CSField label="Bottom callout (optional)">
          <input className="input" value={cs.stack_callout || ''} onChange={(e) => set('stack_callout', e.target.value)} onBlur={(e) => save('stack_callout', e.target.value)} placeholder="e.g. No aggregator. No middlemen. Direct only." style={{ width: '100%' }} />
        </CSField>
      </Section>

      {/* Chart */}
      <Section title="Numbers page chart" eyebrow="Step 6">
        <CSField label="Chart type">
          <select className="input" value={cs.chart_type || 'none'} onChange={(e) => { set('chart_type', e.target.value); save('chart_type', e.target.value); }} style={{ width: '100%' }}>
            <option value="none">No chart (skip Numbers page chart)</option>
            <option value="bar_orders">Bar chart — order growth (12-18 months)</option>
            <option value="bar_retention">Bar chart — retention % (18 months)</option>
            <option value="ltv_cpa">LTV / CPA visual (subscription stories)</option>
          </select>
        </CSField>
        <CSField label="Chart data (JSON)" hint="Bars: {months: [{label, value}, ...]} · LTV/CPA: {cpa: 280, ltv: 2500, multiplier: 8.9}">
          <textarea className="textarea" rows={4} value={JSON.stringify(cs.chart_data || {}, null, 2)} onChange={(e) => { try { set('chart_data', JSON.parse(e.target.value)); } catch {} }} onBlur={(e) => { try { save('chart_data', JSON.parse(e.target.value)); } catch {} }} style={{ width: '100%', fontFamily: 'var(--font-mono)', fontSize: 11 }} />
        </CSField>
      </Section>

      {/* Stats cards */}
      <Section title="Numbers page stats grid" eyebrow="Step 7" hint="3 or 4 cards. Different metrics from cover KPIs.">
        <ListEditor
          items={cs.stats_cards}
          onChange={(items) => { set('stats_cards', items); save('stats_cards', items); }}
          newItem={() => ({ label: '', value: '', unit: '', sub_text: '' })}
          maxItems={4}
          renderItem={(item, update) => (
            <Row>
              <CSField label="Label" small>
                <input className="input" value={item.label || ''} onChange={(e) => update({ label: e.target.value })} style={{ width: '100%' }} />
              </CSField>
              <CSField label="Value" small>
                <input className="input" value={item.value || ''} onChange={(e) => update({ value: e.target.value })} style={{ width: '100%' }} />
              </CSField>
              <CSField label="Unit" small>
                <input className="input" value={item.unit || ''} onChange={(e) => update({ unit: e.target.value })} style={{ width: '100%' }} />
              </CSField>
              <CSField label="Sub" small>
                <input className="input" value={item.sub_text || ''} onChange={(e) => update({ sub_text: e.target.value })} style={{ width: '100%' }} />
              </CSField>
            </Row>
          )}
        />
      </Section>

      {/* About Linea */}
      <Section title="About Linea services (page 6)" eyebrow="Step 8" hint="4 service pillars. Defaults pre-filled.">
        <ListEditor
          items={cs.about_linea_services}
          onChange={(items) => { set('about_linea_services', items); save('about_linea_services', items); }}
          newItem={(idx) => ({ number: String(idx + 1).padStart(2, '0'), title: '', sub_text: '' })}
          maxItems={4}
          renderItem={(item, update, idx) => (
            <Row>
              <CSField label="#" small>
                <input className="input" value={item.number || String(idx + 1).padStart(2, '0')} onChange={(e) => update({ number: e.target.value })} style={{ width: 60 }} />
              </CSField>
              <CSField label="Title" small>
                <input className="input" value={item.title || ''} onChange={(e) => update({ title: e.target.value })} style={{ width: '100%' }} />
              </CSField>
              <CSField label="Sub text" small>
                <input className="input" value={item.sub_text || ''} onChange={(e) => update({ sub_text: e.target.value })} style={{ width: '100%' }} />
              </CSField>
            </Row>
          )}
        />
      </Section>

      {/* CTA */}
      <Section title="CTA page" eyebrow="Step 9">
        <CSField label="CTA subhead">
          <textarea className="textarea" rows={2} value={cs.cta_subhead || ''} onChange={(e) => set('cta_subhead', e.target.value)} onBlur={(e) => save('cta_subhead', e.target.value)} placeholder="e.g. Same playbook, ready for the next brand." style={{ width: '100%' }} />
        </CSField>
      </Section>

      {/* Tags + summary for matching */}
      <Section title="Tags · for outreach matching" eyebrow="Step 10" hint="Used by the LinkedIn campaign auto-picker to choose the best case study per prospect. Add as many tags as fit; empty fields are ignored.">
        <CSField label="Summary (1-2 lines, used by the matching LLM)">
          <textarea className="textarea" rows={3}
            value={cs.summary || ''}
            onChange={(e) => set('summary', e.target.value)}
            onBlur={(e) => save('summary', e.target.value)}
            placeholder="e.g. F&B brand in Dubai. Took online sales from 0 to 15K customers in year one with Meta + Shopify. Strong fit for restaurant / pastry / D2C brands launching in GCC."
            style={{ width: '100%' }}
          />
        </CSField>
        <TagChipsRow
          tags={cs.tags || {}}
          onChange={(next) => { set('tags', next); save('tags', next); }}
        />
      </Section>
    </div>
  );
}

function TagChipsRow({ tags, onChange }) {
  const TAXONOMY = [
    { key: 'industry', label: 'Industry', options: ['F&B','Beauty','Fashion','SaaS','Agency','Retail','Real estate','Travel','Education','Health','Luxury','D2C consumer','Other'] },
    { key: 'biz_model', label: 'Business model', options: ['D2C','B2B','B2C','Marketplace','Subscription','Retainer','One-off'] },
    { key: 'stage', label: 'Stage', options: ['Pre-launch','Launching','Scaling','Mature','Pivoting'] },
    { key: 'region', label: 'Region', options: ['UAE','GCC','Italy','Europe','MENA','Global'] },
    { key: 'audience', label: 'Audience', options: ['Mass-market','Premium','Luxury','Niche','Local','Tourist'] },
  ];
  const set = (k, v) => onChange({ ...tags, [k]: v });
  const toggle = (k, opt) => {
    const arr = tags[k] || [];
    set(k, arr.includes(opt) ? arr.filter(x => x !== opt) : [...arr, opt]);
  };
  const setKeywords = (str) => set('keywords', str.split(',').map(s => s.trim()).filter(Boolean));
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      {TAXONOMY.map((t) => (
        <div key={t.key}>
          <div className="label" style={{ marginBottom: 6 }}>{t.label}</div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
            {t.options.map((opt) => {
              const active = (tags[t.key] || []).includes(opt);
              return (
                <button key={opt} onClick={() => toggle(t.key, opt)} style={{
                  padding: '4px 10px', fontSize: 11, cursor: 'pointer',
                  background: active ? 'var(--accent-bg)' : 'transparent',
                  border: '1px solid', borderColor: active ? 'var(--accent)' : 'var(--divider)',
                  color: active ? 'var(--accent)' : 'var(--text-secondary)',
                  borderRadius: 4,
                }}>{opt}</button>
              );
            })}
          </div>
        </div>
      ))}
      <div>
        <div className="label" style={{ marginBottom: 6 }}>Keywords (comma-separated, free-form)</div>
        <input
          className="input"
          value={(tags.keywords || []).join(', ')}
          onChange={(e) => setKeywords(e.target.value)}
          placeholder="e.g. restaurants, pastry, food delivery, dubai mall, croissant"
          style={{ width: '100%' }}
        />
      </div>
    </div>
  );
}

/* ============================================================
   SHARED: Section, Row, Field, ListEditor, BulletList
   ============================================================ */

function Section({ title, eyebrow, hint, children }) {
  return (
    <section style={{ borderTop: '1px solid var(--divider)', paddingTop: 18 }}>
      <div className="label" style={{ marginBottom: 4 }}>{eyebrow}</div>
      <h3 style={{ fontFamily: 'var(--font-display)', fontStretch: '75%', fontWeight: 700, fontSize: 18, margin: '0 0 6px' }}>{title}</h3>
      {hint && <div style={{ fontSize: 12, color: 'var(--text-dim)', marginBottom: 12 }}>{hint}</div>}
      <div style={{ display: 'grid', gap: 12 }}>{children}</div>
    </section>
  );
}

function Row({ children }) {
  return <div style={{ display: 'grid', gridTemplateColumns: `repeat(${React.Children.count(children)}, 1fr)`, gap: 10 }}>{children}</div>;
}

function CSField({ label, hint, small, children }) {
  return (
    <div>
      <label className="label" style={{ display: 'block', marginBottom: small ? 4 : 6, fontSize: small ? 10 : 11 }}>{label}</label>
      {children}
      {hint && <div style={{ fontSize: 10, color: 'var(--text-dim)', marginTop: 4 }}>{hint}</div>}
    </div>
  );
}

function BulletList({ items, onChange, placeholder }) {
  const list = Array.isArray(items) ? items : [];
  const update = (i, v) => onChange(list.map((x, idx) => idx === i ? v : x));
  const remove = (i) => onChange(list.filter((_, idx) => idx !== i));
  const add = () => onChange([...list, '']);
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      {list.map((item, i) => (
        <div key={i} style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
          <input className="input" value={item} onChange={(e) => update(i, e.target.value)} placeholder={placeholder} style={{ flex: 1 }} />
          <button className="btn btn-sm" style={{ padding: '4px 8px', color: 'var(--red)' }} onClick={() => remove(i)}><Icon name="x" size={11} /></button>
        </div>
      ))}
      <button className="btn btn-sm" onClick={add} style={{ alignSelf: 'flex-start' }}><Icon name="plus" size={11} /> Add</button>
    </div>
  );
}

function ListEditor({ items, onChange, newItem, maxItems, renderItem }) {
  const list = Array.isArray(items) ? items : [];
  const update = (i, patch) => onChange(list.map((x, idx) => idx === i ? { ...x, ...patch } : x));
  const remove = (i) => onChange(list.filter((_, idx) => idx !== i));
  const add = () => onChange([...list, newItem(list.length)]);
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {list.map((item, i) => (
        <div key={i} className="card" style={{ padding: 12, position: 'relative' }}>
          {renderItem(item, (patch) => update(i, patch), i)}
          <button onClick={() => remove(i)} style={{ position: 'absolute', top: 6, right: 6, background: 'none', border: 'none', color: 'var(--text-dim)', cursor: 'pointer', padding: 2 }}><Icon name="x" size={11} /></button>
        </div>
      ))}
      {(!maxItems || list.length < maxItems) && (
        <button className="btn btn-sm" onClick={add} style={{ alignSelf: 'flex-start' }}><Icon name="plus" size={11} /> Add {list.length === 0 ? 'first item' : 'item'}</button>
      )}
    </div>
  );
}

/* ============================================================
   IMAGES TAB — Phase 2: library on left, slot map on right
   ============================================================ */

const SLOT_DEFS = [
  { name: 'client_logo',    label: 'Client logo',          page: 'Topbar (every page)',           aspect: '5:1',  help: 'Wide horizontal lockup' },
  { name: 'cover_hero',     label: 'Cover hero',           page: 'Page 1 — Cover',                aspect: '5:3',  help: 'Main feature image' },
  { name: 'hotel_logos',    label: 'B2B partner logos',    page: 'Page 2 — Before (callout)',     aspect: '5:2',  help: 'Up to 10', multi: true, max: 10 },
  { name: 'ecom_hero',      label: 'Ecom landing page',    page: 'Page 4 — Live and Direct',      aspect: '5:3',  help: 'Full-width screenshot' },
  { name: 'ecom_secondary', label: 'Ecom secondary',       page: 'Page 4 — Live and Direct',      aspect: '4:3',  help: 'Secondary landing screenshot' },
  { name: 'ecom_mobile',    label: 'Mobile screenshot',    page: 'Page 4 — Live and Direct',      aspect: '9:16', help: 'Phone aspect' },
  { name: 'ad_creatives',   label: 'Ad creatives',         page: 'Page 5 — Numbers (optional)',   aspect: '1:1',  help: 'Up to 4', multi: true, max: 4 },
  { name: 'founders_photo', label: 'Founders photo',       page: 'Page 7 — CTA',                  aspect: '5:3',  help: '78mm wide on the page' },
];

function _aspectToFloat(s) {
  const [a, b] = (s || '5:3').split(':').map(Number);
  return (a && b) ? `${a} / ${b}` : '5 / 3';
}

function ImagesTab({ cs, reload }) {
  const [images, setImages] = useState(cs.images || []);
  const [busyUpload, setBusyUpload] = useState(0);
  const [error, setError] = useState(null);
  const fileInputRef = useRef(null);

  // Keep local list in sync if parent reloads
  useEffect(() => { setImages(cs.images || []); }, [cs.images]);

  const slots = cs.image_slots || {};

  const refreshImages = async () => {
    const r = await fetch(`/api/case-studies/${cs.id}`, { credentials: 'include' });
    if (r.ok) {
      const d = await r.json();
      setImages(d.images || []);
    }
  };

  const uploadFiles = async (fileList) => {
    const files = Array.from(fileList || []).filter((f) => f.type.startsWith('image/'));
    if (files.length === 0) return;
    setBusyUpload((n) => n + files.length);
    setError(null);
    for (const f of files) {
      try {
        const fd = new FormData();
        fd.append('file', f);
        const r = await fetch(`/api/case-studies/${cs.id}/images`, {
          method: 'POST', credentials: 'include', body: fd,
        });
        if (!r.ok) {
          const d = await r.json().catch(() => ({}));
          throw new Error(d.detail || `${r.status} ${r.statusText}`);
        }
        const data = await r.json();
        setImages((prev) => [data, ...prev]);
      } catch (e) {
        setError(`Couldn't upload ${f.name}: ${e.message}`);
      } finally {
        setBusyUpload((n) => Math.max(0, n - 1));
      }
    }
  };

  const deleteImage = async (image_id) => {
    if (!confirm('Delete this image? Any slot using it will be cleared.')) return;
    const r = await fetch(`/api/case-studies/${cs.id}/images/${image_id}`, { method: 'DELETE', credentials: 'include' });
    if (r.ok) {
      setImages((prev) => prev.filter((x) => x.id !== image_id));
      reload();
    }
  };

  const assignSlot = async (slotName, value) => {
    // value is { image_id } or { image_ids: [...] } or null to clear
    const body = value || {};
    const r = await fetch(`/api/case-studies/${cs.id}/slots/${slotName}`, {
      method: 'PATCH', credentials: 'include',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(body),
    });
    if (r.ok) reload();
  };

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '320px 1fr', height: '100%', overflow: 'hidden' }}>
      {/* LEFT: library */}
      <div style={{ borderRight: '1px solid var(--divider)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
        <div style={{ padding: '16px 16px 8px', borderBottom: '1px solid var(--divider)' }}>
          <div className="label" style={{ marginBottom: 8 }}>Image library</div>
          <UploadZone busy={busyUpload > 0} onFiles={uploadFiles} onClickPick={() => fileInputRef.current?.click()} />
          <input ref={fileInputRef} type="file" multiple accept="image/*" style={{ display: 'none' }} onChange={(e) => { uploadFiles(e.target.files); e.target.value = ''; }} />
          {error && <div style={{ color: 'var(--red)', fontSize: 11, marginTop: 6 }}>{error}</div>}
        </div>
        <div style={{ flex: 1, overflowY: 'auto', padding: 12 }}>
          {images.length === 0 && busyUpload === 0 && (
            <div style={{ padding: 28, textAlign: 'center', color: 'var(--text-dim)', fontSize: 11 }}>No images yet. Drop files above to upload.</div>
          )}
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 8 }}>
            {images.map((img) => (
              <LibraryThumb key={img.id} img={img} onDelete={() => deleteImage(img.id)} />
            ))}
          </div>
        </div>
      </div>

      {/* RIGHT: slot map */}
      <div style={{ overflowY: 'auto', padding: 24 }}>
        <div className="label" style={{ marginBottom: 16 }}>Slot map · drag images from the library into a slot</div>
        <div style={{ display: 'grid', gap: 14 }}>
          {SLOT_DEFS.map((def) => {
            const value = slots[def.name];
            return def.multi
              ? <MultiSlotRow key={def.name} def={def} ids={Array.isArray(value) ? value : []} images={images} onChange={(ids) => assignSlot(def.name, { image_ids: ids })} />
              : <SlotRow key={def.name} def={def} imageId={typeof value === 'string' ? value : null} images={images} onChange={(id) => assignSlot(def.name, id ? { image_id: id } : null)} />;
          })}
        </div>
      </div>
    </div>
  );
}

function UploadZone({ busy, onFiles, onClickPick }) {
  const [drag, setDrag] = useState(false);
  return (
    <div
      onDragOver={(e) => { e.preventDefault(); setDrag(true); }}
      onDragLeave={() => setDrag(false)}
      onDrop={(e) => { e.preventDefault(); setDrag(false); onFiles(e.dataTransfer.files); }}
      onClick={onClickPick}
      style={{
        padding: '14px 12px',
        border: `1.5px dashed ${drag ? 'var(--accent)' : 'var(--divider)'}`,
        background: drag ? 'var(--accent-bg)' : 'transparent',
        cursor: 'pointer', textAlign: 'center', borderRadius: 8,
        transition: 'all 0.1s', userSelect: 'none',
      }}
    >
      <div style={{ fontSize: 12, color: drag ? 'var(--accent)' : 'var(--text-secondary)', fontWeight: 600 }}>
        {busy ? 'Uploading…' : 'Drop images or click to upload'}
      </div>
      <div style={{ fontSize: 10, color: 'var(--text-dim)', marginTop: 3 }}>JPG · PNG · WEBP · max 12 MB</div>
    </div>
  );
}

function LibraryThumb({ img, onDelete }) {
  const onDragStart = (e) => {
    e.dataTransfer.setData('text/x-cs-image-id', img.id);
    e.dataTransfer.effectAllowed = 'copy';
  };
  const aspectLabel = img.aspect_ratio ? `${(img.aspect_ratio).toFixed(2)}:1` : '?';
  return (
    <div
      draggable
      onDragStart={onDragStart}
      style={{
        position: 'relative',
        background: 'var(--raised)',
        border: '1px solid var(--divider)',
        borderRadius: 6,
        overflow: 'hidden',
        cursor: 'grab',
      }}
      title={img.filename}
    >
      <div style={{ aspectRatio: '1 / 1', background: '#000', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <img src={img.thumbnail_url} alt={img.filename} style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }} draggable="false" />
      </div>
      <div style={{ padding: '4px 6px', fontSize: 9, color: 'var(--text-dim)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 4 }}>
        <span className="mono" style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{aspectLabel}</span>
        <button
          onClick={(e) => { e.stopPropagation(); onDelete(); }}
          style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-dim)', padding: 2, lineHeight: 1 }}
          title="Delete from library"
        >
          <Icon name="trash" size={10} />
        </button>
      </div>
    </div>
  );
}

function SlotRow({ def, imageId, images, onChange }) {
  const assigned = images.find((i) => i.id === imageId);
  const [hover, setHover] = useState(false);
  const onDragOver = (e) => {
    if (e.dataTransfer.types.includes('text/x-cs-image-id')) {
      e.preventDefault();
      e.dataTransfer.dropEffect = 'copy';
      setHover(true);
    }
  };
  const onDrop = (e) => {
    e.preventDefault();
    setHover(false);
    const id = e.dataTransfer.getData('text/x-cs-image-id');
    if (id) onChange(id);
  };
  return (
    <div className="card" style={{ padding: 14, display: 'grid', gridTemplateColumns: '220px 1fr', gap: 14, alignItems: 'flex-start' }}>
      <div
        onDragOver={onDragOver}
        onDragLeave={() => setHover(false)}
        onDrop={onDrop}
        style={{
          aspectRatio: _aspectToFloat(def.aspect),
          border: `1.5px dashed ${hover ? 'var(--accent)' : (assigned ? 'transparent' : 'var(--divider)')}`,
          background: assigned ? '#000' : (hover ? 'var(--accent-bg)' : 'var(--raised)'),
          borderRadius: 6,
          overflow: 'hidden',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          position: 'relative',
          transition: 'all 0.1s',
        }}
      >
        {assigned ? (
          <>
            <img src={assigned.thumbnail_url} alt={assigned.filename} style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }} />
            <button
              onClick={() => onChange(null)}
              style={{ position: 'absolute', top: 4, right: 4, padding: '2px 6px', fontSize: 10, background: 'rgba(0,0,0,0.7)', color: '#fff', border: 'none', borderRadius: 4, cursor: 'pointer' }}
              title="Clear assignment"
            >
              ×
            </button>
          </>
        ) : (
          <div style={{ textAlign: 'center', color: hover ? 'var(--accent)' : 'var(--text-dim)', padding: 8 }}>
            <div style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em' }}>{def.label}</div>
            <div style={{ fontSize: 10, color: 'var(--text-dim)', marginTop: 4, fontFamily: 'var(--font-mono)' }}>{def.aspect}</div>
          </div>
        )}
      </div>
      <div>
        <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>{def.label}</div>
        <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 2 }}>{def.page}</div>
        <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 6 }}>Aspect ratio <span className="mono">{def.aspect}</span> · {def.help}</div>
        {assigned && (
          <div style={{ fontSize: 11, color: 'var(--text-secondary)', marginTop: 8 }}>
            <Icon name="check" size={10} style={{ color: 'var(--accent)', marginRight: 4 }} />
            <span className="mono">{assigned.filename}</span>
          </div>
        )}
      </div>
    </div>
  );
}

function MultiSlotRow({ def, ids, images, onChange }) {
  const cells = ids.map((id) => images.find((i) => i.id === id) || null);
  while (cells.length < def.max) cells.push(null);

  const setIdAt = (idx, newId) => {
    const next = [...ids];
    if (newId === null) {
      next.splice(idx, 1);
    } else {
      next[idx] = newId;
    }
    onChange(next.filter(Boolean));
  };

  return (
    <div className="card" style={{ padding: 14 }}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 10 }}>
        <div>
          <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>{def.label}</div>
          <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 2 }}>{def.page}</div>
        </div>
        <div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
          <span className="mono">{ids.length}/{def.max}</span> · aspect <span className="mono">{def.aspect}</span>
        </div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.min(def.max, 5)}, 1fr)`, gap: 8 }}>
        {cells.map((img, idx) => (
          <MultiSlotCell
            key={idx}
            img={img}
            aspect={def.aspect}
            onAssign={(newId) => setIdAt(idx, newId)}
            onClear={() => setIdAt(idx, null)}
          />
        ))}
      </div>
      <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 8 }}>{def.help}</div>
    </div>
  );
}

function MultiSlotCell({ img, aspect, onAssign, onClear }) {
  const [hover, setHover] = useState(false);
  const onDragOver = (e) => {
    if (e.dataTransfer.types.includes('text/x-cs-image-id')) {
      e.preventDefault(); e.dataTransfer.dropEffect = 'copy';
      setHover(true);
    }
  };
  const onDrop = (e) => {
    e.preventDefault(); setHover(false);
    const id = e.dataTransfer.getData('text/x-cs-image-id');
    if (id) onAssign(id);
  };
  return (
    <div
      onDragOver={onDragOver}
      onDragLeave={() => setHover(false)}
      onDrop={onDrop}
      style={{
        aspectRatio: _aspectToFloat(aspect),
        border: `1.5px dashed ${hover ? 'var(--accent)' : (img ? 'transparent' : 'var(--divider)')}`,
        background: img ? '#000' : (hover ? 'var(--accent-bg)' : 'var(--raised)'),
        borderRadius: 4,
        overflow: 'hidden',
        position: 'relative',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}
    >
      {img ? (
        <>
          <img src={img.thumbnail_url} alt={img.filename} style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }} />
          <button
            onClick={onClear}
            style={{ position: 'absolute', top: 2, right: 2, padding: '0 5px', fontSize: 10, background: 'rgba(0,0,0,0.7)', color: '#fff', border: 'none', borderRadius: 3, cursor: 'pointer' }}
          >
            ×
          </button>
        </>
      ) : (
        <span style={{ fontSize: 9, color: hover ? 'var(--accent)' : 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{aspect}</span>
      )}
    </div>
  );
}

/* ============================================================
   PREVIEW + RENDER TABS — Phase 3
   ============================================================ */

function PreviewTab({ cs }) {
  // Cache-bust on every brief change so the iframe re-renders
  const [reloadKey, setReloadKey] = useState(0);
  useEffect(() => { setReloadKey((k) => k + 1); }, [cs.updated_at]);
  const url = `/api/case-studies/${cs.id}/preview.html?_=${reloadKey}`;
  return (
    <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
      <div style={{ padding: '10px 16px', borderBottom: '1px solid var(--divider)', display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: 'var(--text-dim)' }}>
        <Icon name="eye" size={12} />
        <span>Live preview · re-renders when the brief or image slots change.</span>
        <div style={{ flex: 1 }} />
        <a href={url} target="_blank" rel="noopener" className="btn btn-sm">
          <Icon name="external" size={11} /> Open in new tab
        </a>
        <button className="btn btn-sm" onClick={() => setReloadKey((k) => k + 1)}>
          <Icon name="refresh" size={11} /> Refresh
        </button>
      </div>
      <iframe
        key={reloadKey}
        src={url}
        style={{ flex: 1, width: '100%', border: 'none', background: '#1a1a1a' }}
        title="case study preview"
      />
    </div>
  );
}

function RenderTab({ cs, reload }) {
  const [busy, setBusy] = useState(false);
  const [last, setLast] = useState(cs.last_rendered_at || null);
  const [err, setErr] = useState(null);

  const previewUrl = `/api/case-studies/${cs.id}/preview.html`;

  const snapshot = async () => {
    setBusy(true);
    setErr(null);
    try {
      const r = await fetch(`/api/case-studies/${cs.id}/render`, { method: 'POST', credentials: 'include' });
      if (!r.ok) {
        const d = await r.json().catch(() => ({}));
        throw new Error(d.detail || `${r.status} ${r.statusText}`);
      }
      const data = await r.json();
      setLast(data.last_rendered_at);
      reload();
    } catch (e) { setErr(e.message); } finally { setBusy(false); }
  };

  return (
    <div style={{ height: '100%', overflow: 'auto', padding: 28 }}>
      <div style={{ maxWidth: 720 }}>
        <h2 style={{ fontFamily: 'var(--font-display)', fontStretch: '75%', fontWeight: 800, fontSize: 22, margin: '0 0 8px' }}>Render</h2>
        <p style={{ color: 'var(--text-secondary)', fontSize: 13, lineHeight: 1.6, marginTop: 0 }}>
          The HTML version is fully functional. To produce a PDF, open the preview in a new tab and use your browser's <strong>Print → Save as PDF</strong> (Cmd+P) at A4 page size with no headers/footers. Server-side Playwright render is queued for the next phase.
        </p>

        <div className="card" style={{ padding: 20, marginTop: 18, display: 'flex', flexDirection: 'column', gap: 10 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap' }}>
            <a href={previewUrl} target="_blank" rel="noopener" className="btn btn-primary">
              <Icon name="external" size={12} /> Open preview in new tab
            </a>
            <button className="btn" onClick={snapshot} disabled={busy}>
              <Icon name="download" size={12} /> {busy ? 'Saving snapshot…' : 'Save HTML snapshot'}
            </button>
            {last && <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>Last snapshot: {new Date(last).toLocaleString()}</span>}
          </div>
          {err && <div style={{ color: 'var(--red)', fontSize: 12 }}>{err}</div>}
          <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 4 }}>
            Snapshot saves the rendered HTML at <code className="mono">/srv/linea-crm/case-study-pdfs/{cs.id}/{cs.client_slug}-&lt;timestamp&gt;.html</code> on the server.
          </div>
        </div>

        <h3 style={{ fontFamily: 'var(--font-display)', fontStretch: '75%', fontWeight: 700, fontSize: 16, marginTop: 28 }}>Tip · Manual PDF (matches the existing skill)</h3>
        <div style={{ fontSize: 12, color: 'var(--text-dim)', lineHeight: 1.6 }}>
          On your Mac, run:
        </div>
        <pre style={{ background: 'var(--raised)', border: '1px solid var(--divider)', padding: 12, fontSize: 11, fontFamily: 'var(--font-mono)', overflowX: 'auto', marginTop: 8, lineHeight: 1.5 }}>{`# 1. Save the preview HTML locally:
curl -L -b "$LINEA_CRM_COOKIE" \\
  "https://crm.lineagroup.ae/api/case-studies/${cs.id}/preview.html" \\
  -o ~/Desktop/${cs.client_slug}.html

# 2. Render to PDF with Chrome headless (per .claude/skills/case-study-builder/SKILL.md):
CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
"$CHROME" --headless --disable-gpu --no-pdf-header-footer --print-to-pdf-no-header \\
  --print-to-pdf="$HOME/Desktop/${cs.client_name.replace(/[^a-zA-Z0-9]+/g, '')}-CaseStudy-LineaDark.pdf" \\
  "file://$HOME/Desktop/${cs.client_slug}.html"

open "$HOME/Desktop/${cs.client_name.replace(/[^a-zA-Z0-9]+/g, '')}-CaseStudy-LineaDark.pdf"`}</pre>
      </div>
    </div>
  );
}

Object.assign(window, { CaseStudies, CaseStudyEditor });
