// 문장 설명 — 조각 시트(길게 누르기)의 ✨ 버튼으로 한 문장의 번역·구조·포인트·뉘앙스를 본다.
// 영어·일본어가 같은 시트를 쓴다. 서버(server/explain.js)가 luna로 만들고 DB에 캐시한다.
// ☆ 저장을 누르면 그 시점의 설명이 saved_sentences에 스냅샷으로 남고, 헤더의 저장된문장 탭
// (SavedSentencesTab)에서 모아 본다 — 표현·어휘·조각 칸반(저장함)과는 별개다.
//
// classic <script type="text/babel" src="pages/Explain.jsx"> 로 로드된다 — ES 모듈이 아니므로
// import/export 금지, 전역 함수 선언만 쓴다 (pages/Lookup.jsx와 같은 규칙).
// chunkRanges·sliceRuby·JpRuby·FuriganaToggle·useRubyCls·apiFetch는 index.html의 전역을 그대로 쓴다.
const { useState, useEffect, useRef, useMemo } = React;

// 칩에 보여줄 문장 앞부분 — 일본어는 글자가 촘촘해 더 짧게 자른다
function exClip(text, lang) {
  const n = lang === 'jp' ? 12 : 22;
  const t = text.trim();
  return t.length > n ? t.slice(0, n).trimEnd() + '…' : t;
}

// 설명 본문(구조·포인트·뉘앙스) — 시트와 저장된문장 카드가 같이 쓴다
function ExplainBody({ d, lang }) {
  if (!d) return null;
  return (
    <>
      {d.structure_ko && (
        <div className="lk-sec">
          <div className="lk-sec-t">구조</div>
          <div className="ex-struct">{d.structure_ko}</div>
        </div>
      )}
      {!!d.points?.length && (
        <div className="lk-sec">
          <div className="lk-sec-t">포인트</div>
          {d.points.map((p, i) => (
            <div key={i} className="ex-point">
              <div className={`ex-target ${lang === 'jp' ? 'jp' : ''}`}>
                {lang === 'jp' ? <JpRuby text={p.target} ruby={p.target_ruby} /> : p.target}
              </div>
              <div className="ex-note">{p.note}</div>
            </div>
          ))}
        </div>
      )}
      {d.nuance_ko && (
        <div className="lk-sec">
          <div className="lk-sec-t">뉘앙스</div>
          <div className="ex-nuance">{d.nuance_ko}</div>
        </div>
      )}
    </>
  );
}

// 한 줄(line)은 같은 화자의 문장 여러 개가 합쳐져 있을 수 있다 — 항상 한 문장만 설명한다.
// offset은 조각 시트에서 고른 조각의 시작 위치: 그 조각이 속한 문장을 처음에 고른다.
function SentenceExplainSheet({ line, lines, offset = 0, lang, program, date, episodeId, cls, onClose }) {
  const text     = line?.text ?? '';
  const ranges   = useMemo(() => chunkRanges(text, 'sentence', lang), [text, lang]);
  const initial  = Math.max(0, ranges.findIndex(([a, b]) => offset >= a && offset < b));
  const [idx, setIdx] = useState(initial);
  const [st, setSt]   = useState({ loading: true, data: null, error: null });
  const [savedId, setSavedId] = useState(null);   // 이 문장이 저장된문장에 있으면 그 id
  const [saving, setSaving]   = useState(false);
  const openedAt = useRef(Date.now());
  const settled  = () => Date.now() - openedAt.current > 250;

  const [a, b]   = ranges[idx] ?? [0, text.length];
  const sentence = text.slice(a, b).trim();
  const ruby     = lang === 'jp' ? sliceRuby(line?.text_ruby, text, a, b) : null;
  const apiLang  = lang === 'jp' ? 'ja' : 'en';

  // 앞·뒤 대사 — 상황을 알아야 말투·뉘앙스 설명이 정확하다
  const pos  = Array.isArray(lines) ? lines.indexOf(line) : -1;
  const near = (l) => (l ? `${l.speaker}: ${l.text}` : null);
  const prev = pos > 0 ? near(lines[pos - 1]) : null;
  const next = pos >= 0 ? near(lines[pos + 1]) : null;

  const load = async (force = false) => {
    setSt({ loading: true, data: null, error: null });
    try {
      const d = await apiFetch('/api/explain-sentence', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          sentence, lang: apiLang, program,
          speaker: line?.speaker ?? null, translation: line?.translation ?? null,
          lineText: text, prev, next, force,
        }),
      });
      return d;
    } catch (e) {
      throw new Error(e.message || '설명을 받지 못했습니다');
    }
  };

  const settle = (d) => { setSt({ loading: false, data: d, error: null }); setSavedId(d?.saved_id ?? null); };

  useEffect(() => {
    let alive = true;
    load()
      .then(d => { if (alive) settle(d); })
      .catch(e => { if (alive) setSt({ loading: false, data: null, error: e.message }); });
    return () => { alive = false; };
  }, [sentence]);   // eslint-disable-line react-hooks/exhaustive-deps

  const regen = () => {
    if (st.loading) return;
    load(true)
      .then(settle)
      .catch(e => setSt({ loading: false, data: null, error: e.message }));
  };

  // ☆ 저장 ↔ ★ 저장됨 — 지금 화면의 설명을 그대로 스냅샷으로 담는다
  const toggleSave = async () => {
    const d = st.data;
    if (saving || st.loading || !d) return;
    setSaving(true);
    try {
      if (savedId) {
        await apiFetch(`/api/saved-sentences/${savedId}`, { method: 'DELETE' });
        setSavedId(null);
      } else {
        const r = await apiFetch('/api/saved-sentences', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            lang: apiLang, program, date: date ?? null, episode_id: episodeId ?? null,
            speaker: line?.speaker ?? null, sentence, translation: line?.translation ?? null,
            explain: { translation_ko: d.translation_ko, structure_ko: d.structure_ko,
                       points: d.points, nuance_ko: d.nuance_ko },
          }),
        });
        setSavedId(r.id ?? null);
      }
    } catch (e) {
      alert('저장 실패: ' + e.message);
    }
    setSaving(false);
  };

  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  const d = st.data;

  return (
    <div className="pick-back ex-back" onClick={() => { if (settled()) onClose(); }}>
      <ExplainStyles />
      <div className={`pick ${cls}`} onClick={e => e.stopPropagation()}
           role="dialog" aria-modal="true" aria-label="문장 설명">
        <div className="pick-hd">
          <span className="pick-title">✨ 문장 설명</span>
          <button className="pick-x" onClick={onClose} aria-label="닫기">✕</button>
        </div>

        {ranges.length > 1 && (
          <div className="ex-chips" role="tablist" aria-label="문장 고르기">
            {ranges.map(([x, y], i) => (
              <button key={i} role="tab" aria-selected={i === idx}
                className={`ex-chip ${i === idx ? 'on' : ''}`}
                onClick={() => setIdx(i)}>
                <span className="ex-chip-n">{i + 1}</span>{exClip(text.slice(x, y), lang)}
              </button>
            ))}
          </div>
        )}

        <div className="pick-body">
          <div className="ex-head">
            <div className={`ex-sent ${lang === 'jp' ? 'jp' : ''}`}>
              {lang === 'jp' ? <JpRuby text={sentence} ruby={ruby} /> : sentence}
            </div>
            {d?.translation_ko && <div className="ex-ko">{d.translation_ko}</div>}
          </div>

          {st.loading && <div className="lk-note lk-slow">설명 만드는 중...</div>}
          {st.error && (
            <div className="lk-note">
              {st.error}
              <div style={{ marginTop: 8 }}>
                <button className="btn" onClick={regen}>다시 시도</button>
              </div>
            </div>
          )}

          <ExplainBody d={d} lang={lang} />
        </div>

        <div className="pick-ft">
          <div className="pick-acts">
            <button className={`btn ${st.loading ? 'generating' : ''}`} onClick={regen} disabled={st.loading}
                    title="캐시된 설명을 버리고 새로 만듭니다">↻ 다시 만들기</button>
            <button className={`btn ${savedId ? 'saved' : ''} ${saving ? 'generating' : ''}`}
                    onClick={toggleSave} disabled={st.loading || !d}
                    title={savedId ? '저장된문장에서 뺍니다' : '이 설명을 저장된문장에 담습니다'}>
              {savedId ? '★ 저장됨' : '☆ 저장'}
            </button>
            <span className="grow" />
            <button className="btn" onClick={onClose}>닫기</button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ── 저장된문장 탭 ─────────────────────────────────────────────
// ☆ 저장한 문장 설명을 최신순 카드로 모아 본다. 프로그램 필터만 있고 학습 상태(칸반)는 없다.
const EX_PROGRAMS = {
  en: [['all', '전체'], ['easy', 'Easy'], ['power', 'Power']],
  jp: [['all', '전체'], ['jp_basic', '초급'], ['jp_middle', '중급']],
};
const exProgCls   = (p) => (p === 'power' ? 'power' : p === 'jp_basic' ? 'jp-basic' : p === 'jp_middle' ? 'jp-middle' : '');
const exProgLabel = (p) => ({ easy: 'EASY', power: 'POWER', jp_basic: '초급', jp_middle: '중급 일본어' }[p] ?? '');

function SavedSentencesTab({ lang, onNavigate }) {
  const [items, setItems]   = useState(null);     // null = 아직 로드 전
  const [prog, setProg]     = useState('all');
  const [open, setOpen]     = useState({});       // id → 펼침
  const sentCls = useRubyCls('ex-sent');
  const apiLang = lang === 'jp' ? 'ja' : 'en';

  const load = () => apiFetch(`/api/saved-sentences?lang=${apiLang}`).then(setItems).catch(() => setItems([]));
  useEffect(() => { load(); }, [apiLang]);   // eslint-disable-line react-hooks/exhaustive-deps

  const del = async (id) => {
    try {
      await apiFetch(`/api/saved-sentences/${id}`, { method: 'DELETE' });
      setItems(list => list.filter(s => s.id !== id));
    } catch (e) { alert('삭제 실패: ' + e.message); }
  };

  const filtered = (items ?? []).filter(s => prog === 'all' || s.program === prog);

  return (
    <div>
      <ExplainStyles />
      <div className="saved-filters">
        {EX_PROGRAMS[lang === 'jp' ? 'jp' : 'en'].map(([v, l]) => (
          <button key={v} className={`filter-btn ${prog === v ? 'active' : ''}`} onClick={() => setProg(v)}>{l}</button>
        ))}
        {lang === 'jp' && <><div style={{ flex: 1 }} /><FuriganaToggle /></>}
      </div>

      {items && !filtered.length && (
        <div className="empty-state">
          <div className="icon">✨</div>
          <div>저장된 문장이 없습니다</div>
          <div className="ex-empty-sub">본문을 길게 눌러 ✨ 문장 설명을 열고 ☆ 저장을 누르세요</div>
        </div>
      )}

      {filtered.map(s => {
        const cls  = exProgCls(s.program);
        const d    = s.explain;
        const isOn = !!open[s.id];
        const meta = [exProgLabel(s.program), s.date, s.speaker && s.speaker !== 'N' ? `${s.speaker}의 대사` : null]
          .filter(Boolean).join(' · ');
        return (
          <div key={s.id} className={`ex-card ${cls}`}>
            <div className="saved-top">
              <div className="saved-content">
                <div className={`${sentCls} ${lang === 'jp' ? 'jp' : ''}`}>
                  {lang === 'jp' ? <JpRuby text={s.sentence} ruby={s.sentence_ruby} /> : s.sentence}
                </div>
                {d?.translation_ko && <div className="ex-ko">{d.translation_ko}</div>}
                {meta && <div className="saved-meta">{meta}</div>}
              </div>
              <button className="del-btn" onClick={() => del(s.id)} aria-label="삭제">×</button>
            </div>
            <div className="saved-actions">
              <button className={`btn ${cls}`} onClick={() => setOpen(o => ({ ...o, [s.id]: !isOn }))}
                      aria-expanded={isOn}>
                {isOn ? '▲ 접기' : '▼ 설명 보기'}
              </button>
              {onNavigate && s.program && s.date && (
                <button className={`btn ${cls}`} onClick={() => onNavigate(s.program, s.date)}>📅 본문</button>
              )}
            </div>
            {isOn && (
              <div className="ex-card-body">
                <ExplainBody d={d} lang={lang} />
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
}

function ExplainStyles() {
  return <style>{EXPLAIN_CSS}</style>;
}

const EXPLAIN_CSS = `
/* 섹션 제목·안내문 — pages/Lookup.jsx의 LookupStyles는 사전 시트가 열려 있을 때만 주입되므로
   같은 규칙을 여기에도 둔다(내용 동일). */
.lk-sec{margin-top:16px;padding-top:13px;border-top:1px solid var(--border)}
.lk-sec-t{font-size:11px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--muted2);margin-bottom:9px}
.lk-note{font-size:12.5px;color:var(--muted2);line-height:1.6;padding:2px 0 4px}
.lk-slow{animation:pulse 1.6s ease-in-out infinite}

.ex-chips{display:flex;gap:6px;overflow-x:auto;padding:0 16px 12px;border-bottom:1px solid var(--border);scrollbar-width:none}
.ex-chips::-webkit-scrollbar{display:none}
.ex-chip{
  flex-shrink:0;display:inline-flex;align-items:center;gap:6px;max-width:220px;
  padding:5px 10px 5px 6px;border-radius:var(--pill);border:1px solid var(--border);
  background:var(--surface2);color:var(--muted);font-size:12.5px;font-weight:600;white-space:nowrap;
  transition:background .12s,color .12s,border-color .12s;
}
.ex-chip:hover{border-color:var(--muted2)}
.ex-chip.on{background:var(--ac-soft);border-color:var(--ac-line);color:var(--ac-ink)}
.ex-chip-n{width:17px;height:17px;display:grid;place-items:center;border-radius:50%;
  background:var(--surface3);color:var(--muted);font-size:10px;font-weight:700}
.ex-chip.on .ex-chip-n{background:var(--ac);color:#fff}

.ex-head{padding-bottom:12px;border-bottom:1px solid var(--border);margin-bottom:4px}
.ex-sent{font-family:var(--font-serif);font-size:21px;font-weight:600;line-height:1.4;color:var(--text);word-break:break-word}
.ex-sent.jp{font-family:inherit;font-size:19px;line-height:1.6}
.ex-sent.jp ruby rt{font-size:10px;color:var(--muted)}
.ex-ko{margin-top:7px;font-size:14px;color:var(--ac-ink);font-weight:600;line-height:1.55}

.ex-struct{font-size:14px;line-height:1.6;color:var(--text);padding:9px 12px;background:var(--surface2);border-radius:var(--r-sm)}
.ex-point{padding:9px 0 9px 12px;border-left:2px solid var(--ac-line)}
.ex-point + .ex-point{margin-top:4px}
.ex-target{font-size:15px;font-weight:700;color:var(--ac-ink);line-height:1.4;word-break:break-word}
.ex-target.jp{line-height:1.6}
.ex-target.jp ruby rt{font-size:9px;color:var(--muted)}
.ex-note{margin-top:4px;font-size:13.5px;line-height:1.65;color:var(--text)}
.ex-nuance{font-size:13.5px;line-height:1.7;color:var(--text)}

/* 저장된문장 카드 — index.html .card 톤 */
.ex-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);padding:16px 18px;margin-bottom:12px;box-shadow:var(--sh-sm)}
.ex-card .ex-sent{font-size:17px}
.ex-card .ex-sent.jp{font-size:16px}
.ex-card .ex-ko{margin-top:5px;font-size:13.5px}
.ex-card .saved-actions{margin-bottom:0}
.ex-card-body{margin-top:12px;padding-top:12px;border-top:1px solid var(--border)}
.ex-card-body .lk-sec + .lk-sec{margin-top:12px}
.ex-empty-sub{font-size:12.5px;color:var(--muted2);margin-top:2px}
`;
