// ===== Extract: ถอด prompt จากภาพตัวอย่าง =====
// 1) อ่าน metadata ฝังในไฟล์ (PNG tEXt/iTXt/zTXt = SD WebUI/ComfyUI/NovelAI, JPEG XMP) — offline, ฟรี, ทันที
// 2) fallback: วิเคราะห์ภาพด้วย Gemini Vision (ผู้ใช้ใส่ API key เอง เก็บใน localStorage เครื่องตัวเอง)

const EXTRACT_TEXT = {
  th: {
    title: 'ถอด Prompt จากภาพ',
    sub: 'ลากรูปมาวาง หรือกดเลือกไฟล์ — ถ้าเป็นภาพ AI (PNG จาก SD/ComfyUI ฯลฯ) จะอ่าน prompt ที่ฝังในไฟล์ได้ทันที',
    drop: 'วางรูปตรงนี้ / กดเลือกไฟล์',
    metaFound: 'เจอ prompt ฝังในไฟล์',
    metaNone: 'ไม่เจอ metadata ในไฟล์นี้ — ใช้ AI วิเคราะห์ภาพแทนได้ด้านล่าง',
    aiTitle: 'วิเคราะห์ภาพด้วย AI',
    modeNexus: 'Nexus (Claude)',
    modeGemini: 'Gemini (key เอง)',
    nexusHint: 'ส่งภาพให้ Nexus (Claude) ถอดให้ — ต้องเปิด bridge ก่อน: รัน start-bridge.bat (หรือ python3 extract-bridge.py)',
    nexusOff: '● bridge ยังไม่เปิด — รัน start-bridge.bat ก่อน',
    nexusOn: '● Nexus bridge พร้อม',
    nexusBtn: 'ถอด prompt ด้วย Nexus',
    aiHint: 'ใส่ Gemini API key ของคุณ (ฟรีที่ aistudio.google.com) — key เก็บในเครื่องคุณเท่านั้น ไม่ถูกส่งไปที่อื่นนอกจาก Google',
    aiBtn: 'ถอด prompt ด้วย Gemini',
    aiWorking: 'กำลังวิเคราะห์…',
    useBtn: 'ส่งเข้า Builder',
    copyBtn: 'คัดลอก',
    copied: 'คัดลอกแล้ว!',
    otherMeta: 'metadata อื่นๆ ในไฟล์',
    errAi: 'วิเคราะห์ไม่สำเร็จ',
    keyPh: 'AIza… (Gemini API key)',
  },
  en: {
    title: 'Extract Prompt from Image',
    sub: 'Drop an image or pick a file — AI-generated PNGs (SD/ComfyUI etc.) carry their prompt in the file, read instantly',
    drop: 'Drop image here / click to choose',
    metaFound: 'Prompt found in file metadata',
    metaNone: 'No embedded metadata — use AI analysis below instead',
    aiTitle: 'Analyze with AI',
    modeNexus: 'Nexus (Claude)',
    modeGemini: 'Gemini (own key)',
    nexusHint: 'Send the image to Nexus (Claude) to extract — start the bridge first: run start-bridge.bat (or python3 extract-bridge.py)',
    nexusOff: '● bridge offline — run start-bridge.bat first',
    nexusOn: '● Nexus bridge ready',
    nexusBtn: 'Extract with Nexus',
    aiHint: 'Paste your Gemini API key (free at aistudio.google.com) — stored only on this device, sent only to Google',
    aiBtn: 'Extract with Gemini',
    aiWorking: 'Analyzing…',
    useBtn: 'Send to Builder',
    copyBtn: 'Copy',
    copied: 'Copied!',
    otherMeta: 'Other metadata in file',
    errAi: 'Analysis failed',
    keyPh: 'AIza… (Gemini API key)',
  },
};

const BRIDGE_URL = 'http://localhost:5055';

async function _apInflate(u8) {
  try {
    const ds = new DecompressionStream('deflate');
    const ab = await new Response(new Blob([u8]).stream().pipeThrough(ds)).arrayBuffer();
    return new Uint8Array(ab);
  } catch { return new Uint8Array(); }
}

// PNG: walk chunks, collect tEXt / iTXt / zTXt
async function _apParsePNG(buf) {
  const dv = new DataView(buf), td = new TextDecoder();
  const sig = new Uint8Array(buf, 0, 8);
  if (sig[0] !== 0x89 || sig[1] !== 0x50) return [];
  const out = [];
  let off = 8;
  while (off + 12 <= buf.byteLength) {
    const len = dv.getUint32(off);
    const type = td.decode(new Uint8Array(buf, off + 4, 4));
    if (type === 'IEND') break;
    if (len > 0 && off + 8 + len <= buf.byteLength) {
      const data = new Uint8Array(buf.slice(off + 8, off + 8 + len));
      const z = data.indexOf(0);
      if (type === 'tEXt' && z > 0) {
        out.push({ key: td.decode(data.slice(0, z)), value: td.decode(data.slice(z + 1)) });
      } else if (type === 'zTXt' && z > 0) {
        out.push({ key: td.decode(data.slice(0, z)), value: td.decode(await _apInflate(data.slice(z + 2))) });
      } else if (type === 'iTXt' && z > 0) {
        const key = td.decode(data.slice(0, z));
        const comp = data[z + 1];
        let p = z + 3;
        while (p < data.length && data[p] !== 0) p++; p++;
        while (p < data.length && data[p] !== 0) p++; p++;
        let val = data.slice(p);
        if (comp === 1) val = await _apInflate(val);
        out.push({ key, value: td.decode(val) });
      }
    }
    off += 12 + len;
  }
  return out;
}

// JPEG/WebP/อื่นๆ: scan หา XMP packet + SD "parameters" ใน byte stream
function _apScanGeneric(buf) {
  const s = new TextDecoder('utf-8', { fatal: false }).decode(buf);
  const out = [];
  const xmp = s.match(/<x:xmpmeta[\s\S]*?<\/x:xmpmeta>/);
  if (xmp) {
    const dec = (t) => t.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, '&').trim();
    const desc = xmp[0].match(/<dc:description>[\s\S]*?<rdf:li[^>]*>([\s\S]*?)<\/rdf:li>/);
    if (desc && desc[1].trim()) out.push({ key: 'XMP description', value: dec(desc[1]) });
    const uc = xmp[0].match(/<exif:UserComment>[\s\S]*?<rdf:li[^>]*>([\s\S]*?)<\/rdf:li>/);
    if (uc && uc[1].trim()) out.push({ key: 'XMP UserComment', value: dec(uc[1]) });
  }
  const sd = s.match(/parameters[:\s]{1,4}([\s\S]{10,3000}?)(?:|$)/);
  if (sd && !out.some(o => o.value.includes(sd[1].slice(0, 40)))) out.push({ key: 'parameters', value: sd[1].trim() });
  return out;
}

// เลือก prompt หลักจาก entries (เรียงตามความน่าจะเป็น)
function _apPickPrompt(entries) {
  const get = (k) => entries.find(e => e.key.toLowerCase() === k);
  const params = get('parameters') || entries.find(e => e.key === 'XMP UserComment');
  if (params) return params.value.split(/\nNegative prompt:/)[0].trim();
  const comfy = get('prompt');
  if (comfy) {
    try {
      const j = JSON.parse(comfy.value);
      const texts = [];
      const walk = (o) => { if (o && typeof o === 'object') { for (const k in o) { if (k === 'text' && typeof o[k] === 'string' && o[k].length > 5) texts.push(o[k]); else walk(o[k]); } } };
      walk(j);
      if (texts.length) return texts.sort((a, b) => b.length - a.length)[0];
    } catch { return comfy.value; }
  }
  const nai = get('comment');
  if (nai) { try { const j = JSON.parse(nai.value); if (j.prompt) return j.prompt; } catch {} }
  const desc = get('description') || get('xmp description') || get('dream');
  if (desc) return desc.value;
  return null;
}

async function _apGemini(apiKey, b64, mime) {
  const body = {
    contents: [{ parts: [
      { inline_data: { mime_type: mime, data: b64 } },
      { text: 'Describe this image as one detailed English image-generation prompt: subject, setting, style, lighting, camera/composition, mood, color palette. Output ONLY the prompt text, no preamble.' },
    ] }],
  };
  const r = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${encodeURIComponent(apiKey)}`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
  });
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
  const j = await r.json();
  const txt = j?.candidates?.[0]?.content?.parts?.map(p => p.text).join('') || '';
  if (!txt) throw new Error('empty response');
  return txt.trim();
}

// Nexus bridge — POST ภาพ base64 ไป local bridge (extract-bridge.py → claude -p)
async function _apNexus(b64, mime) {
  const r = await fetch(`${BRIDGE_URL}/extract`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ image: b64, mime }),
  });
  const j = await r.json().catch(() => ({}));
  if (!r.ok || j.error) throw new Error(j.error || `HTTP ${r.status}`);
  if (!j.prompt) throw new Error('empty response');
  return j.prompt.trim();
}

function ExtractScreen({ lang, onUse }) {
  const L = EXTRACT_TEXT[lang] || EXTRACT_TEXT.en;
  const [imgUrl, setImgUrl] = useState(null);
  const [fileInfo, setFileInfo] = useState(null);   // {name, mime, b64}
  const [entries, setEntries] = useState([]);
  const [mainPrompt, setMainPrompt] = useState(null);
  const [scanned, setScanned] = useState(false);
  const [mode, setMode] = useState(() => { try { return localStorage.getItem('autoprompt:extractMode') || 'nexus'; } catch { return 'nexus'; } });
  const [bridgeUp, setBridgeUp] = useState(false);
  const [aiKey, setAiKey] = useState(() => { try { return localStorage.getItem('autoprompt:geminiKey') || ''; } catch { return ''; } });
  const [aiBusy, setAiBusy] = useState(false);
  const [aiErr, setAiErr] = useState(null);
  const [copied, setCopied] = useState(false);
  const fileRef = useRef(null);

  // เช็คว่า Nexus bridge เปิดอยู่ไหม (health check)
  useEffect(() => {
    let alive = true;
    const ping = () => fetch(`${BRIDGE_URL}/`, { signal: AbortSignal.timeout(2000) })
      .then(r => r.ok).catch(() => false).then(ok => { if (alive) setBridgeUp(ok); });
    ping();
    const iv = setInterval(ping, 5000);
    return () => { alive = false; clearInterval(iv); };
  }, []);
  const pickMode = (m) => { setMode(m); try { localStorage.setItem('autoprompt:extractMode', m); } catch {} };

  const handleFile = async (file) => {
    if (!file || !file.type.startsWith('image/')) return;
    setEntries([]); setMainPrompt(null); setAiErr(null); setScanned(false); setCopied(false);
    setImgUrl(URL.createObjectURL(file));
    const buf = await file.arrayBuffer();
    let found = [];
    if (file.type === 'image/png') found = await _apParsePNG(buf);
    if (!found.length) found = _apScanGeneric(buf);
    setEntries(found);
    setMainPrompt(_apPickPrompt(found));
    setScanned(true);
    // เก็บ base64 ไว้สำหรับ AI fallback (ย่อไม่ทำ — Gemini รับได้ถึง ~20MB)
    const b64 = btoa(new Uint8Array(buf).reduce((a, b) => a + String.fromCharCode(b), ''));
    setFileInfo({ name: file.name, mime: file.type, b64 });
  };

  const runAi = async () => {
    if (!fileInfo) return;
    if (mode === 'gemini' && !aiKey.trim()) return;
    setAiBusy(true); setAiErr(null);
    try {
      let txt;
      if (mode === 'nexus') {
        txt = await _apNexus(fileInfo.b64, fileInfo.mime);
      } else {
        try { localStorage.setItem('autoprompt:geminiKey', aiKey.trim()); } catch {}
        txt = await _apGemini(aiKey.trim(), fileInfo.b64, fileInfo.mime);
      }
      setMainPrompt(txt);
    } catch (e) { setAiErr(`${L.errAi}: ${e.message}`); }
    setAiBusy(false);
  };

  const copy = async () => {
    if (!mainPrompt) return;
    try { await navigator.clipboard.writeText(mainPrompt); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch {}
  };

  return (
    <section className="screen extract-screen" style={{ padding: '12px 14px 90px' }}>
      <h2 style={{ margin: '4px 0 2px' }}>{L.title}</h2>
      <p style={{ opacity: .7, fontSize: 13, margin: '0 0 12px' }}>{L.sub}</p>

      <div
        className="extract-drop"
        style={{ border: '2px dashed var(--ink-30, #b9b2a2)', borderRadius: 14, padding: imgUrl ? 8 : 36, textAlign: 'center', cursor: 'pointer', background: 'rgba(0,0,0,.02)' }}
        onClick={() => fileRef.current && fileRef.current.click()}
        onDragOver={(e) => e.preventDefault()}
        onDrop={(e) => { e.preventDefault(); handleFile(e.dataTransfer.files[0]); }}
      >
        {imgUrl
          ? <img src={imgUrl} alt="" style={{ maxWidth: '100%', maxHeight: 260, borderRadius: 10 }}/>
          : <span style={{ opacity: .6 }}>{L.drop}</span>}
        <input ref={fileRef} type="file" accept="image/*" hidden onChange={(e) => handleFile(e.target.files[0])}/>
      </div>

      {scanned && (
        <div style={{ marginTop: 14 }}>
          {mainPrompt ? (
            <div className="extract-result" style={{ background: 'var(--card, #fff)', border: '1px solid rgba(0,0,0,.08)', borderRadius: 12, padding: 12 }}>
              <div style={{ fontWeight: 700, fontSize: 13, marginBottom: 6 }}>✓ {L.metaFound}</div>
              <textarea readOnly value={mainPrompt} rows={Math.min(8, Math.max(3, Math.ceil(mainPrompt.length / 70)))}
                style={{ width: '100%', fontFamily: 'JetBrains Mono, monospace', fontSize: 12.5, border: 'none', background: 'transparent', resize: 'vertical' }}/>
              <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
                <button className="btn" onClick={copy}>{copied ? L.copied : L.copyBtn}</button>
                {onUse && <button className="btn primary" onClick={() => onUse(mainPrompt)}>{L.useBtn}</button>}
              </div>
            </div>
          ) : (
            <p style={{ fontSize: 13, opacity: .75 }}>{L.metaNone}</p>
          )}

          {entries.length > 1 && (
            <details style={{ marginTop: 10, fontSize: 12.5 }}>
              <summary style={{ cursor: 'pointer', opacity: .7 }}>{L.otherMeta} ({entries.length})</summary>
              {entries.map((e, i) => (
                <div key={i} style={{ margin: '8px 0', padding: 8, background: 'rgba(0,0,0,.03)', borderRadius: 8 }}>
                  <b>{e.key}</b>
                  <div style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', maxHeight: 140, overflow: 'auto', fontFamily: 'JetBrains Mono, monospace', fontSize: 11.5 }}>{e.value.slice(0, 2000)}</div>
                </div>
              ))}
            </details>
          )}

          <div style={{ marginTop: 16, paddingTop: 12, borderTop: '1px solid rgba(0,0,0,.08)' }}>
            <div style={{ fontWeight: 700, fontSize: 13, marginBottom: 8 }}>{L.aiTitle}</div>

            {/* mode selector */}
            <div style={{ display: 'flex', gap: 6, marginBottom: 10 }}>
              {[['nexus', L.modeNexus], ['gemini', L.modeGemini]].map(([m, lbl]) => (
                <button key={m} onClick={() => pickMode(m)}
                  style={{ flex: 1, padding: '7px 10px', borderRadius: 8, fontSize: 13, cursor: 'pointer',
                    border: mode === m ? '2px solid #e0489a' : '1px solid rgba(0,0,0,.15)',
                    background: mode === m ? 'rgba(224,72,154,.08)' : 'transparent',
                    fontWeight: mode === m ? 700 : 400 }}>
                  {lbl}
                </button>
              ))}
            </div>

            {mode === 'nexus' ? (
              <div>
                <p style={{ fontSize: 12, opacity: .65, margin: '0 0 8px' }}>{L.nexusHint}</p>
                <div style={{ fontSize: 12, marginBottom: 8, color: bridgeUp ? '#1f8a5b' : '#c0392b' }}>
                  {bridgeUp ? L.nexusOn : L.nexusOff}
                </div>
                <button className="btn primary" disabled={aiBusy || !bridgeUp} onClick={runAi}>
                  {aiBusy ? L.aiWorking : L.nexusBtn}
                </button>
              </div>
            ) : (
              <div>
                <p style={{ fontSize: 12, opacity: .65, margin: '0 0 8px' }}>{L.aiHint}</p>
                <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                  <input type="password" value={aiKey} onChange={(e) => setAiKey(e.target.value)} placeholder={L.keyPh}
                    style={{ flex: '1 1 200px', padding: '8px 10px', borderRadius: 8, border: '1px solid rgba(0,0,0,.15)', fontSize: 13 }}/>
                  <button className="btn primary" disabled={aiBusy || !aiKey.trim()} onClick={runAi}>
                    {aiBusy ? L.aiWorking : L.aiBtn}
                  </button>
                </div>
              </div>
            )}
            {aiErr && <p style={{ color: '#c0392b', fontSize: 12.5, marginTop: 6 }}>{aiErr}</p>}
          </div>
        </div>
      )}
    </section>
  );
}
window.ExtractScreen = ExtractScreen;
