/* ============================================================
   Translate — a Google-Translate-style tool.
   • Text mode: type English, see Russian in the universal stack
     (Cyrillic / phonetic / English), with audio + language swap.
   • Live mode: a mic that runs a real-time bilingual conversation.
   ============================================================ */
(function () {
  const { useState, useRef, useEffect, useCallback } = React;
  const A = window.APP;
  const speak = window.speakRU;

  /* 2026-06-05 7:47PM — Translate rewritten per doc 03 §A1.
     TextMode now calls POST /api/translate and renders the canonical
     StackedGloss result (natural translation + literal + per-token
     gloss + tap-to-hear + a save heart, inherited from StackedGloss).
     Tiering: single words resolve live (debounced); multi-word phrases
     require explicit submit (Enter / the Translate button) since they
     may hit the paid LLM tier. Loading skeleton + kind error/retry
     states. Quick-phrase chips now actually call the API (cache-warm).
     LiveMode untouched. Status: ⚠️ PENDING USER TESTING */

  const CYRILLIC = /[А-Яа-яЁё]/;
  function detectDir(text) { return CYRILLIC.test(text) ? 'ru2en' : 'en2ru'; }
  function isMultiWord(text) { return /\s/.test(text.trim()) && text.trim().split(/\s+/).length > 1; }

  // Map the API response (doc 03 §A2.1) into a StackedGloss item.
  // The shapes already align; we just attach `direction` for dedupe
  // and guarantee a `words` array so the renderer never throws.
  function toGlossItem(res, dir) {
    const words = Array.isArray(res.words) && res.words.length
      ? res.words
      : [{ ru: res.ru, phon: (res.words && res.words[0] && res.words[0].phon) || '', en: res.translation }];
    return {
      type: res.type || (words.length > 1 ? 'sentence' : 'word'),
      ru: res.ru,
      translation: res.translation,
      literal: res.literal || null,
      note: res.note || null,
      words,
      direction: dir === 'ru2en' ? 'ru' : 'en',
      lemma_id: res.lemma_id != null ? res.lemma_id : null,
    };
  }

  async function callTranslate(text, dir, signal) {
    const r = await fetch('/api/translate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ text: text.trim(), direction: dir }),
      signal,
    });
    let body = null;
    try { body = await r.json(); } catch (e) { /* non-JSON */ }
    if (!r.ok || !body) {
      const err = new Error((body && body.error) || ('translate ' + r.status));
      err.payload = body && body.partial ? body.partial : null;
      err.code = body && body.error;
      throw err;
    }
    if (body.error) {
      // graceful degradation: server returned an error envelope with maybe a partial
      const err = new Error(body.error);
      err.payload = body.partial || null;
      err.code = body.error;
      throw err;
    }
    return body;
  }

  function Seg({ options, value, onChange }) {
    return (
      <div style={{ display:'flex', gap:4, background:'var(--chip)', borderRadius:12, padding:4 }}>
        {options.map(o => (
          <button key={o.id} onClick={() => onChange(o.id)}
            style={{ appearance:'none', border:'none', cursor:'pointer', flex:1, borderRadius:9,
              padding:'9px 8px', fontFamily:'"Hanken Grotesk",system-ui', fontWeight:800, fontSize:14,
              background: value === o.id ? 'var(--surface)' : 'transparent',
              color: value === o.id ? 'var(--ink)' : 'var(--muted)',
              boxShadow: value === o.id ? '0 1px 4px -1px rgba(0,0,0,0.15)' : 'none', transition:'all .15s' }}>
            {o.label}
          </button>
        ))}
      </div>
    );
  }

  function Skeleton() {
    return (
      <div style={{ background:'var(--surface)', border:'1px solid var(--line)', borderRadius:24,
        padding:'16px 14px 18px' }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', padding:'0 4px 12px' }}>
          <div style={{ width:90, height:11, borderRadius:6, background:'var(--chip)', animation:'pulse 1.2s ease-in-out infinite' }} />
          <div style={{ width:72, height:30, borderRadius:999, background:'var(--chip)', animation:'pulse 1.2s ease-in-out infinite' }} />
        </div>
        <div style={{ display:'flex', gap:10, justifyContent:'center', padding:'8px 0 14px' }}>
          {[54,46,62].map((w,i) => (
            <div key={i} style={{ width:w, height:58, borderRadius:14, background:'var(--chip)',
              animation:'pulse 1.2s ease-in-out infinite', animationDelay:(i*0.12)+'s' }} />
          ))}
        </div>
        <div style={{ borderTop:'1px dashed var(--line)', paddingTop:13, marginTop:2 }}>
          <div style={{ width:'70%', height:14, borderRadius:7, background:'var(--chip)', animation:'pulse 1.2s ease-in-out infinite' }} />
        </div>
        <div style={{ textAlign:'center', marginTop:12, fontFamily:'"JetBrains Mono",monospace', fontSize:11,
          color:'var(--muted)', letterSpacing:'0.08em' }}>translating…</div>
      </div>
    );
  }

  function TextMode() {
    const [dir, setDir] = useState('en2ru');       // active translation direction
    const [manual, setManual] = useState(false);   // user overrode auto-detect
    const [text, setText] = useState('');
    const [status, setStatus] = useState('idle');  // idle · loading · done · error
    const [item, setItem] = useState(null);        // StackedGloss item
    const [errMsg, setErrMsg] = useState('');
    const abortRef = useRef(null);
    const debounceRef = useRef(null);

    const effectiveDir = manual ? dir : detectDir(text);
    const [from, to] = effectiveDir === 'en2ru' ? ['English', 'Russian'] : ['Russian', 'English'];
    const chips = ['Hello', 'Thank you', 'Where is the bathroom', "I don't understand", 'How much is this'];

    const cancelInflight = () => {
      if (abortRef.current) { abortRef.current.abort(); abortRef.current = null; }
      if (debounceRef.current) { clearTimeout(debounceRef.current); debounceRef.current = null; }
    };

    const run = useCallback(async (raw, d) => {
      const q = (raw || '').trim();
      if (!q) { setStatus('idle'); setItem(null); return; }
      cancelInflight();
      const ctrl = new AbortController();
      abortRef.current = ctrl;
      setStatus('loading'); setErrMsg('');
      try {
        const res = await callTranslate(q, d, ctrl.signal);
        if (ctrl.signal.aborted) return;
        setItem(toGlossItem(res, res.direction || d));
        setStatus('done');
      } catch (err) {
        if (err.name === 'AbortError') return;
        // fail loudly but kindly; surface any partial we did resolve
        if (err.payload) { setItem(toGlossItem(err.payload, d)); }
        else { setItem(null); }
        setErrMsg(err.code === 'llm_unavailable'
          ? 'Full translation coming soon — try shorter phrases or single words.'
          : err.code === 'text_too_long' ? 'That’s a bit long — try a shorter phrase.'
          : err.code === 'empty_text' ? ''
          : err.code === 'rate_limited' ? 'Too many translations just now — give it a moment.'
          : 'Couldn’t translate that just now.');
        setStatus(err.code === 'empty_text' ? 'idle' : 'error');
      } finally {
        if (abortRef.current === ctrl) abortRef.current = null;
      }
    }, []);

    // Live-resolve single words (free dictionary/cache tier); debounce.
    // Multi-word input waits for explicit submit (Enter / Translate btn).
    const onChange = (v) => {
      setText(v);
      cancelInflight();
      const q = v.trim();
      if (!q) { setStatus('idle'); setItem(null); return; }
      const d = manual ? dir : detectDir(v);
      if (!isMultiWord(v)) {
        debounceRef.current = setTimeout(() => run(v, d), 350);
      } else {
        // keep prior result visible; require submit for the LLM tier
        setStatus(s => (s === 'loading' ? 'idle' : s));
      }
    };

    const submit = () => { const d = manual ? dir : detectDir(text); run(text, d); };

    const swap = () => {
      const nd = effectiveDir === 'en2ru' ? 'ru2en' : 'en2ru';
      setManual(true); setDir(nd); setText(''); setItem(null); setStatus('idle'); cancelInflight();
    };

    useEffect(() => () => cancelInflight(), []);

    const showSkeleton = status === 'loading' && !item;
    const showResult = item != null;

    return (
      <div style={{ display:'flex', flexDirection:'column', gap:14 }}>
        {/* language bar */}
        <div style={{ display:'flex', alignItems:'center', gap:10 }}>
          <div style={{ flex:1, textAlign:'center', fontFamily:'"Hanken Grotesk",system-ui', fontWeight:800,
            fontSize:14.5, color:'var(--ink)', background:'var(--surface)', border:'1px solid var(--line)',
            borderRadius:12, padding:'11px 8px' }}>{from}</div>
          <button onClick={swap} aria-label="Swap direction"
            style={{ appearance:'none', border:'none', cursor:'pointer', width:40, height:40, borderRadius:12,
              background:'var(--ink)', color:'#fff', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}>
            <svg width="20" height="20" viewBox="0 0 24 24"><path d="M7 4v13M7 4L4 7m3-3l3 3M17 20V7m0 13l3-3m-3 3l-3-3"
              stroke="#fff" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
          </button>
          <div style={{ flex:1, textAlign:'center', fontFamily:'"Hanken Grotesk",system-ui', fontWeight:800,
            fontSize:14.5, color:'var(--ink)', background:'var(--surface)', border:'1px solid var(--line)',
            borderRadius:12, padding:'11px 8px' }}>{to}</div>
        </div>
        {!manual && (
          <div style={{ textAlign:'center', fontFamily:'"JetBrains Mono",monospace', fontSize:10,
            color:'var(--muted)', letterSpacing:'0.08em', marginTop:-8 }}>auto-detecting language</div>
        )}

        {/* input */}
        <div style={{ background:'var(--surface)', border:'1px solid var(--line)', borderRadius:18, padding:'14px 16px',
          display:'flex', flexDirection:'column', gap:10 }}>
          <textarea value={text}
            onChange={e => onChange(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submit(); } }}
            rows={2}
            placeholder={effectiveDir === 'en2ru' ? 'Type English…' : 'Введите русский…'}
            style={{ width:'100%', border:'none', outline:'none', resize:'none', background:'transparent',
              fontFamily: effectiveDir === 'en2ru' ? '"Hanken Grotesk",system-ui' : '"Golos Text",system-ui',
              fontSize:19, fontWeight:600, color:'var(--ink)', lineHeight:1.3 }} />
          {text.trim() && isMultiWord(text) && (
            <button onClick={submit}
              style={{ alignSelf:'flex-end', appearance:'none', border:'none', cursor:'pointer',
                background:'var(--primary)', color:'#fff', borderRadius:999, padding:'8px 18px',
                fontFamily:'"Hanken Grotesk",system-ui', fontWeight:800, fontSize:13,
                boxShadow:'0 8px 18px -10px var(--primary)' }}>Translate</button>
          )}
        </div>

        {/* output */}
        {showSkeleton ? <Skeleton /> : showResult ? (
          <div style={{ display:'flex', flexDirection:'column', gap:10 }}>
            <StackedGloss item={item} surface="translate"
              saveKind={item.lemma_id != null ? 'lemma' : (item.type === 'word' ? 'phrase' : 'translation')} />
            {item.literal && (
              <div style={{ display:'flex', alignItems:'baseline', gap:8, padding:'0 6px' }}>
                <span style={{ fontFamily:'"JetBrains Mono",monospace', fontSize:9.5, fontWeight:600,
                  letterSpacing:'0.12em', textTransform:'uppercase', color:'var(--muted)', flexShrink:0 }}>literal</span>
                <span style={{ fontFamily:'"Hanken Grotesk",system-ui', fontWeight:600, fontSize:13.5,
                  color:'var(--muted)', lineHeight:1.3 }}>“{item.literal}”</span>
              </div>
            )}
            {status === 'error' && errMsg && (
              <div style={{ fontFamily:'"Hanken Grotesk",system-ui', fontWeight:600, fontSize:12.5,
                color:'var(--muted)', padding:'0 6px' }}>{errMsg} <button onClick={submit}
                style={{ appearance:'none', border:'none', background:'transparent', cursor:'pointer',
                  color:'var(--primary-deep)', fontWeight:800, fontSize:12.5, fontFamily:'inherit' }}>Retry</button></div>
            )}
          </div>
        ) : status === 'error' ? (
          <div style={{ background:'var(--surface)', border:'1px solid var(--line)', borderRadius:18,
            padding:'18px 16px', textAlign:'center' }}>
            <div style={{ fontFamily:'"Hanken Grotesk",system-ui', fontWeight:700, fontSize:14, color:'var(--ink)' }}>
              {errMsg || 'Couldn’t translate that just now.'}</div>
            <button onClick={submit}
              style={{ marginTop:12, appearance:'none', border:'1px solid var(--line)', cursor:'pointer',
                background:'var(--bg)', borderRadius:999, padding:'8px 18px',
                fontFamily:'"Hanken Grotesk",system-ui', fontWeight:800, fontSize:13, color:'var(--primary-deep)' }}>
              Tap to retry</button>
          </div>
        ) : (
          <div style={{ background:'transparent', border:'1.5px dashed var(--line)', borderRadius:18,
            padding:'24px 16px', minHeight:96, display:'flex', alignItems:'center', justifyContent:'center' }}>
            <div style={{ fontFamily:'"Hanken Grotesk",system-ui', fontWeight:600, fontSize:14, color:'var(--muted)',
              textAlign:'center' }}>Translation appears here.</div>
          </div>
        )}

        {/* quick phrases — cache-warm, call the real API */}
        {effectiveDir === 'en2ru' && (
          <div style={{ display:'flex', flexWrap:'wrap', gap:8 }}>
            {chips.map(c => (
              <button key={c} onClick={() => { setText(c); setManual(true); setDir('en2ru'); run(c, 'en2ru'); }}
                style={{ appearance:'none', border:'1px solid var(--line)', cursor:'pointer', background:'var(--surface)',
                  borderRadius:999, padding:'8px 13px', fontFamily:'"Hanken Grotesk",system-ui', fontWeight:700,
                  fontSize:12.5, color:'var(--ink)' }}>{c}</button>
            ))}
          </div>
        )}
      </div>
    );
  }

  function LiveMode() {
    const [phase, setPhase] = useState('idle'); // idle · listening · running
    const [shown, setShown] = useState(0);
    const timers = useRef([]);
    const demo = A.liveDemo;
    useEffect(() => () => timers.current.forEach(clearTimeout), []);

    const start = () => {
      timers.current.forEach(clearTimeout); timers.current = [];
      setShown(0); setPhase('listening');
      timers.current.push(setTimeout(() => {
        setPhase('running');
        demo.forEach((ex, i) => {
          timers.current.push(setTimeout(() => {
            setShown(i + 1);
            speak(ex.ru);
          }, i * 2200));
        });
      }, 900));
    };

    return (
      <div style={{ display:'flex', flexDirection:'column', gap:14, minHeight:380 }}>
        <div style={{ flex:1, display:'flex', flexDirection:'column', gap:12 }}>
          {demo.slice(0, shown).map((ex, i) => (
            <div key={i} style={{ display:'flex', flexDirection:'column', gap:8,
              animation:'screenIn .3s ease' }}>
              <div style={{ alignSelf:'flex-start', maxWidth:'85%', background:'var(--chip)', borderRadius:'16px 16px 16px 4px',
                padding:'10px 14px', fontFamily:'"Hanken Grotesk",system-ui', fontWeight:600, fontSize:14.5, color:'var(--ink)' }}>
                {ex.en}
              </div>
              <div style={{ alignSelf:'flex-end', maxWidth:'88%', background:'var(--ink)', borderRadius:'16px 16px 4px 16px',
                padding:'12px 15px', display:'flex', alignItems:'center', gap:12 }}>
                <div style={{ minWidth:0 }}>
                  <div style={{ fontFamily:'"Golos Text",system-ui', fontWeight:600, fontSize:18, color:'#fff', lineHeight:1.2 }}>{ex.ru}</div>
                  <div style={{ fontFamily:'"JetBrains Mono",monospace', fontSize:11, color:'rgba(255,255,255,0.6)', marginTop:3 }}>{ex.phon}</div>
                </div>
                <button onClick={() => speak(ex.ru)} style={{ appearance:'none', border:'none', cursor:'pointer',
                  background:'transparent', flexShrink:0 }}><Speaker size={18} color="rgba(255,255,255,0.85)" /></button>
              </div>
            </div>
          ))}
          {phase === 'listening' && (
            <div style={{ textAlign:'center', fontFamily:'"Hanken Grotesk",system-ui', fontWeight:700, fontSize:14,
              color:'var(--muted)', paddingTop:30 }}>Listening…</div>
          )}
          {phase === 'idle' && (
            <div style={{ textAlign:'center', fontFamily:'"Hanken Grotesk",system-ui', fontWeight:600, fontSize:14,
              color:'var(--muted)', padding:'40px 20px' }}>
              Tap the mic and speak — Russian Learning App transcribes and translates the conversation in real time.
            </div>
          )}
        </div>

        <div style={{ display:'flex', justifyContent:'center', paddingTop:6 }}>
          <button onClick={start}
            style={{ appearance:'none', border:'none', cursor:'pointer', width:78, height:78, borderRadius:999,
              background: phase === 'listening' ? 'oklch(0.58 0.17 25)' : 'var(--primary)', color:'#fff',
              display:'flex', alignItems:'center', justifyContent:'center', position:'relative',
              boxShadow:'0 14px 30px -12px var(--primary)' }}>
            {phase === 'listening' && <span style={{ position:'absolute', inset:-6, borderRadius:999,
              border:'3px solid oklch(0.58 0.17 25)', opacity:0.4, animation:'ping 1.1s ease-out infinite' }} />}
            <svg width="30" height="30" viewBox="0 0 24 24">
              <rect x="9" y="3" width="6" height="11" rx="3" fill="#fff" />
              <path d="M5 11a7 7 0 0014 0M12 18v3" stroke="#fff" strokeWidth="2" fill="none" strokeLinecap="round" />
            </svg>
          </button>
        </div>
      </div>
    );
  }

  function Translate() {
    const [mode, setMode] = useState('text');
    return (
      <div style={{ minHeight:'100%', background:'var(--bg)' }}>
        <div style={{ padding:'58px 20px 0' }}>
          <div style={{ fontFamily:'"Bricolage Grotesque",system-ui', fontWeight:800, fontSize:30,
            color:'var(--ink)', letterSpacing:'-0.02em' }}>Translate</div>
          <div style={{ marginTop:14 }}>
            <Seg value={mode} onChange={setMode}
              options={[{ id:'text', label:'Text' }, { id:'live', label:'Live conversation' }]} />
          </div>
        </div>
        <div style={{ padding:'18px 20px 100px' }}>
          {mode === 'text' ? <TextMode /> : <LiveMode />}
        </div>
      </div>
    );
  }

  window.Translate = Translate;
})();
