// PWA Autoprompt — Stack + Blocks (V1 × V4 mix)
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ===== shorthand =====
const cn = (...xs) => xs.filter(Boolean).join(' ');
const tx = (lang, k) => (STRINGS[lang] || STRINGS.en)[k] || k;

// ===== icon (re-uses Ico from shared.jsx) =====
const I = window.Ico;
const Thumb = window.VizThumb;

// ===== Real AI image preview via Pollinations.ai (no API key) =====
// Lazy <img>: only fetches when scrolled into view.
function AIPromptThumb({ prompt, w = '100%', h = 50, fallback = 'abstract' }) {
  // 2026-06-10: pollinations.ai กลายเป็น paid (HTTP 402) — ปิด remote fetch ถาวร ใช้ CSS thumb แทน
  return <Thumb kind={fallback} w={w} h={h}/>;
  /* superseded — remote preview path (เผื่อ pollinations กลับมาฟรี)
  const [err, setErr] = useState(false);
  const cleaned = String(prompt || '').replace(/[^\w\s,.-]/g, ' ').slice(0, 200);
  const url = `https://image.pollinations.ai/prompt/${encodeURIComponent(cleaned)}?width=256&height=256&nologo=true&seed=42`;
  if (err || !cleaned) return <Thumb kind={fallback} w={w} h={h}/>;
  return (
    <div className="ai-thumb" style={{ width: w, height: h }}>
      <Thumb kind={fallback} w="100%" h="100%"/>
      <img className="ai-thumb-img" src={url} alt="" loading="lazy" decoding="async"
        onError={() => setErr(true)}/>
    </div>
  );
  */
}

// ===== block helpers =====
function findItem(typeId, catId, itemId) {
  const cat = BLOCK_DATA[typeId].categories.find(c => c.id === catId);
  if (!cat) return null;
  const it = cat.items.find(i => i.id === itemId);
  if (!it) return null;
  return { ...it, catId, catColor: cat.color, catLabel: cat.label };
}

// localStorage helpers
const ls = {
  get(k, d) { try { const v = localStorage.getItem(k); return v == null ? d : JSON.parse(v); } catch { return d; } },
  set(k, v) { try { localStorage.setItem(k, JSON.stringify(v)); } catch {} },
};

// ===== root =====
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "lang": "th",
  "dark": false,
  "showThumbs": true,
  "aiThumbs": false,
  "density": "comfy",
  "accent": "#ff6b5b"
}/*EDITMODE-END*/;

function App() {
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const t = (k) => tx(tweaks.lang, k);
  const auth = useAuth();
  const limits = auth.limits;

  // Modals
  const [signInOpen, setSignInOpen] = useState(false);
  const [signInReason, setSignInReason] = useState(null);
  const [pricingOpen, setPricingOpen] = useState(false);
  const requireSignIn = (reason) => { setSignInReason(reason); setSignInOpen(true); };
  const requireUpgrade = () => { setPricingOpen(true); };

  // Server-fetch simulation
  const [serverState, setServerState] = useState('loading'); // loading | ready
  useEffect(() => {
    const start = Date.now();
    // simulate library fetch from server
    const min = 700, max = 1100;
    const delay = min + Math.random() * (max - min);
    const id = setTimeout(() => setServerState('ready'), Math.max(0, delay - (Date.now() - start)));
    return () => clearTimeout(id);
  }, []);

  useEffect(() => {
    document.documentElement.classList.toggle('dark', !!tweaks.dark);
    document.documentElement.dataset.density = tweaks.density;
    document.documentElement.style.setProperty('--accent', tweaks.accent);
  }, [tweaks.dark, tweaks.density, tweaks.accent]);

  // ===== state =====
  const [tab, setTab] = useState('build');
  const [type, setType] = useState('image');
  const [subject, setSubject] = useState('');
  const [context, setContext] = useState('');
  const [blocks, setBlocks] = useState([
  ].filter(Boolean));
  const [copyState, setCopyState] = useState('idle'); // idle | copied
  const [enhancing, setEnhancing] = useState(false);
  const [aiEnhanced, setAiEnhanced] = useState(null); // string | null
  const [showInstall, setShowInstall] = useState(() => ls.get('autoprompt:installDismissed', false) ? false : true);
  const [saved, setSaved] = useState(() => ls.get('autoprompt:saved', []));
  const [history, setHistory] = useState(() => ls.get('autoprompt:history', SEED_HISTORY));

  // when type changes, keep blocks if they exist for that type, else clear
  useEffect(() => {
    setBlocks(prev => prev.filter(b => BLOCK_DATA[type].categories.some(c => c.id === b.catId && c.items.some(i => i.id === b.id))));
    setAiEnhanced(null);
  }, [type]);

  const prompt = useMemo(() => composePrompt(type, subject, context, blocks), [type, subject, context, blocks]);
  const isBlockOn = (catId, itemId) => blocks.some(b => b.catId === catId && b.id === itemId);

  const toggleBlock = (catId, itemId) => {
    setAiEnhanced(null);
    if (isBlockOn(catId, itemId)) {
      setBlocks(blocks.filter(b => !(b.catId === catId && b.id === itemId)));
    } else {
      const item = findItem(type, catId, itemId);
      if (item) setBlocks([...blocks, item]);
    }
  };
  const moveBlock = (idx, dir) => {
    const next = [...blocks];
    const j = idx + dir;
    if (j < 0 || j >= next.length) return;
    [next[idx], next[j]] = [next[j], next[idx]];
    setBlocks(next);
  };
  const removeBlock = (idx) => setBlocks(blocks.filter((_, i) => i !== idx));
  const clearAll = () => { setBlocks([]); setSubject(''); setContext(''); setAiEnhanced(null); };

  // Random: fill empty categories with a random pick. Skips bigCat (hair/body) unless empty stack.
  const onRandomize = (mode = 'empty') => {
    setAiEnhanced(null);
    const cats = BLOCK_DATA[type].categories;
    let next = mode === 'all' ? [] : [...blocks];
    const usedCatIds = new Set(next.map(b => b.catId));
    for (const cat of cats) {
      if (usedCatIds.has(cat.id)) continue;
      // skip big categories (hair, body) by default — user opts in by tapping per-cat dice
      if (cat.bigCat) continue;
      if (!cat.items.length) continue;
      const pick = cat.items[Math.floor(Math.random() * cat.items.length)];
      const item = findItem(type, cat.id, pick.id);
      if (item) next.push(item);
    }
    setBlocks(next);
  };
  const onRandomizeCategory = (catId) => {
    setAiEnhanced(null);
    const cat = BLOCK_DATA[type].categories.find(c => c.id === catId);
    if (!cat || !cat.items.length) return;
    const pick = cat.items[Math.floor(Math.random() * cat.items.length)];
    const item = findItem(type, catId, pick.id);
    if (!item) return;
    // replace any existing block from this category, or add
    const others = blocks.filter(b => b.catId !== catId);
    setBlocks([...others, item]);
  };

  const onCopy = async () => {
    const text = aiEnhanced || prompt;
    const final = text;
    try { await navigator.clipboard.writeText(final); }
    catch {
      const ta = document.createElement('textarea'); ta.value = final;
      document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove();
    }
    setCopyState('copied');
    setTimeout(() => setCopyState('idle'), 1400);
  };

  const onSave = () => {
    const SAVE_LIMIT = Infinity;
    const entry = {
      id: Date.now(),
      type, subject, context, blocks: blocks.map(b => ({ catId: b.catId, id: b.id })),
      prompt: aiEnhanced || prompt,
      at: Date.now(),
      thumb: blocks[0]?.thumb || BLOCK_DATA[type].icon,
    };
    const next = [entry, ...saved].slice(0, 50);
    setSaved(next); ls.set('autoprompt:saved', next);
    setCopyState('saved'); setTimeout(() => setCopyState('idle'), 1400);
    // also push to history
    const h = [entry, ...history.filter(x => x.prompt !== entry.prompt)].slice(0, 50);
    setHistory(h); ls.set('autoprompt:history', h);
  };

  const onShare = async () => {
    if (navigator.share) {
      try { await navigator.share({ title: 'Autoprompt', text: aiEnhanced || prompt }); } catch {}
    } else {
      onCopy();
    }
  };

  const onEnhance = async () => {
    if (enhancing) return;
    setEnhancing(true); setAiEnhanced(null);
    auth.bump('enhance');
    try {
      if (typeof window.claude?.complete === 'function') {
        // Anthropic-hosted artifact: use built-in
        const out = await window.claude.complete(
          `You are an expert prompt engineer. Take this draft prompt and rewrite it for clarity, ` +
          `power, and specificity. Keep the user's intent. Return ONLY the improved prompt, no preamble. ` +
          `Target tool: ${type === 'image' ? 'image-generation AI (e.g. Midjourney, SDXL)' :
                         type === 'video' ? 'video AI (e.g. Sora, Runway)' :
                         type === 'code'  ? 'code AI (e.g. Claude Code, Cursor)' :
                         'a general LLM (e.g. ChatGPT, Claude)'}.\n\nDraft:\n${prompt}`
        );
        setAiEnhanced((out || '').trim());
      } else {
        // Self-hosted deploy: prep an "enhance request" and copy it for the user to paste into their AI
        const target = type === 'image' ? 'image AI (Midjourney/SDXL/Flux)' :
                       type === 'video' ? 'video AI (Sora/Runway)' :
                       type === 'code'  ? 'code AI (Claude/Cursor/Copilot)' :
                                          'a general LLM (Claude/ChatGPT/Gemini)';
        const wrapper = `You are an expert prompt engineer. Rewrite the following ${target} prompt for clarity, power, and specificity. Keep the user's intent. Return ONLY the improved prompt, no preamble.\n\nDraft:\n${prompt}`;
        await navigator.clipboard.writeText(wrapper);
        setAiEnhanced(
          '💡 No built-in AI on this host.\n\n' +
          'A ready-to-paste "enhance" request was copied to your clipboard — paste it into Claude, ChatGPT, or Gemini, ' +
          'then bring the improved prompt back here.\n\n' +
          '🔗 claude.ai · chatgpt.com · gemini.google.com\n\n' +
          '── Your current draft ──\n' + prompt
        );
      }
    } catch (e) {
      setAiEnhanced('(AI enhance failed — using your draft as-is)\n\n' + prompt);
    }
    setEnhancing(false);
  };

  const loadSaved = (entry) => {
    setType(entry.type);
    setSubject(entry.subject);
    setContext(entry.context || '');
    setBlocks(entry.blocks.map(b => findItem(entry.type, b.catId, b.id)).filter(Boolean));
    setAiEnhanced(null);
    setTab('build');
  };

  const removeSaved = (id) => {
    const next = saved.filter(s => s.id !== id);
    setSaved(next); ls.set('autoprompt:saved', next);
  };

  // ===== render =====
  return (
    <div className="app">
      <TopBar
        t={t} lang={tweaks.lang} dark={tweaks.dark}
        onLang={() => setTweak('lang', tweaks.lang === 'th' ? 'en' : 'th')}
        onDark={() => setTweak('dark', !tweaks.dark)}
        auth={auth}
        onSignIn={() => requireSignIn(null)}
        onUpgrade={requireUpgrade}
        onAccount={() => setTab('account')}
      />
      {serverState === 'loading' && <ServerLoading/>}
      {showInstall && <InstallBanner t={t} onDismiss={() => { setShowInstall(false); ls.set('autoprompt:installDismissed', true); }}/>}

      <main className="main">
        {tab === 'build' && (
          <BuildScreen
            t={t} tweaks={tweaks}
            type={type} setType={setType}
            subject={subject} setSubject={setSubject}
            context={context} setContext={setContext}
            blocks={blocks} prompt={prompt} aiEnhanced={aiEnhanced}
            toggleBlock={toggleBlock} moveBlock={moveBlock} removeBlock={removeBlock}
            clearAll={clearAll} isBlockOn={isBlockOn}
            onCopy={onCopy} onSave={onSave} onShare={onShare}
            onEnhance={onEnhance} enhancing={enhancing}
            onRandomize={onRandomize} onRandomizeCategory={onRandomizeCategory}
            copyState={copyState}
            auth={auth} requireSignIn={requireSignIn} requireUpgrade={requireUpgrade}
          />
        )}
        {tab === 'extract' && <ExtractScreen lang={tweaks.lang} onUse={(text) => { setSubject(text); setType('image'); setTab('build'); }}/>}
        {tab === 'saved' && <ListScreen t={t} title={t('favorites')} items={saved} onLoad={loadSaved} onRemove={removeSaved} empty={t('noHistory')}/>}
        {tab === 'history' && <ListScreen t={t} title={t('history')} items={history} onLoad={loadSaved} onRemove={(id) => { const n = history.filter(s => s.id !== id); setHistory(n); ls.set('autoprompt:history', n); }} empty={t('noHistory')}/>}
        {tab === 'account' && <AccountScreen t={t} tweaks={tweaks} setTweak={setTweak}
          auth={auth}
          onSignIn={() => requireSignIn(null)}
          onUpgrade={requireUpgrade}/>}
      </main>

      <TabBar t={t} active={tab} setActive={setTab}/>

      <SignInModal open={signInOpen} onClose={() => setSignInOpen(false)}
        onSignIn={auth.signIn} reason={signInReason}/>
      <PricingModal open={pricingOpen} onClose={() => setPricingOpen(false)}
        user={auth.user} onUpgrade={auth.upgrade}
        onSignInFirst={() => { setPricingOpen(false); requireSignIn('Sign in first to upgrade.'); }}/>

      <TweaksPanel title="Tweaks">
        <TweakSection label="Language"/>
        <TweakRadio label="UI lang" value={tweaks.lang} options={[{value:'th',label:'ไทย'},{value:'en',label:'English'}]} onChange={(v) => setTweak('lang', v)}/>
        <TweakSection label="Appearance"/>
        <TweakToggle label="Dark mode" value={tweaks.dark} onChange={(v) => setTweak('dark', v)}/>
        <TweakRadio label="Density" value={tweaks.density} options={['compact','comfy']} onChange={(v) => setTweak('density', v)}/>
        <TweakToggle label={t('showThumb')} value={tweaks.showThumbs} onChange={(v) => setTweak('showThumbs', v)}/>
        <TweakToggle label="AI image previews (external, slow)" value={tweaks.aiThumbs} onChange={(v) => setTweak('aiThumbs', v)}/>
        <TweakColor label="Accent" value={tweaks.accent} options={['#ff6b5b','#4a7eff','#1f8a5b','#a259e6','#ff9d00']} onChange={(v) => setTweak('accent', v)}/>
      </TweaksPanel>
    </div>
  );
}

// ===== TopBar =====
function TopBar({ t, lang, dark, onLang, onDark, auth, onSignIn, onUpgrade, onAccount }) {
  return (
    <header className="topbar">
      <div className="brand">
        <div className="brand-mark">✦</div>
        <div className="brand-name">{t('appName')}</div>
        <span className="brand-tag">prompts for anything</span>
      </div>
      <div className="topbar-actions">
        {auth.user ? (
          <button className={cn('plan-badge', auth.planId === 'pro' && 'pro')} onClick={onAccount} title="Account">
            <span className="avatar">{auth.user.name?.[0]?.toUpperCase() || '✦'}</span>
            <span className="plan-name">{auth.limits.label}</span>
            {auth.planId === 'free' && <span className="plan-up" onClick={(e) => { e.stopPropagation(); onUpgrade(); }}>Upgrade</span>}
          </button>
        ) : (
          <button className="btn btn-sm primary" onClick={onSignIn}>Sign in</button>
        )}
        <button className="iconbtn" onClick={onLang} title="Switch language">
          <I name="lang" size={16}/><span className="iconbtn-lbl">{lang.toUpperCase()}</span>
        </button>
        <button className="iconbtn" onClick={onDark} title="Toggle dark">
          <I name={dark ? 'sun' : 'moon'} size={16}/>
        </button>
      </div>
    </header>
  );
}

// ===== Install banner (PWA) =====
function ServerLoading() {
  return (
    <div className="server-loading">
      <div className="server-loading-spinner"></div>
      <span>Loading library from server…</span>
    </div>
  );
}

function UsageBar({ label, used, max }) {
  if (max === Infinity) return null;
  const pct = Math.min(100, (used / max) * 100);
  const remaining = Math.max(0, max - used);
  return (
    <div className="usage-bar">
      <div className="usage-bar-head">
        <span className="usage-label">{label}</span>
        <span className={cn('usage-count', remaining === 0 && 'maxed')}>{used} / {max}</span>
      </div>
      <div className="usage-bar-track">
        <div className="usage-bar-fill" style={{ width: pct + '%', background: pct >= 100 ? 'var(--accent)' : 'var(--green)' }}/>
      </div>
    </div>
  );
}

function InstallBanner({ t, onDismiss }) {
  const [canInstall, setCanInstall] = useState(!!window.__deferredInstall);
  useEffect(() => {
    const f = () => setCanInstall(!!window.__deferredInstall);
    window.addEventListener('install-ready', f);
    return () => window.removeEventListener('install-ready', f);
  }, []);
  const install = async () => {
    if (window.__deferredInstall) {
      window.__deferredInstall.prompt();
      const { outcome } = await window.__deferredInstall.userChoice;
      window.__deferredInstall = null;
      if (outcome === 'accepted') onDismiss();
    } else {
      alert(
        'ถ้ารันใน browser ปกติ: ใช้ menu “Add to Home Screen” / “Install app”\n' +
        'In a regular browser, use “Add to Home Screen” or “Install app” from the menu.'
      );
    }
  };
  return (
    <div className="install-banner">
      <div className="install-icon">📲</div>
      <div className="install-text">
        <strong>Install Autoprompt</strong>
        <span>เพิ่มลงหน้าจอหลัก ใช้ออฟไลน์ได้ทุกที่ · Works offline as a PWA</span>
      </div>
      <button className="btn btn-sm" onClick={install}><I name="download" size={12}/>Install</button>
      <button className="iconbtn" onClick={onDismiss}><I name="close" size={14}/></button>
    </div>
  );
}

// ===== TabBar =====
function TabBar({ t, active, setActive }) {
  const items = [
    { id: 'build',    ico: 'sparkle', label: t('build') },
    { id: 'extract',  ico: 'image',   label: t('extract') },
    { id: 'history',  ico: 'history', label: t('history') },
    { id: 'saved',    ico: 'star',    label: t('favorites') },
    { id: 'account',  ico: 'cog',     label: t('account') || 'Account' },
  ];
  return (
    <nav className="tabbar">
      {items.map(it => (
        <button key={it.id} className={cn('tab', active === it.id && 'active')} onClick={() => setActive(it.id)}>
          <I name={it.ico} size={18}/><span>{it.label}</span>
        </button>
      ))}
    </nav>
  );
}

// ===== Build screen =====
function BuildScreen(props) {
  const { t, tweaks, type, setType, subject, setSubject, context, setContext, blocks, prompt, aiEnhanced,
          toggleBlock, moveBlock, removeBlock, clearAll, isBlockOn,
          onCopy, onSave, onShare, onEnhance, enhancing, copyState,
          onRandomize, onRandomizeCategory, auth, requireSignIn, requireUpgrade } = props;

  // Pick the right thumbnail component based on tweak
  const ThumbComp = tweaks.aiThumbs
    ? ({ kind, w, h, prompt }) => <AIPromptThumb prompt={prompt} fallback={kind} w={w} h={h}/>
    : ({ kind, w, h }) => <Thumb kind={kind} w={w} h={h}/>;

  return (
    <div className="build">
      {/* LEFT/MAIN column */}
      <div className="build-main">
        {/* Sticky preview */}
        <PreviewCard t={t} prompt={prompt} blocks={blocks} subject={subject} context={context}
          aiEnhanced={aiEnhanced} enhancing={enhancing}
          onCopy={onCopy} onSave={onSave} onShare={onShare} onEnhance={onEnhance} copyState={copyState}
          onRandomize={onRandomize} ThumbComp={ThumbComp}/>

        {/* Subject input */}
        <Section label={`1 · ${t('subject')}`}>
          <textarea className="subj-input"
            placeholder={BLOCK_DATA[type].subjectPh[tweaks.lang] || BLOCK_DATA[type].subjectPh.en}
            value={subject} onChange={(e) => setSubject(e.target.value)} rows={2}/>
        </Section>

        {/* Context / scene description */}
        <Section label={<><span>2 · {t('context')}</span> <span className="muted">{t('optional')}</span></>}
          right={<span className="muted">{context.length} ตัว</span>}>
          <textarea className="ctx-input"
            placeholder={BLOCK_DATA[type].contextPh?.[tweaks.lang] || BLOCK_DATA[type].contextPh?.en || ''}
            value={context} onChange={(e) => setContext(e.target.value)} rows={3}/>
          <div className="ctx-tip">✨ {t('contextTip')}</div>
        </Section>

        {/* Output type */}
        <Section label={`3 · ${t('output')}`}>
          <div className="type-tabs">
            {Object.entries(BLOCK_DATA).map(([id, def]) => (
              <button key={id} className={cn('type-tab', type === id && 'active')} onClick={() => setType(id)}>
                <I name={def.icon} size={18}/>
                <span>{def.label[tweaks.lang] || def.label.en}</span>
              </button>
            ))}
          </div>
        </Section>

        {/* Stack */}
        <Section label={<><span>4 · {t('stack')}</span> <span className="muted">{blocks.length} blocks</span></>}
          right={blocks.length > 0 && <button className="btn-link" onClick={clearAll}><I name="close" size={11}/>{t('clear')}</button>}>
          {blocks.length === 0 ? (
            <div className="empty-stack">{t('empty')}</div>
          ) : (
            <ul className="stack">
              {blocks.map((b, i) => (
                <li key={`${b.catId}-${b.id}-${i}`} className="stack-block"
                    style={{ '--block-color': b.catColor }}>
                  <div className="stack-block-head">
                    {tweaks.showThumbs && <ThumbComp kind={b.thumb} w={32} h={32} prompt={b.phrase}/>}
                    <div className="stack-block-meta">
                      <span className="stack-block-cat">{b.catLabel[tweaks.lang] || b.catLabel.en}</span>
                      <span className="stack-block-label">{b.label}</span>
                    </div>
                    <div className="stack-block-actions">
                      <button className="ico-mini" onClick={() => onRandomizeCategory(b.catId)} title={t('randomCat')}>🎲</button>
                      <button className="ico-mini" disabled={i===0} onClick={() => moveBlock(i, -1)} title={t('moveUp')}>↑</button>
                      <button className="ico-mini" disabled={i===blocks.length-1} onClick={() => moveBlock(i, 1)} title={t('moveDown')}>↓</button>
                      <button className="ico-mini danger" onClick={() => removeBlock(i)} title={t('delete')}>×</button>
                    </div>
                  </div>
                  {b.desc && <div className="stack-block-desc">{b.desc}</div>}
                  <div className="stack-block-phrase">"{b.phrase}"</div>
                </li>
              ))}
            </ul>
          )}
        </Section>

        {/* Library */}
        <Section label={`5 · ${t('library')}`} right={<span className="muted">{t('addMore')}</span>}>
          <Library typeId={type} lang={tweaks.lang} showThumbs={tweaks.showThumbs}
            isBlockOn={isBlockOn} toggleBlock={toggleBlock}
            ThumbComp={ThumbComp} onRandomizeCategory={onRandomizeCategory} t={t}
            auth={auth} requireSignIn={requireSignIn} requireUpgrade={requireUpgrade}/>
        </Section>

        <div style={{ height: 80 }}/>{/* bottom safe area */}
      </div>
    </div>
  );
}

// ===== Section wrapper =====
function Section({ label, right, children }) {
  return (
    <section className="section">
      <div className="section-head">
        <h3 className="section-label">{label}</h3>
        {right && <div className="section-right">{right}</div>}
      </div>
      {children}
    </section>
  );
}

// ===== Preview card (sticky) =====
function PreviewCard({ t, prompt, blocks, subject, context, aiEnhanced, enhancing, onCopy, onSave, onShare, onEnhance, copyState, onRandomize, ThumbComp }) {
  // Build colored spans for the assembled preview
  const segments = useMemo(() => {
    const segs = [];
    const subj = (subject || '').trim();
    const ctx = (context || '').trim();
    if (subj) segs.push({ text: subj, color: null, kind: 'subject' });
    if (ctx) segs.push({ text: ctx, color: '#1a1a1a', kind: 'context', isContext: true });
    blocks.forEach(b => segs.push({ text: b.phrase, color: b.catColor, kind: b.catId, label: b.label }));
    return segs;
  }, [prompt, blocks, subject, context]);

  return (
    <div className={cn('preview-card', enhancing && 'enhancing')}>
      <div className="preview-head">
        <h2 className="preview-title">
          <span className="preview-arrow">↓</span> {t('livePreview')}
          {aiEnhanced && <span className="ai-badge">✨ AI enhanced</span>}
        </h2>
        <div className="preview-thumb-row">
          {blocks.slice(0, 4).map((b, i) => (
            ThumbComp
              ? <ThumbComp key={i} kind={b.thumb} w={28} h={28} prompt={b.phrase}/>
              : <Thumb key={i} kind={b.thumb} w={28} h={28}/>
          ))}
          {blocks.length > 4 && <div className="thumb-more">+{blocks.length-4}</div>}
        </div>
      </div>

      <div className="preview-body">
        {aiEnhanced ? (
          <div className="preview-text mono">{aiEnhanced}</div>
        ) : segments.length === 0 ? (
          <div className="preview-empty">{prompt}</div>
        ) : (
          <div className="preview-text mono">
            {segments.map((s, i) => (
              <React.Fragment key={i}>
                {i > 0 && <span className="preview-sep">, </span>}
                <span className="preview-seg"
                  style={s.isContext ? {
                    background: 'color-mix(in srgb, var(--ink) 8%, transparent)',
                    borderBottom: '2px dashed var(--ink)',
                    fontStyle: 'italic',
                  } : s.color ? {
                    background: `color-mix(in srgb, ${s.color} 22%, transparent)`,
                    borderBottom: `2px solid ${s.color}`,
                  } : { fontWeight: 600 }}>{s.text}</span>
              </React.Fragment>
            ))}
          </div>
        )}
      </div>

      <div className="preview-actions">
        <button className={cn('btn primary', copyState === 'copied' && 'success')} onClick={onCopy}>
          <I name="copy" size={14}/>{copyState === 'copied' ? t('copied') : t('copy')}
        </button>
        <button className={cn('btn', copyState === 'saved' && 'success')} onClick={onSave}>
          <I name="bookmark" size={14}/>{copyState === 'saved' ? t('saved') : t('save')}
        </button>
        <button className="btn" onClick={onShare}>
          <I name="share" size={14}/>
        </button>
        <button className="btn dice" onClick={() => onRandomize && onRandomize('empty')} title={t('randomEmpty')}>
          🎲
        </button>
        <button className={cn('btn accent', enhancing && 'busy')} onClick={onEnhance} disabled={enhancing}>
          <I name="sparkle" size={14}/>{enhancing ? t('enhancing') : t('enhance')}
        </button>
      </div>
    </div>
  );
}

// ===== Library: tabs of categories, grid of block cards =====
function Library({ typeId, lang, showThumbs, isBlockOn, toggleBlock, ThumbComp, onRandomizeCategory, t, auth, requireSignIn, requireUpgrade }) {
  const cats = BLOCK_DATA[typeId].categories;
  const limit = auth?.limits?.maxCategoriesPerType ?? Infinity;
  const bigUnlocked = auth?.limits?.bigCategoriesUnlocked ?? false;

  // Decide which categories are locked
  const lockedIds = new Set();
  cats.forEach((c, idx) => {
    if (c.bigCat && !bigUnlocked) lockedIds.add(c.id);
    if (idx >= limit) lockedIds.add(c.id);
  });
  const [activeCat, setActiveCat] = useState(cats[0].id);
  const [query, setQuery] = useState('');
  const [showMore, setShowMore] = useState(false);
  useEffect(() => { setActiveCat(cats[0].id); setQuery(''); setShowMore(false); }, [typeId]);
  useEffect(() => { setQuery(''); setShowMore(false); }, [activeCat]);

  const cat = cats.find(c => c.id === activeCat) || cats[0];
  const q = query.trim().toLowerCase();
  const filtered = useMemo(() => {
    if (!q) return cat.items;
    return cat.items.filter(it =>
      it.label.toLowerCase().includes(q) || it.phrase.toLowerCase().includes(q)
    );
  }, [cat, q]);
  const catLocked = lockedIds.has(cat.id);

  const onClickCard = (it) => {
    toggleBlock(cat.id, it.id);
  };

  const PAGE = 24;
  const visible = showMore || q ? filtered : filtered.slice(0, PAGE);
  const isBig = cat.items.length > PAGE;

  return (
    <div className="library">
      <div className="lib-tabs">
        {cats.map(c => {
          const locked = lockedIds.has(c.id);
          return (
            <button key={c.id}
              className={cn('lib-tab', activeCat === c.id && 'active', locked && 'locked')}
              style={{ '--cat-color': c.color }}
              onClick={() => setActiveCat(c.id)}>
              {locked ? <Lock size={11}/> : <span className="lib-tab-dot" style={{ background: c.color }}/>}
              {c.label[lang] || c.label.en}
              {c.items.length > PAGE && <span className="lib-tab-count">{c.items.length}</span>}
            </button>
          );
        })}
      </div>
      {/* Random-from-category row */}
      <div className="lib-cat-actions">
        <button className="btn-link" onClick={() => onRandomizeCategory && onRandomizeCategory(cat.id)} title={t ? t('randomCat') : 'Random'}>
          🎲 {t ? t('random') : 'Random'} — {cat.label[lang] || cat.label.en}
        </button>
        <span className="muted" style={{ fontSize: 11 }}>{filtered.length}{q ? ` / ${cat.items.length}` : ''}</span>
      </div>
      {isBig && (
        <div className="lib-search">
          <I name="search" size={14}/>
          <input
            type="text"
            placeholder={lang === 'th' ? `ค้นหาใน ${cat.items.length} รายการ…` : `Search ${cat.items.length} items…`}
            value={query}
            onChange={(e) => setQuery(e.target.value)}/>
          {query && <button className="ico-mini" onClick={() => setQuery('')}>×</button>}
        </div>
      )}
      {catLocked && (
        <div className="lib-locked-banner">
          <Lock size={16}/>
          <div className="lib-locked-text">
            <strong>{cat.bigCat ? 'Pro library' : 'Pro category'}</strong>
            <span>{cat.bigCat ? ` — ${cat.items.length} ตัวเลือกชั้นพรีเมียม` : ' — upgrade to unlock'}</span>
          </div>
          <button className="btn accent btn-sm" onClick={() => requireUpgrade?.()}><Ico name="bolt" size={12}/>Upgrade</button>
        </div>
      )}
      <div className="lib-grid">
        {visible.map(it => {
          const on = isBlockOn(cat.id, it.id);
          return (
            <button key={it.id}
              className={cn('lib-card', on && 'on', catLocked && 'locked', it.desc && 'has-desc')}
              style={{ '--cat-color': cat.color }}
              onClick={() => onClickCard(it)}>
              {showThumbs && (
                ThumbComp
                  ? <ThumbComp kind={it.thumb || 'abstract'} w={'100%'} h={120} prompt={it.phrase}/>
                  : <Thumb kind={it.thumb || 'abstract'} w={'100%'} h={120}/>
              )}
              <div className="lib-card-label">
                <span>{it.label}</span>
                {on ? <I name="check" size={12}/> : <I name="plus" size={12}/>}
              </div>
              {it.desc && <div className="lib-card-desc">{it.desc}</div>}
              <div className="lib-card-phrase">{it.phrase}</div>
            </button>
          );
        })}
        {filtered.length === 0 && (
          <div className="lib-empty">{lang === 'th' ? 'ไม่พบรายการ' : 'No matches'}</div>
        )}
      </div>
      {isBig && !q && filtered.length > PAGE && !showMore && (
        <button className="btn-link lib-more" onClick={() => setShowMore(true)}>
          {lang === 'th' ? `แสดงทั้งหมด ${filtered.length} รายการ →` : `Show all ${filtered.length} →`}
        </button>
      )}
      {isBig && showMore && (
        <button className="btn-link lib-more" onClick={() => setShowMore(false)}>
          {lang === 'th' ? '← ย่อกลับ' : '← Show less'}
        </button>
      )}
    </div>
  );
}

// ===== List screen (history / saved) =====
function ListScreen({ t, title, items, onLoad, onRemove, empty }) {
  return (
    <div className="list-screen">
      <h2 className="screen-title">{title}</h2>
      {items.length === 0 ? (
        <div className="list-empty">{empty}</div>
      ) : (
        <ul className="list">
          {items.map(it => (
            <li key={it.id} className="list-item">
              <Thumb kind={it.thumb || 'blank'} w={56} h={56}/>
              <div className="list-item-body">
                <div className="list-item-prompt">{it.prompt}</div>
                <div className="list-item-meta">
                  <span>{BLOCK_DATA[it.type]?.label?.en || it.type}</span>
                  <span>·</span>
                  <span>{it.blocks?.length || 0} blocks</span>
                  <span>·</span>
                  <span>{relTime(it.at)}</span>
                </div>
              </div>
              <div className="list-item-actions">
                <button className="iconbtn" onClick={() => onLoad(it)} title="Load"><I name="edit" size={14}/></button>
                <button className="iconbtn" onClick={() => { navigator.clipboard?.writeText(it.prompt); }} title="Copy"><I name="copy" size={14}/></button>
                <button className="iconbtn" onClick={() => onRemove(it.id)} title="Remove"><I name="close" size={14}/></button>
              </div>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

// ===== Account screen (auth + plan + settings) =====
function AccountScreen({ t, tweaks, setTweak, auth, onSignIn, onUpgrade }) {
  const [cacheStats, setCacheStats] = useState({ images: 0, app: 0, swReady: false });
  useEffect(() => {
    const updateStats = () => {
      const sw = navigator.serviceWorker?.controller;
      if (!sw) { setCacheStats(s => ({ ...s, swReady: false })); return; }
      const ch = new MessageChannel();
      ch.port1.onmessage = (e) => {
        if (e.data?.type === 'stats') setCacheStats({ images: e.data.images, app: e.data.app, swReady: true });
      };
      sw.postMessage({ type: 'stats' }, [ch.port2]);
      // Fallback: also broadcast via clients API
      navigator.serviceWorker.ready.then(reg => {
        if (reg.active) reg.active.postMessage({ type: 'stats' });
      });
      navigator.serviceWorker.addEventListener('message', (ev) => {
        if (ev.data?.type === 'stats') setCacheStats({ images: ev.data.images, app: ev.data.app, swReady: true });
      }, { once: true });
    };
    updateStats();
    const id = setInterval(updateStats, 3000);
    return () => clearInterval(id);
  }, []);

  const swCmd = (type) => {
    navigator.serviceWorker?.controller?.postMessage({ type });
    setTimeout(() => { /* stats refresh on interval */ }, 200);
  };

  const exportJson = () => {
    const data = {
      saved: ls.get('autoprompt:saved', []),
      history: ls.get('autoprompt:history', []),
      exportedAt: new Date().toISOString(),
    };
    const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a'); a.href = url; a.download = `autoprompt-${Date.now()}.json`;
    a.click(); URL.revokeObjectURL(url);
  };
  return (
    <div className="settings">
      <h2 className="screen-title">{t('account')}</h2>

      {/* Account & plan card */}
      {auth.user ? (
        <div className={cn('account-card', auth.planId === 'pro' && 'pro')}>
          <div className="account-head">
            <div className="account-avatar">{auth.user.name?.[0]?.toUpperCase() || '✦'}</div>
            <div className="account-info">
              <div className="account-name">{auth.user.name}</div>
              <div className="account-email">{auth.user.email}</div>
            </div>
            <div className={cn('plan-pill', auth.planId === 'pro' && 'pro')}>
              {auth.planId === 'pro' && '★ '}{auth.limits.label}
            </div>
          </div>
          {auth.planId === 'free' && (
            <div className="account-usage">
              <UsageBar label="AI Enhance today" used={auth.usedToday('enhance')} max={auth.limits.aiEnhancePerDay}/>
              <UsageBar label="AI thumbnails today" used={auth.usedToday('thumbs')} max={auth.limits.aiThumbsPerDay}/>
            </div>
          )}
          <div className="setting-row" style={{ marginTop: 12 }}>
            {auth.planId === 'free' && <button className="btn accent" onClick={onUpgrade}><Ico name="bolt" size={14}/>Upgrade to Pro</button>}
            {auth.planId === 'pro' && <button className="btn" onClick={() => { if (confirm('Cancel Pro subscription? (demo)')) auth.downgrade(); }}>Manage subscription</button>}
            <button className="btn" onClick={auth.signOut}><Ico name="close" size={13}/>Sign out</button>
          </div>
        </div>
      ) : (
        <div className="account-card guest">
          <div className="account-head">
            <div className="account-avatar guest-avatar">?</div>
            <div className="account-info">
              <div className="account-name">Guest</div>
              <div className="account-email">Sign in to copy, save, and use AI features</div>
            </div>
          </div>
          <div className="setting-row" style={{ marginTop: 12 }}>
            <button className="btn primary" onClick={onSignIn}>Sign in — free forever</button>
            <button className="btn accent" onClick={onUpgrade}><Ico name="bolt" size={14}/>See Pro plans</button>
          </div>
        </div>
      )}
      <div className="setting-grp">
        <div className="setting-label">Language / ภาษา</div>
        <div className="setting-row">
          <button className={cn('btn', tweaks.lang === 'th' && 'primary')} onClick={() => setTweak('lang', 'th')}>ไทย</button>
          <button className={cn('btn', tweaks.lang === 'en' && 'primary')} onClick={() => setTweak('lang', 'en')}>English</button>
        </div>
      </div>
      <div className="setting-grp">
        <div className="setting-label">Theme</div>
        <div className="setting-row">
          <button className={cn('btn', !tweaks.dark && 'primary')} onClick={() => setTweak('dark', false)}><I name="sun" size={14}/>Light</button>
          <button className={cn('btn', tweaks.dark && 'primary')} onClick={() => setTweak('dark', true)}><I name="moon" size={14}/>Dark</button>
        </div>
      </div>
      <div className="setting-grp">
        <div className="setting-label">Density</div>
        <div className="setting-row">
          <button className={cn('btn', tweaks.density === 'compact' && 'primary')} onClick={() => setTweak('density', 'compact')}>Compact</button>
          <button className={cn('btn', tweaks.density === 'comfy' && 'primary')} onClick={() => setTweak('density', 'comfy')}>Comfy</button>
        </div>
      </div>
      <div className="setting-grp">
        <div className="setting-label">Accent color</div>
        <div className="setting-row">
          {['#ff6b5b','#4a7eff','#1f8a5b','#a259e6','#ff9d00'].map(c => (
            <button key={c} className="swatch" style={{ background: c, outline: tweaks.accent === c ? '2px solid var(--ink)' : 'none', outlineOffset: 2 }}
              onClick={() => setTweak('accent', c)}/>
          ))}
        </div>
      </div>
      <div className="setting-grp">
        <div className="setting-label">Preview thumbnails</div>
        <div className="setting-row">
          <button className={cn('btn', tweaks.showThumbs && 'primary')} onClick={() => setTweak('showThumbs', true)}>On</button>
          <button className={cn('btn', !tweaks.showThumbs && 'primary')} onClick={() => setTweak('showThumbs', false)}>Off</button>
          <span style={{ flex: 1 }}/>
          <button className={cn('btn', tweaks.aiThumbs && 'primary')} onClick={() => setTweak('aiThumbs', !tweaks.aiThumbs)}>{tweaks.aiThumbs ? '✨ AI on' : '✨ AI off'}</button>
        </div>
        <p className="setting-about" style={{ marginTop: 8 }}>
          AI thumbnails are generated by <strong>pollinations.ai</strong> (free, no key). First load is slow; cached after that.
        </p>
      </div>
      <div className="setting-grp">
        <div className="setting-label">⚡ Offline cache (PWA)</div>
        <div className="cache-stats">
          <div>
            <div className="cache-num">{cacheStats.swReady ? '✓' : '…'}</div>
            <div className="cache-lbl">Service Worker</div>
          </div>
          <div>
            <div className="cache-num">{cacheStats.images}</div>
            <div className="cache-lbl">AI images cached</div>
          </div>
          <div>
            <div className="cache-num">{cacheStats.app}</div>
            <div className="cache-lbl">App files cached</div>
          </div>
        </div>
        <div className="setting-row" style={{ marginTop: 10 }}>
          <button className="btn" onClick={() => swCmd('clear-images')}><I name="close" size={13}/>Clear AI images</button>
          <button className="btn" onClick={() => swCmd('clear-app')}><I name="close" size={13}/>Clear app cache</button>
          <button className="btn danger" onClick={async () => {
            if (!confirm('Unregister Service Worker and clear all caches? (Reloads page)')) return;
            const regs = await navigator.serviceWorker?.getRegistrations() || [];
            await Promise.all(regs.map(r => r.unregister()));
            const keys = await caches.keys();
            await Promise.all(keys.map(k => caches.delete(k)));
            location.reload();
          }}><I name="close" size={13}/>Reset Service Worker</button>
        </div>
        <p className="setting-about" style={{ marginTop: 6 }}>
          ไฟล์ที่เคยโหลดแล้ว (รวมถึงภาพ AI preview) จะอยู่ตลอด แม้ปิดอินเตอร์เน็ต. ถือเป็น PWA ได้จริงๆ.
        </p>
      </div>
      <div className="setting-grp">
        <div className="setting-label">Data</div>
        <div className="setting-row">
          <button className={cn('btn', !auth.limits.canExport && 'locked')} onClick={() => { if (!auth.limits.canExport) { onUpgrade(); return; } exportJson(); }}>
            {!auth.limits.canExport && <Lock size={12}/>}
            <I name="download" size={14}/>Export saved + history (.json)
          </button>
          <button className="btn danger" onClick={() => { if (confirm('Delete all saved + history?')) { localStorage.removeItem('autoprompt:saved'); localStorage.removeItem('autoprompt:history'); location.reload(); } }}><I name="close" size={14}/>Clear all data</button>
        </div>
      </div>
      <div className="setting-grp">
        <div className="setting-label">About</div>
        <p className="setting-about">
          <strong>Autoprompt</strong> — multi-purpose prompt builder. Compose by stacking blocks per category, see a live preview, copy or hand off to your favorite AI.
          <br/><br/>
          Works offline as a PWA. All user data lives in your browser. AI image previews fetched from pollinations.ai and cached locally.
        </p>
      </div>
    </div>
  );
}

// ===== utilities =====
function relTime(ts) {
  const s = Math.floor((Date.now() - ts) / 1000);
  if (s < 60) return 'just now';
  if (s < 3600) return Math.floor(s/60) + 'm ago';
  if (s < 86400) return Math.floor(s/3600) + 'h ago';
  return Math.floor(s/86400) + 'd ago';
}

const SEED_HISTORY = [
  { id: 1, type: 'image',  subject: 'Italian kitchen at golden hour', blocks: [{catId:'style',id:'cinematic'},{catId:'light',id:'golden'}], prompt: 'Italian kitchen at golden hour, cinematic shot, film grain, golden hour, warm sunlight', at: Date.now() - 3600*1000*2, thumb: 'cinematic' },
  { id: 2, type: 'code',   subject: 'React form with zod validation', blocks: [{catId:'lang',id:'ts'},{catId:'fw',id:'react'},{catId:'inc',id:'tests'}], prompt: 'React form with zod validation, in TypeScript with strict types, using React 18 + hooks, include unit tests', at: Date.now() - 86400*1000, thumb: 'code' },
  { id: 3, type: 'email',  subject: 'Cold outreach to a podcast host', blocks: [{catId:'purpose',id:'cold'},{catId:'tone',id:'warm'},{catId:'len',id:'short'}], prompt: 'Cold outreach to a podcast host, cold outreach email, warm personable tone, keep it under 80 words', at: Date.now() - 86400*1000*3, thumb: 'mail' },
];

// mount
ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
