import { useCallback, useEffect, useMemo, useState } from 'react'
import { api, type VocabGrade, type VocabWord } from '../../api/client'
import { speak, speechSupported, stopSpeech } from '../../audio/speech'
import { useFocusTrap } from '../../hooks/useFocusTrap'

// GardenPanel is the vocabulary garden: every word the writer has looked up,
// grown into a blossom that opens further the more she remembers it, plus a
// gentle spaced-repetition review. Words are captured automatically on lookup
// (zero effort), so the garden fills itself as she writes. The sleepy kitten
// naps among the blossoms — the same companion gag, at rest in her little
// meadow. Bilingual, zh-first, to match Petal's chrome.

interface Props {
  onClose: () => void
  // Open the document a word was met in (so "where did I see this?" is one tap).
  onOpenDoc?: (docId: string) => void
}

// blossom maps a word's successful-review count to how bloomed its flower looks:
// a fresh seedling opens into a full blossom as it's remembered. No wilting — a
// forgotten word just stops climbing, never shames.
function blossom(reps: number): string {
  if (reps <= 0) return '🌱'
  if (reps <= 2) return '🌿'
  if (reps <= 4) return '🌷'
  if (reps <= 6) return '🌸'
  return '🌺'
}

// blankOut hides the target word in its example sentence with a soft blank, so a
// flashcard can quiz recall in context. Whole-word, case-insensitive.
function blankOut(sentence: string, word: string): string {
  if (!sentence) return ''
  try {
    const re = new RegExp(`\\b${word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'gi')
    return sentence.replace(re, '____')
  } catch {
    return sentence
  }
}

export function GardenPanel({ onClose, onOpenDoc }: Props) {
  const [words, setWords] = useState<VocabWord[] | null>(null)
  const [due, setDue] = useState<VocabWord[]>([])
  const [error, setError] = useState(false)
  // Review session state: the queue (snapshot of due at start), a cursor, and
  // whether the current card's answer is revealed.
  const [queue, setQueue] = useState<VocabWord[] | null>(null)
  const [cursor, setCursor] = useState(0)
  const [revealed, setRevealed] = useState(false)
  const [expanded, setExpanded] = useState<string | null>(null)
  const panelRef = useFocusTrap<HTMLElement>()

  // Read-aloud is fire-and-forget, so a word she tapped could still be speaking
  // when the panel closes. Cancel any in-flight audio on unmount so it can't
  // outlive the garden.
  useEffect(() => stopSpeech, [])

  const load = useCallback(async () => {
    setError(false)
    // Settle the two requests independently: a failed /due shouldn't blank the
    // whole garden when the word list loaded fine. Only listVocab failing is a
    // true error state; a dueVocab failure just hides the review button.
    const [allRes, dueRes] = await Promise.allSettled([api.listVocab(), api.dueVocab()])
    if (allRes.status === 'fulfilled') setWords(allRes.value)
    else setError(true)
    if (dueRes.status === 'fulfilled') setDue(dueRes.value)
    else setDue([])
  }, [])

  useEffect(() => {
    void load()
  }, [load])

  // Escape closes the panel (or ends a review session back to the garden).
  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        if (queue) setQueue(null)
        else onClose()
      }
    }
    window.addEventListener('keydown', onKey)
    return () => window.removeEventListener('keydown', onKey)
  }, [onClose, queue])

  const startReview = useCallback(() => {
    if (due.length === 0) return
    setQueue(due)
    setCursor(0)
    setRevealed(false)
  }, [due])

  const grade = useCallback(
    async (g: VocabGrade) => {
      if (!queue) return
      const card = queue[cursor]
      if (card) {
        try {
          await api.reviewVocab(card.id, g)
        } catch {
          /* keep going — a failed grade just won't reschedule */
        }
      }
      const nextCursor = cursor + 1
      if (nextCursor >= queue.length) {
        // Session done — refresh the garden and drop back to it.
        setQueue(null)
        void load()
      } else {
        setCursor(nextCursor)
        setRevealed(false)
      }
    },
    [queue, cursor, load],
  )

  const removeWord = useCallback(async (id: string) => {
    setWords((prev) => (prev ? prev.filter((w) => w.id !== id) : prev))
    setDue((prev) => prev.filter((w) => w.id !== id))
    try {
      await api.deleteVocab(id)
    } catch {
      void load()
    }
  }, [load])

  return (
    <div className="petal-no-print fixed inset-0 z-40 flex justify-end">
      <div
        className="absolute inset-0"
        style={{ background: 'rgba(61, 46, 57, 0.18)' }}
        onClick={() => (queue ? setQueue(null) : onClose())}
      />

      <aside
        ref={panelRef}
        role="dialog"
        aria-modal="true"
        aria-label="词汇花园 · Vocabulary Garden"
        tabIndex={-1}
        className="relative flex h-full w-full max-w-[420px] flex-col"
        style={{
          background: 'var(--color-surface)',
          borderLeft: '1px solid var(--color-border)',
          boxShadow: 'var(--shadow-soft)',
        }}
      >
        <header
          className="flex items-center justify-between px-5 py-4"
          style={{ borderBottom: '1px solid var(--color-border)' }}
        >
          <div>
            <div className="text-base font-extrabold text-plum">🌷 词汇花园 · Vocabulary Garden</div>
            <div className="text-xs" style={{ color: 'var(--color-muted)' }}>
              {queue ? '复习中 · Reviewing — recall, then grade yourself' : 'Words you looked up, blooming as you learn them'}
            </div>
          </div>
          <button
            type="button"
            aria-label="Close garden"
            onClick={onClose}
            className="flex h-8 w-8 items-center justify-center rounded-full text-lg"
            style={{ color: 'var(--color-muted)' }}
            onMouseEnter={(e) => (e.currentTarget.style.background = 'var(--color-surface-alt)')}
            onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}
          >
            ✕
          </button>
        </header>

        {queue ? (
          <ReviewSession
            queue={queue}
            cursor={cursor}
            revealed={revealed}
            onReveal={() => setRevealed(true)}
            onGrade={grade}
            onQuit={() => setQueue(null)}
          />
        ) : (
          <GardenView
            words={words}
            due={due}
            error={error}
            expanded={expanded}
            onToggleExpand={(id) => setExpanded((cur) => (cur === id ? null : id))}
            onStartReview={startReview}
            onRemove={removeWord}
            onOpenDoc={onOpenDoc}
            onRetry={load}
          />
        )}
      </aside>
    </div>
  )
}

// --- garden grid ------------------------------------------------------------

function GardenView({
  words,
  due,
  error,
  expanded,
  onToggleExpand,
  onStartReview,
  onRemove,
  onOpenDoc,
  onRetry,
}: {
  words: VocabWord[] | null
  due: VocabWord[]
  error: boolean
  expanded: string | null
  onToggleExpand: (id: string) => void
  onStartReview: () => void
  onRemove: (id: string) => void
  onOpenDoc?: (docId: string) => void
  onRetry: () => void
}) {
  // Index the due cards once so the per-word "due" check below is O(1), not a
  // linear scan of `due` for every word in the garden.
  const dueIds = useMemo(() => new Set(due.map((d) => d.id)), [due])
  return (
    <>
      {due.length > 0 && (
        <div className="px-4 pt-4">
          <button
            type="button"
            onClick={onStartReview}
            className="w-full rounded-full py-3 text-sm font-extrabold text-white"
            style={{ background: 'var(--color-accent)' }}
            onMouseEnter={(e) => (e.currentTarget.style.background = 'var(--color-accent-hover)')}
            onMouseLeave={(e) => (e.currentTarget.style.background = 'var(--color-accent)')}
          >
            复习 {due.length} 个词 · Review {due.length} due 🌸
          </button>
        </div>
      )}

      <div className="min-h-0 flex-1 overflow-y-auto px-4 py-4">
        {error ? (
          <div className="px-2 py-6 text-center text-sm" style={{ color: 'var(--color-muted)' }}>
            Couldn’t load your garden just now.
            <button onClick={onRetry} className="ml-1 font-bold text-plum underline">
              Try again
            </button>
          </div>
        ) : words === null ? (
          <div className="px-2 py-6 text-center text-sm" style={{ color: 'var(--color-muted)' }}>
            Loading…
          </div>
        ) : words.length === 0 ? (
          <div className="px-3 py-10 text-center" style={{ color: 'var(--color-muted)' }}>
            <div className="mb-2 text-4xl">🌱🐱💤</div>
            <p className="text-sm leading-relaxed">
              你的花园还空着。<br />
              右键点一个英文单词查它的意思——它就会在这里发芽。
            </p>
            <p className="mt-2 text-xs">
              Your garden is empty. Look up an English word (right-click it) and it’ll sprout here.
            </p>
          </div>
        ) : (
          <ul className="flex flex-col gap-1.5">
            {words.map((w) => {
              const open = expanded === w.id
              const isDue = dueIds.has(w.id)
              return (
                <li key={w.id}>
                  <button
                    type="button"
                    onClick={() => onToggleExpand(w.id)}
                    className="flex w-full items-center gap-2.5 rounded-2xl px-3 py-2.5 text-left"
                    style={{
                      background: open ? 'var(--color-surface-alt)' : 'transparent',
                      border: `1px solid ${open ? 'var(--color-border)' : 'transparent'}`,
                    }}
                  >
                    <span className="text-xl" aria-hidden>
                      {blossom(w.reps)}
                    </span>
                    <span className="flex min-w-0 flex-1 flex-col">
                      <span className="truncate text-sm font-bold text-plum">{w.word}</span>
                      {(w.gloss || w.definition) && (
                        <span
                          className="truncate text-xs"
                          style={{
                            color: 'var(--color-muted)',
                            fontFamily: w.gloss
                              ? "'Nunito','PingFang SC','Microsoft YaHei','Noto Sans CJK SC',sans-serif"
                              : 'var(--font-body)',
                          }}
                        >
                          {w.gloss || w.definition}
                        </span>
                      )}
                    </span>
                    {isDue && (
                      <span
                        className="shrink-0 rounded-full px-2 py-0.5 text-[10px] font-bold"
                        style={{ background: 'var(--color-accent)', color: '#fff' }}
                      >
                        待复习 · due
                      </span>
                    )}
                  </button>

                  {open && (
                    <div className="mb-1 ml-9 mr-2 mt-1 flex flex-col gap-2 text-xs" style={{ color: 'var(--color-plum)' }}>
                      {w.phonetic && <span style={{ color: 'var(--color-muted)' }}>/{w.phonetic}/</span>}
                      {w.example && (
                        <p className="italic leading-snug" style={{ fontFamily: 'var(--font-body)' }}>
                          “{w.example}”
                        </p>
                      )}
                      <div className="text-[11px]" style={{ color: 'var(--color-muted)' }}>
                        复习 {w.reps} 次 · seen {w.reps}× · 间隔 {w.interval_days}d
                      </div>
                      <div className="flex items-center gap-2">
                        {speechSupported() && (
                          <button
                            type="button"
                            onClick={() => speak(w.word)}
                            className="rounded-full px-2.5 py-1 text-xs font-semibold"
                            style={{ background: 'var(--color-surface-alt)' }}
                          >
                            🔊 朗读
                          </button>
                        )}
                        {w.doc_id && onOpenDoc && (
                          <button
                            type="button"
                            onClick={() => onOpenDoc(w.doc_id as string)}
                            className="rounded-full px-2.5 py-1 text-xs font-semibold"
                            style={{ background: 'var(--color-surface-alt)' }}
                          >
                            📄 出处 · Source
                          </button>
                        )}
                        <button
                          type="button"
                          onClick={() => onRemove(w.id)}
                          className="ml-auto rounded-full px-2.5 py-1 text-xs font-semibold"
                          style={{ color: 'var(--color-muted)' }}
                        >
                          🗑 移除
                        </button>
                      </div>
                    </div>
                  )}
                </li>
              )
            })}
          </ul>
        )}
      </div>

      {!error && words && words.length > 0 && (
        <div
          className="shrink-0 px-4 py-2.5 text-center text-[11px]"
          style={{ borderTop: '1px solid var(--color-border)', color: 'var(--color-muted)' }}
        >
          🐱💤 {words.length} 朵花在花园里 · {words.length} blossom{words.length > 1 ? 's' : ''} growing
        </div>
      )}
    </>
  )
}

// --- flashcard review -------------------------------------------------------

function ReviewSession({
  queue,
  cursor,
  revealed,
  onReveal,
  onGrade,
  onQuit,
}: {
  queue: VocabWord[]
  cursor: number
  revealed: boolean
  onReveal: () => void
  onGrade: (g: VocabGrade) => void
  onQuit: () => void
}) {
  const card = queue[cursor]
  // The meaning shown/asked is the Chinese gloss, or the English definition when
  // a word has no gloss — so definition-only words are still reviewable.
  const meaning = card?.gloss || card?.definition || ''
  // Alternate the quiz direction so she practices both recognition (see the
  // English, recall the meaning) and production (see the meaning, recall the
  // English word). Parity of the cursor keeps it deterministic within a session.
  const production = cursor % 2 === 1 && !!meaning

  const prompt = useMemo(() => {
    if (!card) return ''
    if (production) return meaning
    return card.example ? blankOut(card.example, card.word) : card.word
  }, [card, production, meaning])

  if (!card) return null

  return (
    <div className="flex min-h-0 flex-1 flex-col px-5 py-4">
      <div className="mb-3 flex items-center justify-between text-xs" style={{ color: 'var(--color-muted)' }}>
        <span>
          {cursor + 1} / {queue.length}
        </span>
        <button type="button" onClick={onQuit} className="font-semibold underline">
          结束 · End
        </button>
      </div>

      {/* The card */}
      <div
        className="flex flex-1 flex-col items-center justify-center rounded-3xl px-5 py-8 text-center"
        style={{ background: 'var(--color-surface-alt)', border: '1px solid var(--color-border)' }}
      >
        <div className="mb-2 text-3xl" aria-hidden>
          {blossom(card.reps)}
        </div>
        <div
          className="text-xl font-extrabold leading-snug text-plum"
          style={{
            fontFamily: production
              ? "'Nunito','PingFang SC','Microsoft YaHei','Noto Sans CJK SC',sans-serif"
              : 'var(--font-body)',
          }}
        >
          {prompt}
        </div>
        <div className="mt-1 text-xs" style={{ color: 'var(--color-muted)' }}>
          {production ? '这个中文意思的英文单词是？· Which English word?' : '这个词什么意思？· What does this mean?'}
        </div>

        {revealed && (
          <div className="mt-5 w-full border-t pt-4" style={{ borderColor: 'var(--color-border)' }}>
            <div className="flex items-center justify-center gap-2">
              <span className="text-lg font-extrabold text-plum">{card.word}</span>
              {speechSupported() && (
                <button
                  type="button"
                  onClick={() => speak(card.word)}
                  aria-label={`Pronounce ${card.word}`}
                  className="flex h-6 w-6 items-center justify-center rounded-full text-xs"
                  style={{ background: 'var(--color-surface)' }}
                >
                  🔊
                </button>
              )}
            </div>
            {card.phonetic && (
              <div className="mt-0.5 text-xs" style={{ color: 'var(--color-muted)' }}>
                /{card.phonetic}/
              </div>
            )}
            {meaning && (
              <div
                className="mt-1 text-sm font-semibold"
                style={{
                  color: 'var(--color-accent-hover)',
                  // Chinese gloss gets the CJK stack; an English definition fallback
                  // reads better in the body font.
                  fontFamily: card.gloss
                    ? "'Nunito','PingFang SC','Microsoft YaHei','Noto Sans CJK SC',sans-serif"
                    : 'var(--font-body)',
                }}
              >
                {meaning}
              </div>
            )}
            {card.example && (
              <p className="mt-2 text-xs italic leading-snug" style={{ color: 'var(--color-muted)', fontFamily: 'var(--font-body)' }}>
                “{card.example}”
              </p>
            )}
          </div>
        )}
      </div>

      {/* Controls */}
      <div className="mt-4">
        {!revealed ? (
          <button
            type="button"
            onClick={onReveal}
            className="w-full rounded-full py-3 text-sm font-extrabold text-white"
            style={{ background: 'var(--color-accent)' }}
            onMouseEnter={(e) => (e.currentTarget.style.background = 'var(--color-accent-hover)')}
            onMouseLeave={(e) => (e.currentTarget.style.background = 'var(--color-accent)')}
          >
            翻看答案 · Show answer
          </button>
        ) : (
          <div className="grid grid-cols-3 gap-2">
            <GradeButton color="var(--color-peach)" zh="再来" en="Again" onClick={() => onGrade('again')} />
            <GradeButton color="var(--color-mint)" zh="记得" en="Good" onClick={() => onGrade('good')} />
            <GradeButton color="var(--color-honey)" zh="太简单" en="Easy" onClick={() => onGrade('easy')} />
          </div>
        )}
      </div>
    </div>
  )
}

function GradeButton({ color, zh, en, onClick }: { color: string; zh: string; en: string; onClick: () => void }) {
  return (
    <button
      type="button"
      onClick={onClick}
      className="flex flex-col items-center rounded-2xl py-2.5 text-plum"
      style={{ background: color }}
    >
      <span className="text-sm font-extrabold">{zh}</span>
      <span className="text-[11px] font-semibold opacity-80">{en}</span>
    </button>
  )
}
