// Main app — composes dashboard + avatar picker, holds state.
const { useState: useStateApp, useEffect: useEffectApp } = React;
const { Sparkles: SparklesIcon, X: XIcon } = LucideReact;
const { Settings, ChevronLeft, GraduationCap, Home, BarChart3, Library, MessageSquareHeart, ShoppingBag, Volume2 } = LucideReact;
// BookOpen already destructured in dashboard-widgets.jsx — use that one (don't re-declare)

// ── Auth / API helpers ────────────────────────────────────────────────────
const TOKEN_KEY = 'ttest_auth_token'
const USER_KEY  = 'ttest_user_data'

function getApiBase() {
  // Override wins. Otherwise host-aware: localhost → local backend, else prod.
  const override = localStorage.getItem('ttest_api_url')
  if (override) return override
  const h = (typeof window !== 'undefined' && window.location && window.location.hostname) || ''
  if (h === 'localhost' || h === '127.0.0.1' || h === '0.0.0.0') return 'http://localhost:8000'
  return 'https://2ttest.up.railway.app'
}

function getAuthToken() {
  return localStorage.getItem(TOKEN_KEY) || sessionStorage.getItem(TOKEN_KEY) || null
}

function apiFetch(path, options = {}) {
  const token = getAuthToken()
  const { method = 'GET', body, headers = {} } = options
  return fetch(getApiBase() + path, {
    method,
    headers: {
      ...(body ? { 'Content-Type': 'application/json' } : {}),
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
      ...headers,
    },
    ...(body ? { body: typeof body === 'string' ? body : JSON.stringify(body) } : {}),
  })
}

// ── Date helpers ──────────────────────────────────────────────────────────
const DAYS   = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']
const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
function formatDate(d) {
  return `${DAYS[d.getDay()]} · ${MONTHS[d.getMonth()]} ${d.getDate()}`
}

// ── Course mapping from exam sets ─────────────────────────────────────────
const CATEGORY_LABEL = { alevel: 'A-Level', toeic: 'TOEIC', m1: 'ม.1', m4: 'ม.4', gpax: 'กพ.', onet: 'O-NET', toefl: 'TOEFL', ielts: 'IELTS' }
const SUBJECT_LABEL  = { english: 'English', math: 'Math', thai: 'Thai', science: 'Science', social: 'Social', reasoning: 'Reasoning' }

function absUrl(path) {
  if (!path) return path
  if (/^https?:/i.test(path)) return path
  return getApiBase().replace(/\/$/, '') + path
}

// New: real ExamSet from /admin/sets API
function setToCourse(s, accuracyRate) {
  const sub   = s.subject
  const skill = sub === 'math' ? 'writing' : 'reading'
  const yearLabel = s.year ? ` ${s.year}` : ''
  const total = s.question_count || 0
  const rounds = Math.max(1, Number(s.rounds_count) || 1)
  return {
    id:               s.id,
    category:         s.category,
    subject:          sub,
    skill,
    level:            `${CATEGORY_LABEL[s.category] || s.category.toUpperCase()}${yearLabel}`,
    title:            s.name,
    description:      s.description || '',
    lessons:          total,
    duration:         `${Math.ceil(total * 1.5)} min`,
    progress:         accuracyRate || 0,
    price:            Number(s.price) || 0,
    originalPrice:    Number(s.original_price ?? s.price) || 0,
    seasonLabel:      s.season_label || null,
    seasonDiscountPercent: s.season_discount_percent || null,
    accessDays:       Number(s.access_days) || 30,
    isForSale:        s.is_for_sale !== false,
    hasAccess:        !!s.has_access,
    daysRemaining:    s.days_remaining,
    roundsCount:      rounds,
    questionsPerRound: rounds > 0 ? Math.floor(total / rounds) : total,
    roundsTotal:      s.rounds_total,
    roundsUsed:       s.rounds_used,
    roundsLeft:       s.rounds_left,
    bannerUrls:       (Array.isArray(s.banner_urls) ? s.banner_urls : []).map(absUrl),
  }
}

// Legacy: aggregate by (category, subject) — used when no real ExamSets exist yet
function examToCourse(exam, accuracyRate) {
  const cat   = exam.category
  const sub   = exam.subject
  const skill = sub === 'math' ? 'writing' : sub === 'english' ? 'reading' : 'reading'
  return {
    id:       `${cat}-${sub}`,
    skill,
    level:    `${CATEGORY_LABEL[cat] || cat.toUpperCase()} 2568`,
    title:    `${SUBJECT_LABEL[sub] || sub} ${CATEGORY_LABEL[cat] || cat}`,
    lessons:  exam.total_questions,
    duration: `${Math.ceil(exam.total_questions * 1.5)} min`,
    progress: accuracyRate || 0,
  }
}

// ── Empty state ───────────────────────────────────────────────────────────
const EmptyState = ({ text }) => (
  <div className="py-10 text-center text-[13px] text-[#8A93A6]">{text}</div>
)

const SidebarItem = ({ icon: Icon, label, active, onClick }) => (
  <button
    onClick={onClick}
    className={`w-full flex items-center gap-3 px-3 h-11 rounded-2xl text-[14px] transition ${
      active ? "bg-[#0B1220] text-white" : "text-[#2B3445] hover:bg-[#F6F8FB]"
    }`}
  >
    <Icon size={17} strokeWidth={2} />
    <span className="font-medium">{label}</span>
  </button>
);

// Inline minimal flame — used in streak card / season badges. Avoid emoji.
const FlameIcon = ({ size = 12, color = "currentColor" }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ display:"inline-block", verticalAlign:"-2px" }}>
    <path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/>
  </svg>
);

// Pick up impersonation handoff from admin: /student-dashboard/?impersonate=1#token=...&user=...
// Stores into localStorage immediately and strips the hash so the token
// doesn't linger in the address bar.
;(() => {
  try {
    if (window.location.search.indexOf("impersonate=1") < 0) return
    const params = new URLSearchParams(window.location.hash.replace(/^#/, ""))
    const tok  = params.get("token")
    const user = params.get("user")
    if (tok)  localStorage.setItem("ttest_auth_token", tok)
    if (user) {
      localStorage.setItem("ttest_user_data", user)
      try {
        const u = JSON.parse(user)
        if (u && u.name) localStorage.setItem("ttest_user_name", u.name)
      } catch (_) {}
    }
    window.history.replaceState(null, "", "/student-dashboard/")
  } catch (_) {}
})()

// ── Main App ──────────────────────────────────────────────────────────────
const App = () => {
  const isImpersonated = (() => {
    try { return JSON.parse(localStorage.getItem(USER_KEY) || "{}").email?.includes("sandbox+") } catch { return false }
  })()
  const userData = (() => { try { return JSON.parse(localStorage.getItem(USER_KEY) || "{}") } catch { return {} } })()
  const userName  = userData.name  || "นักเรียน"
  const userEmail = userData.email || ""

  const [avatarId, setAvatarIdRaw] = useStateApp(() => localStorage.getItem("thep_avatar_id") || "egg")
  const setAvatarId = (id) => { localStorage.setItem("thep_avatar_id", id); setAvatarIdRaw(id) }
  const [pickerOpen,    setPickerOpen]    = useStateApp(false)
  const [popupVisible,  setPopupVisible]  = useStateApp(false)
  const [popupDismissed,setPopupDismissed]= useStateApp(false)
  const [userMenuOpen,  setUserMenuOpen]  = useStateApp(false)
  const hasAvatar = avatarId !== "egg"

  const onLogout = () => {
    if (!confirm("ออกจากระบบ?")) return
    try {
      localStorage.removeItem(TOKEN_KEY)
      localStorage.removeItem(USER_KEY)
      localStorage.removeItem("ttest_user_name")
      sessionStorage.removeItem(TOKEN_KEY)
    } catch {}
    window.location.href = "/landing/"
  }

  const [dashData, setDashData] = useStateApp(null)
  const [packages, setPackages] = useStateApp([])
  const [loading,  setLoading]  = useStateApp(true)

  // Fetch real dashboard data + packages in parallel
  useEffectApp(() => {
    Promise.all([
      apiFetch('/exam/dashboard-data').then(r => r.ok ? r.json() : null).catch(() => null),
      apiFetch('/payments/packages').then(r => r.ok ? r.json() : []).catch(() => []),
    ]).then(([data, pkgs]) => {
      if (data) setDashData(data)
      if (Array.isArray(pkgs)) setPackages(pkgs)
    }).finally(() => setLoading(false))
  }, [])

  useEffectApp(() => {
    if (!hasAvatar) {
      const t = setTimeout(() => setPopupVisible(true), 1500)
      return () => clearTimeout(t)
    }
  }, [])
  useEffectApp(() => { if (hasAvatar) setPopupVisible(false) }, [hasAvatar])

  const [payModal,    setPayModal]    = useStateApp(null) // { item, type, finalPrice, promoCode }

  const [view,        setView]        = useStateApp("dashboard")
  const [activeCourse,setActiveCourse]= useStateApp(null)
  const [section,     setSection]     = useStateApp("overview") // overview | courses | catalog | progress | tutor | settings
  const [catalogCategory, setCatalogCategory] = useStateApp("m1")
  const [catalogSubject,  setCatalogSubject]  = useStateApp("all")

  const openCourse    = (course) => { setActiveCourse(course); setView("simulator") }

  const refreshSets = async () => {
    try {
      const [d, p] = await Promise.all([
        apiFetch('/exam/dashboard-data').then(r => r.ok ? r.json() : null),
        apiFetch('/payments/packages').then(r => r.ok ? r.json() : []),
      ])
      if (d) setDashData(d)
      if (Array.isArray(p)) setPackages(p)
    } catch {}
  }

  const buyPackage = (pkg) => {
    setPayModal({ item: pkg, type: 'package', finalPrice: pkg.price, promoCode: null })
  }

  const buyCourse = async (course) => {
    if (!course?.id) return
    const isFree = !course.price || course.price === 0

    if (isFree) {
      try {
        const r = await apiFetch('/payments/buy-free', { method: 'POST', body: { set_id: course.id } })
        if (!r.ok) { const err = await r.json().catch(() => ({})); alert(err.detail || 'ไม่สำเร็จ'); return }
        await refreshSets()
        openCourse({ ...course, hasAccess: true })
      } catch (e) { alert('Network error: ' + e.message) }
      return
    }

    // Paid course — open payment modal
    setPayModal({ item: course, type: 'course', finalPrice: course.price, promoCode: null })
  }
  const reviewMistake = (m)      => {
    setActiveCourse({ id: "review-" + m.id, title: "Review: " + m.question, skill: m.skill, level: "Review" })
    setView("simulator")
  }

  // Auto-buy hand-off from landing — landing redirects logged-in users to
  //   /student-dashboard/?buy=<set_id>     (individual course)
  //   /student-dashboard/?buy_pkg=<id>      (package bundle)
  // Once dashData + packages are loaded, find the match, kick off the
  // existing buy flow, and strip the param so a refresh doesn't re-prompt.
  const [autoBuyHandled, setAutoBuyHandled] = useStateApp(false)
  useEffectApp(() => {
    if (autoBuyHandled || !dashData) return
    const params = new URLSearchParams(window.location.search)
    const setIdParam = params.get('buy')
    const pkgIdParam = params.get('buy_pkg')
    if (!setIdParam && !pkgIdParam) return
    setAutoBuyHandled(true)
    // Strip the param right away so revisits don't re-fire even on errors
    params.delete('buy'); params.delete('buy_pkg')
    const q = params.toString()
    window.history.replaceState(null, '', '/student-dashboard/' + (q ? '?' + q : ''))

    if (pkgIdParam) {
      const pkg = packages.find(p => p.id === pkgIdParam)
      if (pkg) buyPackage(pkg)
      else alert('ไม่พบแพ็กเกจที่เลือก หรือยังไม่เปิดขาย')
      return
    }
    if (setIdParam) {
      const raw = (dashData.available_sets || []).find(s => s.id === setIdParam)
      if (raw) buyCourse(setToCourse(raw, dashData?.stats?.accuracy_rate))
      else alert('ไม่พบชุดข้อสอบที่เลือก หรือยังไม่เปิดขาย')
    }
  }, [autoBuyHandled, dashData, packages])

  // ── Derived data ──────────────────────────────────────────────────────
  const stats        = dashData?.stats           || {}
  // Prefer real ExamSets when the catalog has any. Fall back to the legacy
  // (category, subject) aggregate so older deployments without sets still render.
  const courses      = dashData?.available_sets?.length > 0
    ? dashData.available_sets.map(s => setToCourse(s, stats.accuracy_rate))
    : (dashData?.available_exams?.length > 0
        ? dashData.available_exams.map(e => examToCourse(e, stats.accuracy_rate))
        : null)
  const progressData    = dashData?.score_history?.length  >= 2 ? dashData.score_history    : null
  const tagAccuracy     = dashData?.tag_accuracy?.length   >  0 ? dashData.tag_accuracy     : null
  const speedAccuracy   = dashData?.speed_accuracy?.length >  0 ? dashData.speed_accuracy   : null
  const forgettingCurve = dashData?.forgetting_curve?.length > 0 ? dashData.forgetting_curve : null
  const mistakes        = dashData?.recent_mistakes?.length >  0 ? dashData.recent_mistakes  : null

  const streak         = dashData?.streak || 0
  const joined         = dashData?.joined || "—"
  const totalSessions  = stats.total_sessions  || 0
  const accuracyRate   = stats.accuracy_rate   || 0
  const subPlan        = dashData?.subscription?.plan
  const levelLabel     = subPlan ? subPlan.charAt(0).toUpperCase() + subPlan.slice(1) : "Free"
  const dateLabel      = formatDate(new Date())

  if (view === "simulator") {
    return <SimulatorView course={activeCourse} avatarId={avatarId} onBack={() => setView("dashboard")} />
  }

  return (
    <div className="min-h-screen" style={{ background: "#F6F8FB", fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
      <div className="flex">
        {/* Sidebar */}
        <aside className="w-[240px] shrink-0 h-screen sticky top-0 px-4 py-6 border-r border-[#E6EAF1]/70 hidden lg:flex flex-col">
          <a href="/" target="_top" title="กลับหน้าแรก" className="px-3 mb-7 flex items-center gap-2.5 rounded-lg -mx-1 px-2 py-1 hover:bg-[#F6F8FB] transition">
            <div className="w-9 h-9 rounded-xl bg-[#0A6CFF] flex items-center justify-center">
              <GraduationCap size={18} className="text-white" strokeWidth={2.2} />
            </div>
            <div>
              <div className="text-[15px] font-bold text-[#0B1220] leading-none" style={{ letterSpacing: "-0.01em" }}>2TTEST</div>
              <div className="text-[10.5px] text-[#6B7588] mt-0.5 uppercase tracking-wider">Exam Practice</div>
            </div>
          </a>

          <nav className="flex flex-col gap-1">
            <SidebarItem icon={Home}               label="Dashboard"  active={section === "overview"} onClick={() => setSection("overview")} />
            <SidebarItem icon={Library}            label="My courses" active={section === "courses"}  onClick={() => setSection("courses")}  />
            <SidebarItem icon={ShoppingBag}        label="ชุดข้อสอบ"  active={section === "catalog"}  onClick={() => setSection("catalog")}  />
            <SidebarItem icon={BookOpen}           label="คำศัพท์"     active={section === "vocab"}    onClick={() => setSection("vocab")}    />
            <SidebarItem icon={BarChart3}          label="Progress"   active={section === "progress"} onClick={() => setSection("progress")} />
            <SidebarItem icon={MessageSquareHeart} label="Tutor chat" active={section === "tutor"}    onClick={() => setSection("tutor")}    />
            <SidebarItem icon={Settings}           label="Settings"   active={section === "settings"} onClick={() => setSection("settings")} />
          </nav>

          <div className="mt-auto rounded-3xl p-4 relative overflow-hidden" style={{ background: "linear-gradient(180deg, #EAF2FF 0%, #EFF6FF 100%)" }}>
            <div className="text-[12px] font-semibold text-[#0857d6] uppercase tracking-wider">Daily streak</div>
            <div className="text-[34px] mt-1 text-[#0B1220]" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight: 500, letterSpacing: "-0.02em" }}>
              {streak > 0 ? `${streak} days` : "Day 0"}
            </div>
            <p className="text-[12px] text-[#2B3445] mt-1 leading-snug inline-flex items-center gap-1">
              {streak > 0 ? "ทำข้อสอบวันนี้เพื่อรักษา streak!" : "ทำข้อสอบข้อแรกเพื่อเริ่ม streak!"}
              <FlameIcon size={13} color="#E8804A"/>
            </p>
          </div>
        </aside>

        {/* Main */}
        <main className="flex-1 min-w-0">
          <header className="px-8 lg:px-10 pt-7 pb-2 flex items-center justify-between gap-4">
            <div>
              <div className="text-[12px] uppercase tracking-[0.16em] text-[#6B7588] font-medium flex items-center gap-2">
                <span>{dateLabel}</span>
                {isImpersonated && (
                  <span title="กำลังทดลองในโหมด student (ไม่กระทบบัญชีจริง)"
                        className="inline-flex items-center gap-1 px-2 h-5 rounded-full"
                        style={{ background:"#FEF3C7", color:"#92400E", fontSize:10.5, fontWeight:700, letterSpacing:".06em" }}>
                    <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg>
                    SANDBOX · TEST MODE
                  </span>
                )}
              </div>
              <h1 className="mt-1 text-[34px] text-[#0B1220]" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight: 500, letterSpacing: "-0.02em" }}>
                Welcome back, {userName.split(" ")[0]} <span className="text-[#6B7588]">·</span> let's study.
              </h1>
            </div>
            <div className="flex items-center gap-2">
              <div className="relative">
                <button
                  onClick={() => setUserMenuOpen(v => !v)}
                  className="rounded-full focus:outline-none focus:ring-2 focus:ring-[#0A6CFF]/30"
                  title={userName + " — เมนูบัญชี"}>
                  <AvatarTile id={avatarId} size={44} ring ringClass="ring-2 ring-white" />
                </button>
                {userMenuOpen && (
                  <>
                    <div className="fixed inset-0 z-30" onClick={() => setUserMenuOpen(false)} />
                    <div className="absolute right-0 top-12 w-56 z-40 bg-white rounded-xl shadow-lg border border-[#E6EAF1] py-2"
                         style={{ boxShadow: "0 16px 40px -12px rgba(15,23,42,0.18)" }}>
                      <div className="px-4 py-2 border-b border-[#F6F8FB]">
                        <div className="text-[13.5px] font-semibold text-[#0B1220] truncate">{userName}</div>
                        <div className="text-[11px] text-[#6B7588] truncate">{userEmail}</div>
                      </div>
                      <button
                        onClick={() => { setPickerOpen(true); setUserMenuOpen(false); }}
                        className="w-full text-left px-4 py-2.5 text-[13.5px] text-[#2B3445] hover:bg-[#F6F8FB] flex items-center gap-2">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg>
                        เปลี่ยนอวตาร
                      </button>
                      <a
                        href="/landing/"
                        className="px-4 py-2.5 text-[13.5px] text-[#2B3445] hover:bg-[#F6F8FB] flex items-center gap-2">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m3 11 9-8 9 8"/><path d="M5 10v10h14V10"/></svg>
                        หน้าหลัก
                      </a>
                      <div className="border-t border-[#F6F8FB] my-1"></div>
                      <button
                        onClick={onLogout}
                        className="w-full text-left px-4 py-2.5 text-[13.5px] text-[#DC2626] hover:bg-[#FEE2E2] flex items-center gap-2 font-semibold">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
                        ออกจากระบบ
                      </button>
                    </div>
                  </>
                )}
              </div>
            </div>
          </header>

          <div className="px-8 lg:px-10 pb-12 pt-4">
            {(() => {
              // When the user picks a single section from the sidebar we collapse the
              // 7/5 split to full-width, so the right column doesn't sit empty.
              const isOverview = section === "overview";
              const colMain = isOverview ? "col-span-12 xl:col-span-7" : "col-span-12";
              const colSide = isOverview ? "col-span-12 xl:col-span-5" : "col-span-12";
              const showCoursesArea  = section === "overview" || section === "courses";
              const showProgressArea = section === "overview" || section === "progress";

              if (section === "vocab") {
                return <VocabSection />;
              }

              if (section === "tutor") {
                return (
                  <Card className="p-10 text-center" style={{ maxWidth: 720, margin: "32px auto" }}>
                    <div style={{ display:"inline-flex", padding:18, borderRadius:999, background:"#EAF2FF", color:"#0857d6", marginBottom:14 }}>
                      <MessageSquareHeart size={28} strokeWidth={2}/>
                    </div>
                    <h2 className="text-[24px] text-[#0B1220]" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight:500 }}>Tutor chat</h2>
                    <p className="text-[14px] text-[#2B3445] mt-2 mx-auto" style={{ maxWidth:420 }}>
                      ระบบแชทกับติวเตอร์กำลังพัฒนา — ระหว่างนี้ทักเราทาง LINE ได้เลย
                    </p>
                    <a href="/landing/#contact" className="inline-flex items-center gap-2 mt-5 h-11 px-6 rounded-full text-white text-[14px] font-semibold transition hover:opacity-90" style={{ background:"#0A6CFF" }}>
                      ติดต่อทีมงาน <ChevronRight size={15} strokeWidth={2.4}/>
                    </a>
                  </Card>
                );
              }

              if (section === "catalog") {
                const SUBJECT_TH = { thai: "ภาษาไทย", math: "คณิตศาสตร์", english: "ภาษาอังกฤษ", science: "วิทยาศาสตร์", social: "สังคมศึกษา" }
                const CATEGORY_TH = { m1: "ม.1 — เตรียมสอบเข้า ม.1", m4: "ม.4 — เตรียมสอบเข้า ม.4", onet: "O-NET", gpax: "กพ.", alevel: "A-Level", toeic: "TOEIC", toefl: "TOEFL", ielts: "IELTS" }
                // PREVIEW MOCK — localhost only, removed once a real Package exists on the backend.
                // Lets the team see how the Bundle will render before publishing the real Package on prod.
                const isLocal = typeof window !== "undefined" && /^(localhost|127\.0\.0\.1|0\.0\.0\.0)$/.test(window.location.hostname || "")
                const PREVIEW_BUNDLE = {
                  id: "preview-bundle-m1",
                  name: "ครบชุด ม.1 — เตรียมสอบเข้า (5 วิชา)",
                  description: "รวม 5 วิชาหลัก: ภาษาไทย · คณิตศาสตร์ · ภาษาอังกฤษ · วิทยาศาสตร์ · สังคมศึกษา",
                  price: 799,
                  access_days: 60,
                  set_count: 5,
                  total_individual_price: 995,
                  sets: [
                    { id: "p1", name: "ภาษาไทย ม.1 — ครบ 200 ข้อ (ป.6)", price: 199 },
                    { id: "p2", name: "คณิตศาสตร์ ม.1 — เตรียมสอบเข้า (ป.6)", price: 199 },
                    { id: "p3", name: "ภาษาอังกฤษ ม.1 — เตรียมสอบเข้า (ป.6)", price: 199 },
                    { id: "p4", name: "วิทยาศาสตร์ ม.1 — เตรียมสอบเข้า (ป.6)", price: 199 },
                    { id: "p5", name: "สังคมศึกษา ม.1 — เตรียมสอบเข้า (ป.6)", price: 199 },
                  ],
                  banner_urls: [],
                  __preview: true,
                }
                const visiblePackages = (isLocal && packages.length === 0) ? [PREVIEW_BUNDLE] : packages
                const allCourses = courses || []
                const availableCategories = Array.from(new Set(allCourses.map(c => c.category).filter(Boolean)))
                const availableSubjectsInCategory = Array.from(new Set(
                  allCourses.filter(c => catalogCategory === "all" || c.category === catalogCategory).map(c => c.subject).filter(Boolean)
                ))
                const filtered = allCourses.filter(c =>
                  (catalogCategory === "all" || c.category === catalogCategory) &&
                  (catalogSubject === "all" || c.subject === catalogSubject)
                )
                return (
                  <div className="px-1">
                    <div className="mb-1 text-[12px] uppercase tracking-[0.16em] text-[#6B7588] font-medium">Browse all sets</div>
                    <h2 className="text-[28px] text-[#0B1220] mb-5" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight:500, letterSpacing:"-0.02em" }}>ชุดข้อสอบทั้งหมด</h2>

                    {/* Featured Bundle card(s) — published packages shown big at top */}
                    {visiblePackages.length > 0 && (
                      <div className="mb-7 grid grid-cols-1 gap-4">
                        {visiblePackages.map((p) => {
                          const savings = Math.max(0, (p.total_individual_price || 0) - (p.price || 0))
                          const pct = p.total_individual_price > 0 ? Math.round((savings / p.total_individual_price) * 100) : 0
                          return (
                            <button key={p.id} onClick={() => p.__preview ? alert("Preview only — Bundle จริงยังไม่ได้สร้างบน prod") : buyPackage(p)}
                              className="text-left p-7 rounded-3xl transition group relative overflow-hidden hover:scale-[0.998]"
                              style={{ background:"linear-gradient(135deg, #0A6CFF 0%, #1E40AF 100%)", color:"white", boxShadow:"0 18px 40px -16px rgba(10,108,255,0.45)" }}>
                              <div className="absolute -right-8 -top-8 w-40 h-40 rounded-full opacity-15" style={{ background:"white" }}></div>
                              <div className="relative flex items-start justify-between gap-5 flex-wrap">
                                <div className="flex-1 min-w-0">
                                  <div className="flex items-center gap-2 flex-wrap">
                                    <div className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[11px] font-bold uppercase tracking-[0.12em]" style={{ background:"#FEF3C7", color:"#92400E" }}>
                                      <FlameIcon size={12} color="#92400E"/> ดีลคุ้มที่สุด · BUNDLE
                                    </div>
                                    {p.__preview && (
                                      <div className="inline-flex items-center gap-1 px-2 py-1 rounded-full text-[10.5px] font-bold uppercase tracking-[0.12em]" style={{ background:"#FEE2E2", color:"#DC2626" }}>
                                        Preview · ยังไม่ live
                                      </div>
                                    )}
                                  </div>
                                  <div className="mt-3 text-[24px] font-semibold leading-tight" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", letterSpacing:"-0.01em" }}>{p.name}</div>
                                  {p.description && <div className="text-[14px] opacity-85 mt-1.5">{p.description}</div>}
                                  <div className="mt-3 flex items-center gap-3 flex-wrap text-[12.5px] opacity-90">
                                    <span>📦 {p.set_count} ชุดในแพ็กเกจ</span>
                                    <span>·</span>
                                    <span style={{ display:"inline-flex", alignItems:"center", gap:4 }}><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>ใช้ได้ {p.access_days} วัน</span>
                                  </div>
                                  {(p.sets || []).length > 0 && (
                                    <div className="mt-2 text-[12px] opacity-80">รวม: {(p.sets || []).map(s => s.name.replace(/ — .*$/, "").replace(/ ม\.\d.*$/, "")).join(" · ")}</div>
                                  )}
                                </div>
                                <div className="text-right shrink-0">
                                  {savings > 0 && <div className="text-[12px] opacity-70 line-through">฿{(p.total_individual_price||0).toLocaleString()}</div>}
                                  <div className="text-[36px] font-bold" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", letterSpacing:"-0.02em" }}>฿{(p.price||0).toLocaleString()}</div>
                                  {savings > 0 && (
                                    <div className="inline-block mt-1 px-2 py-0.5 rounded-full text-[11px] font-bold" style={{ background:"#16A34A", color:"white" }}>
                                      ประหยัด ฿{savings.toLocaleString()} · {pct}% off
                                    </div>
                                  )}
                                  <div className="mt-3 inline-flex items-center gap-1.5 h-9 px-4 rounded-full text-[13px] font-semibold transition group-hover:opacity-90" style={{ background:"white", color:"#0A6CFF" }}>
                                    ซื้อ Bundle <ChevronRight size={14} strokeWidth={2.4}/>
                                  </div>
                                </div>
                              </div>
                            </button>
                          )
                        })}
                      </div>
                    )}

                    {/* Filter bar */}
                    <div className="mb-5 flex items-end gap-5 flex-wrap">
                      <div>
                        <label className="block text-[11.5px] uppercase tracking-[0.12em] text-[#6B7588] font-semibold mb-1.5">หมวดหมู่</label>
                        <div className="relative">
                          <select
                            value={catalogCategory}
                            onChange={(e) => { setCatalogCategory(e.target.value); setCatalogSubject("all"); }}
                            className="appearance-none h-11 pl-4 pr-10 rounded-xl text-[14px] font-medium text-[#0B1220] bg-white cursor-pointer focus:outline-none focus:ring-2 focus:ring-[#0A6CFF]/30"
                            style={{ boxShadow:"inset 0 0 0 1.5px rgba(15,23,42,0.08)", minWidth: 240 }}>
                            {availableCategories.length === 0 && <option value="m1">ม.1 — เตรียมสอบเข้า ม.1</option>}
                            {availableCategories.map(cat => (
                              <option key={cat} value={cat}>{CATEGORY_TH[cat] || cat.toUpperCase()}</option>
                            ))}
                          </select>
                          <svg className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#6B7588" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
                        </div>
                      </div>
                      <div className="flex-1 min-w-[260px]">
                        <label className="block text-[11.5px] uppercase tracking-[0.12em] text-[#6B7588] font-semibold mb-1.5">วิชา</label>
                        <div className="flex gap-2 flex-wrap">
                          {[["all", "ทั้งหมด"], ...availableSubjectsInCategory.map(s => [s, SUBJECT_TH[s] || s])].map(([key, label]) => {
                            const active = catalogSubject === key
                            return (
                              <button key={key}
                                onClick={() => setCatalogSubject(key)}
                                className={`h-9 px-4 rounded-full text-[13px] font-medium transition ${active ? "bg-[#0B1220] text-white" : "bg-white text-[#2B3445] hover:bg-[#F6F8FB]"}`}
                                style={!active ? { boxShadow:"inset 0 0 0 1.2px rgba(15,23,42,0.08)" } : {}}>
                                {label}
                              </button>
                            )
                          })}
                        </div>
                      </div>
                    </div>

                    {/* Result count */}
                    <div className="text-[12.5px] text-[#6B7588] mb-3">
                      พบ <span className="font-semibold text-[#0B1220]">{filtered.length}</span> ชุด
                      {catalogSubject !== "all" && <> · วิชา <span className="font-semibold text-[#0B1220]">{SUBJECT_TH[catalogSubject] || catalogSubject}</span></>}
                    </div>

                    {/* Cards grid */}
                    {filtered.length > 0 ? (
                      <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
                        {filtered.map(c => <CourseCard key={c.id} course={c} onOpen={openCourse} onBuy={buyCourse} />)}
                      </div>
                    ) : loading ? (
                      <div className="py-12 text-center text-[13px] text-[#8A93A6]">กำลังโหลด...</div>
                    ) : (
                      <Card className="p-10 text-center">
                        <div className="text-[#6B7588] text-[13.5px]">ยังไม่มีชุดข้อสอบในหมวดหมู่/วิชาที่เลือก</div>
                      </Card>
                    )}
                  </div>
                );
              }

              if (section === "settings") {
                return (
                  <div style={{ maxWidth: 720, margin: "0 auto" }}>
                    <Card className="p-7 mb-5">
                      <SectionHeader title="ข้อมูลบัญชี" sub="โปรไฟล์ที่ใช้ในการสอบ" />
                      <ProfileCard
                        avatarId={avatarId}
                        onChangeAvatar={() => setPickerOpen(true)}
                        name={userName}
                        email={userEmail || "—"}
                        provider={userData.provider === "facebook" ? "facebook" : "google"}
                        joined={joined}
                        level={levelLabel}
                        streak={streak}
                        totalSessions={totalSessions}
                        accuracyRate={accuracyRate}
                      />
                    </Card>
                    <Card className="p-6">
                      <SectionHeader title="ออกจากระบบ" sub="กลับไปหน้าแลนดิ้ง" />
                      <button onClick={onLogout}
                        className="mt-2 inline-flex items-center gap-2 h-11 px-5 rounded-full text-[14px] font-semibold transition"
                        style={{ background:"#FEE2E2", color:"#DC2626" }}>
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
                        ออกจากระบบ
                      </button>
                    </Card>
                  </div>
                );
              }

              return (
            <div className="grid grid-cols-12 gap-5">

              {/* Profile card */}
              <div className={colMain}>
                <ProfileCard
                  avatarId={avatarId}
                  onChangeAvatar={() => setPickerOpen(true)}
                  name={userName}
                  email={userEmail || "—"}
                  provider={userData.provider === "facebook" ? "facebook" : "google"}
                  joined={joined}
                  level={levelLabel}
                  streak={streak}
                  totalSessions={totalSessions}
                  accuracyRate={accuracyRate}
                />
              </div>

              {/* Progress chart */}
              {showProgressArea && (
              <div className={colSide}>
                {progressData ? (
                  <ProgressCard
                    data={progressData}
                    target={100}
                    current={progressData[progressData.length - 1].score}
                    scoreMin={0}
                    scoreMax={100}
                  />
                ) : (
                  <Card className="p-6">
                    <SectionHeader title="Learning progress" sub="Score history" />
                    <EmptyState text={loading ? "กำลังโหลด..." : "ทำข้อสอบเพื่อดูพัฒนาการของคุณ"} />
                  </Card>
                )}
              </div>
              )}

              {/* Courses */}
              {showCoursesArea && (
              <div className={colMain}>
                {/* Packages (bundles) — show above individual courses when any are published */}
                {packages.length > 0 && (
                  <div className="mb-6">
                    <SectionHeader
                      title="แพ็กเกจสุดคุ้ม"
                      sub={`${packages.length} แพ็กเกจ · ซื้อรวมราคาประหยัดกว่า`}
                    />
                    <div className="grid grid-cols-1 gap-3">
                      {packages.map((p) => {
                        const savings = Math.max(0, (p.total_individual_price || 0) - (p.price || 0))
                        return (
                          <button key={p.id}
                            onClick={() => buyPackage(p)}
                            className="text-left p-5 rounded-2xl bg-white hover:bg-[#F6F8FB] transition group"
                            style={{ boxShadow: "inset 0 0 0 1.5px rgba(15,23,42,0.06)" }}>
                            <div className="flex items-start justify-between gap-3">
                              <div className="flex-1 min-w-0">
                                <div className="text-[10.5px] uppercase tracking-[0.14em] text-[#92400E] font-semibold" style={{ background:"#FEF3C7", padding:"2px 8px", borderRadius:99, display:"inline-block" }}>
                                  Bundle · {p.set_count} ชุด
                                </div>
                                <div className="mt-2 text-[16px] font-semibold text-[#0B1220]">{p.name}</div>
                                {p.description && <div className="text-[13px] text-[#6B7588] mt-1">{p.description}</div>}
                                <div className="mt-2 text-[12px] text-[#8A93A6]">
                                  ใช้ได้ {p.access_days} วัน · {(p.sets || []).map(s => s.name).slice(0, 3).join(' · ')}{(p.sets||[]).length > 3 ? ' · ...' : ''}
                                </div>
                              </div>
                              <div className="text-right shrink-0">
                                {savings > 0 && <div className="text-[11px] text-[#8A93A6] line-through">฿{(p.total_individual_price||0).toLocaleString()}</div>}
                                <div className="text-[20px] font-bold text-[#0A6CFF]" style={{ fontFamily: "Plus Jakarta Sans, sans-serif" }}>฿{(p.price||0).toLocaleString()}</div>
                                {savings > 0 && <div className="text-[10.5px] font-semibold text-[#16A34A] mt-0.5">ประหยัด ฿{savings.toLocaleString()}</div>}
                              </div>
                            </div>
                          </button>
                        )
                      })}
                    </div>
                  </div>
                )}

                <SectionHeader
                  title="My courses & sets"
                  sub={courses ? `${courses.length} ชุดข้อสอบ · กดเพื่อเริ่มทำ` : ""}
                  action={
                    <a href="/landing/#courses" className="text-[12.5px] text-[#2B3445] hover:text-[#0B1220] flex items-center gap-1 transition">
                      Browse catalog <ChevronRight size={14} strokeWidth={2.2} />
                    </a>
                  }
                />
                {courses ? (
                  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                    {courses.map((c) => <CourseCard key={c.id} course={c} onOpen={openCourse} onBuy={buyCourse} />)}
                  </div>
                ) : loading ? (
                  <div className="py-8 text-center text-[13px] text-[#8A93A6]">กำลังโหลด...</div>
                ) : (
                  <Card className="p-8 text-center">
                    <div className="text-[#6B7588] text-[13.5px]">ยังไม่มีชุดข้อสอบ — กรุณาติดต่อทีมงาน</div>
                  </Card>
                )}
              </div>
              )}

              {/* Recent mistakes */}
              {showProgressArea && (
              <div className={colSide}>
                {mistakes ? (
                  <RecentMistakesCard mistakes={mistakes} onReview={reviewMistake} />
                ) : (
                  <Card className="p-6">
                    <SectionHeader title="Recent mistakes" sub="Review and master what slipped" />
                    <EmptyState text={loading ? "กำลังโหลด..." : "ยังไม่มีข้อผิดพลาด — เก่งมาก!"} />
                  </Card>
                )}
              </div>
              )}

              {/* Error pattern + Speed vs accuracy */}
              {showProgressArea && (
              <div className={colMain}>
                {tagAccuracy ? (
                  <ErrorPatternCard data={tagAccuracy} />
                ) : (
                  <Card className="p-6">
                    <SectionHeader title="Error pattern" sub="Accuracy by question type" />
                    <EmptyState text="ทำข้อสอบเพื่อดูรูปแบบข้อผิดพลาด" />
                  </Card>
                )}
              </div>
              )}
              {showProgressArea && (
              <div className={colSide}>
                {speedAccuracy ? (
                  <SpeedAccuracyCard data={speedAccuracy} />
                ) : (
                  <Card className="p-6">
                    <SectionHeader title="Speed vs. accuracy" sub="Know what to fix and how" />
                    <EmptyState text="ทำข้อสอบเพื่อดูข้อมูลความเร็ว" />
                  </Card>
                )}
              </div>
              )}

              {/* Forgetting curve + Consistency */}
              {showProgressArea && (
              <div className={colMain}>
                {forgettingCurve ? (
                  <ForgettingCurveCard data={forgettingCurve} />
                ) : (
                  <Card className="p-6">
                    <SectionHeader title="Forgetting curve" sub="Topics you knew — but may be losing" />
                    <EmptyState text="ทำข้อสอบเพื่อดู forgetting curve" />
                  </Card>
                )}
              </div>
              )}
              {showProgressArea && (
              <div className={colSide}>
                {progressData && progressData.length >= 2 ? (
                  <ConsistencyCard scores={progressData.map(d => d.score)} scoreMin={0} scoreMax={100} />
                ) : (
                  <Card className="p-6">
                    <SectionHeader title="Consistency" sub="Score stability across all tests" />
                    <EmptyState text="ต้องการอย่างน้อย 2 ครั้งเพื่อคำนวณ" />
                  </Card>
                )}
              </div>
              )}

              {/* Predicted score */}
              {showProgressArea && (
              <div className="col-span-12">
                {progressData && progressData.length >= 3 ? (
                  <PredictedScoreCard
                    data={progressData}
                    target={100}
                    current={progressData[progressData.length - 1].score}
                    scoreMin={0}
                    scoreMax={100}
                  />
                ) : (
                  <Card className="p-6">
                    <SectionHeader title="Predicted score" sub="AI projection based on your history" />
                    <EmptyState text="ต้องการอย่างน้อย 3 ครั้งเพื่อคำนวณพยากรณ์" />
                  </Card>
                )}
              </div>
              )}

            </div>
              );
            })()}
          </div>
        </main>
      </div>

      <AvatarPicker
        open={pickerOpen}
        currentId={avatarId}
        onSelect={setAvatarId}
        onClose={() => setPickerOpen(false)}
      />

      {payModal && (
        <PaymentModal
          item={payModal.item}
          type={payModal.type}
          finalPrice={payModal.finalPrice}
          promoCode={payModal.promoCode}
          onClose={() => setPayModal(null)}
          onSuccess={async () => {
            setPayModal(null)
            await refreshSets()
            alert('ชำระเงินสำเร็จ! คอร์สถูกเพิ่มเข้า My Courses แล้ว')
          }}
        />
      )}

      {popupVisible && !popupDismissed && !hasAvatar && (
        <div
          className="mascot-invite fixed bottom-6 right-6 z-40 flex items-end gap-3 rounded-[24px] p-5 shadow-2xl"
          style={{ background: "#FFFFFF", maxWidth: "280px", boxShadow: "0 20px 60px -16px rgba(15,23,42,0.22), 0 0 0 1px rgba(15,23,42,0.06)" }}
        >
          <div className="shrink-0">
            <svg viewBox="0 0 60 76" width="48" height="60" style={{ animation: "egg-bob 2.6s ease-in-out infinite" }}>
              <ellipse cx="30" cy="43" rx="21" ry="28" fill="#FFFDE8" />
              <ellipse cx="30" cy="43" rx="21" ry="28" fill="none" stroke="#EDE6B0" strokeWidth="2.2" />
              <ellipse cx="20" cy="27" rx="5" ry="7" fill="white" opacity="0.5" transform="rotate(-20 20 27)" />
              <path d="M 21 45 Q 25 41 29 45" stroke="#B8921E" strokeWidth="1.8" fill="none" strokeLinecap="round" />
              <path d="M 31 45 Q 35 41 39 45" stroke="#B8921E" strokeWidth="1.8" fill="none" strokeLinecap="round" />
              <circle cx="18" cy="52" r="4" fill="#FCA5A5" opacity="0.3" />
              <circle cx="42" cy="52" r="4" fill="#FCA5A5" opacity="0.3" />
              <text x="41" y="23" style={{ fontFamily: "system-ui", fontSize: "9px", fontWeight: 700, fill: "#C8A832", opacity: 0.8 }}>z</text>
            </svg>
          </div>
          <div className="flex-1 min-w-0">
            <p className="text-[13.5px] font-semibold text-[#0B1220] leading-snug">ยังไม่มีคู่หู!</p>
            <p className="text-[12px] text-[#6B7588] mt-0.5 leading-snug">Summon มาสคอทมาเป็นเพื่อนตลอดการสอบ</p>
            <button
              onClick={() => { setPopupDismissed(true); setPickerOpen(true) }}
              className="mt-3 flex items-center gap-1.5 h-8 px-3.5 rounded-full text-[12.5px] font-semibold text-white transition hover:opacity-90"
              style={{ background: "#0A6CFF" }}
            >
              <SparklesIcon size={12} strokeWidth={2.2} />
              Summon เลย!
            </button>
          </div>
          <button
            onClick={() => setPopupDismissed(true)}
            className="absolute top-3.5 right-3.5 w-7 h-7 rounded-full flex items-center justify-center text-[#8A93A6] hover:bg-[#F6F8FB] transition"
            aria-label="Dismiss"
          >
            <XIcon size={14} strokeWidth={2} />
          </button>
        </div>
      )}
    </div>
  )
}

/* ── Test Simulator ──────────────────────────────────────────────────────────
   Three phases driven by `phase` state:
     intro    — pre-flight info + start button
     running  — question runner with timer + navigator
     results  — score + per-question review
   The runner stores selections locally and submits each answer to /exam/answer
   only on Finish. This way the user can revise answers freely before they
   commit, matching real-exam UX. */
const SimulatorShell = ({ children, onBack, avatarId, badge }) => (
  <div className="min-h-screen" style={{ background: "#F6F8FB", fontFamily: "'Plus Jakarta Sans', 'Sarabun', sans-serif" }}>
    <header className="px-6 lg:px-10 pt-6 pb-4 flex items-center justify-between border-b border-[#E6EAF1] bg-white sticky top-0 z-20">
      <button onClick={onBack} className="th flex items-center gap-2 text-[13.5px] text-[#2B3445] hover:text-[#0B1220] transition">
        <ChevronLeft size={16} strokeWidth={2.2} /> กลับแดชบอร์ด
      </button>
      <div className="flex items-center gap-3">
        {badge && <span className="th text-[11px] uppercase tracking-wider text-[#6B7588] font-semibold">{badge}</span>}
        {avatarId && <AvatarTile id={avatarId} size={32} />}
      </div>
    </header>
    {children}
  </div>
)

const fmtSecs = (s) => {
  if (s == null || s < 0) s = 0
  const m = Math.floor(s / 60), r = s % 60
  return `${String(m).padStart(2,'0')}:${String(r).padStart(2,'0')}`
}

const SimulatorView = ({ course, avatarId, onBack }) => {
  const [phase,     setPhase]     = useStateApp('intro') // intro | running | results
  const [session,   setSession]   = useStateApp(null)    // { session_id, questions[], duration_minutes }
  const [picks,     setPicks]     = useStateApp({})      // { [question_id]: choice_id }
  const [results,   setResults]   = useStateApp(null)    // [{ question, picked, is_correct, correct, explanation }]
  const [idx,       setIdx]       = useStateApp(0)
  const [tLeft,     setTLeft]     = useStateApp(null)    // seconds remaining
  const [busy,      setBusy]      = useStateApp(false)
  const [error,     setError]     = useStateApp(null)
  const [confirmFinish, setConfirmFinish] = useStateApp(false)
  // Practice mode adds inline reveal after each pick. Exam mode is the default
  // and keeps the current "submit everything at the end" behavior.
  const [mode,        setMode]        = useStateApp('exam')   // 'exam' | 'practice'
  const [revealed,    setRevealed]    = useStateApp(new Set())  // Set<question_id> with answer revealed
  const [revealData,  setRevealData]  = useStateApp({})         // { [q.id]: { is_correct, correct, explanation } }
  // Layout density — paper-like: 1 q/page on phones, 3 q/page on tablet+desktop.
  // Auto-flips when the viewport crosses 768px so a tilted iPad reflows cleanly.
  const [perPage,     setPerPage]     = useStateApp(() => (typeof window !== 'undefined' && window.matchMedia('(min-width: 768px)').matches) ? 3 : 1)
  useEffectApp(() => {
    if (typeof window === 'undefined' || !window.matchMedia) return
    const mq = window.matchMedia('(min-width: 768px)')
    const onChange = (e) => setPerPage(e.matches ? 3 : 1)
    mq.addEventListener ? mq.addEventListener('change', onChange) : mq.addListener(onChange)
    return () => { mq.removeEventListener ? mq.removeEventListener('change', onChange) : mq.removeListener(onChange) }
  }, [])

  // Countdown timer — runs only during 'running'
  useEffectApp(() => {
    if (phase !== 'running' || tLeft == null) return
    if (tLeft <= 0) { void doFinish(true); return }
    const t = setTimeout(() => setTLeft(s => (s == null ? null : s - 1)), 1000)
    return () => clearTimeout(t)
  }, [phase, tLeft])

  const startExam = async () => {
    if (busy) return
    setBusy(true); setError(null)
    try {
      const r = await apiFetch('/exam/start', { method:'POST', body: { set_id: course.id, num_questions: course.questionsPerRound || course.lessons || 20, duration_minutes: 60 } })
      const data = await r.json()
      if (!r.ok) { setError(data.detail || 'เริ่มข้อสอบไม่สำเร็จ'); return }
      setSession(data); setIdx(0); setPicks({})
      setTLeft((data.duration_minutes || 60) * 60)
      setPhase('running')
    } catch (e) { setError('Network error: ' + e.message) }
    finally { setBusy(false) }
  }

  const doFinish = async (timeUp = false) => {
    if (busy || !session) return
    setBusy(true); setError(null)
    try {
      // Practice mode already submitted each answer at pick time — build results
      // from the cached reveal data without re-hitting /exam/answer. Any unanswered
      // questions still need a submit so the backend records them as wrong/blank.
      const out = []
      for (const q of session.questions) {
        const picked = picks[q.id] || ''
        const cached = revealData[q.id]
        if (mode === 'practice' && cached) {
          out.push({ question: q, picked, is_correct: !!cached.is_correct, correct: cached.correct, explanation: cached.explanation || '' })
          continue
        }
        const r = await apiFetch('/exam/answer', { method:'POST', body: { session_id: session.session_id, question_id: q.id, selected: picked, time_spent_seconds: 0 } })
        const data = await r.json()
        if (!r.ok) { setError(data.detail || 'ส่งคำตอบไม่สำเร็จ'); setBusy(false); return }
        out.push({ question: q, picked, is_correct: !!data.is_correct, correct: data.correct, explanation: data.explanation || '' })
      }
      setResults(out); setPhase('results'); setConfirmFinish(false)
      if (timeUp) setError('หมดเวลา — ส่งคำตอบอัตโนมัติ')
    } catch (e) { setError('Network error: ' + e.message) }
    finally { setBusy(false) }
  }

  // ── Intro ────────────────────────────────────────────────────────────
  if (phase === 'intro') {
    const rounds = course?.roundsLeft ?? course?.roundsTotal ?? '—'
    const total  = course?.roundsTotal ?? '—'
    return (
      <SimulatorShell onBack={onBack} avatarId={avatarId} badge="Pre-flight">
        <div className="px-6 lg:px-10 py-10 max-w-3xl mx-auto">
          <div className="th text-[11px] uppercase tracking-[0.16em] text-[#6B7588] font-medium">{course?.level || '—'}</div>
          <h1 className="th mt-2 text-[36px] text-[#0B1220] leading-tight" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight: 500, letterSpacing: "-0.02em" }}>
            {course?.title || 'Mock Test'}
          </h1>
          {course?.description && <p className="th mt-3 text-[14.5px] text-[#2B3445] max-w-xl">{course.description}</p>}

          <div className="mt-8 grid grid-cols-2 md:grid-cols-4 gap-3">
            <Card className="p-4">
              <div className="th text-[11px] text-[#6B7588] uppercase tracking-wider">จำนวนข้อ</div>
              <div className="text-[22px] text-[#0B1220] mt-1" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight:500 }}>{course?.questionsPerRound || course?.lessons || '—'}</div>
            </Card>
            <Card className="p-4">
              <div className="th text-[11px] text-[#6B7588] uppercase tracking-wider">เวลา</div>
              <div className="text-[22px] text-[#0B1220] mt-1" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight:500 }}>60 <span className="th text-[13px] text-[#6B7588]">นาที</span></div>
            </Card>
            <Card className="p-4">
              <div className="th text-[11px] text-[#6B7588] uppercase tracking-wider">รอบที่เหลือ</div>
              <div className="text-[22px] text-[#0B1220] mt-1" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight:500 }}>{rounds}<span className="th text-[13px] text-[#6B7588]"> / {total}</span></div>
            </Card>
            <Card className="p-4">
              <div className="th text-[11px] text-[#6B7588] uppercase tracking-wider">ใช้ได้อีก</div>
              <div className="text-[22px] text-[#0B1220] mt-1" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight:500 }}>{course?.daysRemaining ?? course?.accessDays ?? '—'}<span className="th text-[13px] text-[#6B7588]"> วัน</span></div>
            </Card>
          </div>

          {/* Mode selector — pick before starting. Default = Exam (closer to real test). */}
          <div className="mt-6">
            <div className="th text-[11.5px] uppercase tracking-[0.14em] text-[#6B7588] font-semibold mb-2">โหมดการทำข้อสอบ</div>
            <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
              {[
                { key: 'exam',     title: 'โหมดสอบจริง',  sub: 'ไม่บอกเฉลยระหว่างทำ ดูสรุปทีเดียวตอนจบ — เหมาะก่อนสอบจริง', tone: '#0A6CFF' },
                { key: 'practice', title: 'โหมดฝึกฝน',    sub: 'เห็นเฉลย + คำอธิบายทันทีหลังตอบแต่ละข้อ — เหมาะตอนเรียน',  tone: '#16A34A' },
              ].map(opt => {
                const active = mode === opt.key
                return (
                  <button key={opt.key} onClick={() => setMode(opt.key)}
                    className={`th text-left p-4 rounded-2xl transition ${active ? 'bg-white' : 'bg-white hover:bg-[#F6F8FB]'}`}
                    style={{ boxShadow: active ? `inset 0 0 0 2px ${opt.tone}` : 'inset 0 0 0 1.5px rgba(15,23,42,0.08)' }}>
                    <div className="flex items-start gap-3">
                      <span className="shrink-0 w-5 h-5 rounded-full grid place-items-center mt-[2px]" style={{ background: active ? opt.tone : 'transparent', border: active ? 'none' : '2px solid #cfd6e3' }}>
                        {active && <span className="w-2 h-2 rounded-full bg-white"/>}
                      </span>
                      <div className="flex-1 min-w-0">
                        <div className="text-[14.5px] font-semibold" style={{ color: active ? opt.tone : '#0B1220' }}>{opt.title}</div>
                        <div className="text-[12.5px] text-[#6B7588] mt-1 leading-snug">{opt.sub}</div>
                      </div>
                    </div>
                  </button>
                )
              })}
            </div>
          </div>

          <Card className="p-6 mt-5">
            <div className="th text-[12.5px] font-semibold text-[#0B1220] mb-2">ก่อนเริ่ม</div>
            <ul className="th text-[13.5px] text-[#2B3445] space-y-1.5 list-disc pl-5">
              <li>เริ่มทำแล้วเวลาจะนับถอยหลัง — กดเริ่มเมื่อพร้อมเท่านั้น</li>
              <li>การกด "เริ่ม" 1 ครั้ง = ใช้ 1 รอบ จากโควต้าของชุดนี้</li>
              {mode === 'exam'
                ? <li>ระหว่างทำสามารถเลื่อนกลับไปแก้คำตอบเดิมได้ — เฉลยจะแสดงตอนจบ</li>
                : <li>โหมดฝึกฝน — ตอบแล้วจะเห็นเฉลยทันที ไม่สามารถเปลี่ยนคำตอบของข้อนั้น</li>
              }
              <li>คะแนนจะถูกบันทึกทั้ง 2 โหมด เพื่อ track ความก้าวหน้า</li>
            </ul>
          </Card>

          {error && <div className="th mt-4 p-3 rounded-xl text-[13px]" style={{ background:"#FEE2E2", color:"#991B1B" }}>{error}</div>}

          <div className="mt-7 flex items-center gap-3">
            <button onClick={startExam} disabled={busy}
              className="th h-12 px-7 rounded-full text-white text-[15px] font-semibold transition hover:opacity-90 disabled:opacity-50"
              style={{ background:"#0A6CFF" }}>
              {busy ? 'กำลังเตรียม...' : 'เริ่มทำข้อสอบ'}
            </button>
            <button onClick={onBack}
              className="th h-12 px-5 text-[14px] text-[#2B3445] hover:text-[#0B1220] transition">
              ยกเลิก
            </button>
          </div>
        </div>
      </SimulatorShell>
    )
  }

  // ── Running ──────────────────────────────────────────────────────────
  if (phase === 'running' && session) {
    const totalQ        = session.questions.length
    const answeredCount = Object.keys(picks).filter(k => picks[k]).length
    // Pagination — `idx` keeps tracking the "anchor" question for backward
    // compat (navigator click still calls setIdx(i)); the page is derived from it.
    const pageStart = Math.min(totalQ - 1, Math.max(0, Math.floor(idx / perPage) * perPage))
    const pageEnd   = Math.min(totalQ, pageStart + perPage)
    const pageQs    = session.questions.slice(pageStart, pageEnd)
    const pageNum   = Math.floor(pageStart / perPage) + 1
    const totalPages = Math.max(1, Math.ceil(totalQ / perPage))
    const isLastPage = pageEnd >= totalQ
    const onPick = async (question, cid) => {
      if (revealed.has(question.id)) return  // practice mode locks answer once revealed
      setPicks(p => ({ ...p, [question.id]: cid }))
      if (mode === 'practice') {
        try {
          const r = await apiFetch('/exam/answer', { method:'POST', body: { session_id: session.session_id, question_id: question.id, selected: cid, time_spent_seconds: 0 } })
          const data = await r.json()
          if (r.ok) {
            setRevealData(rd => ({ ...rd, [question.id]: { is_correct: !!data.is_correct, correct: data.correct, explanation: data.explanation || '' } }))
            setRevealed(prev => { const s = new Set(prev); s.add(question.id); return s })
          }
        } catch {}
      }
    }
    const goPrev = () => setIdx(Math.max(0, pageStart - perPage))
    const goNext = () => setIdx(Math.min(totalQ - 1, pageStart + perPage))

    return (
      <SimulatorShell onBack={() => { if (confirm('ออกจากข้อสอบ? คำตอบที่เลือกไว้จะถูกล้าง')) onBack() }} avatarId={avatarId}>
        <div className="px-6 lg:px-10 py-6 max-w-6xl mx-auto">
          {/* Top status bar */}
          <div className="flex items-center justify-between mb-4 flex-wrap gap-3">
            <div className="flex items-center gap-3">
              <span className="th text-[12px] text-[#6B7588] uppercase tracking-wider font-medium">{session.set_name || course?.title}</span>
              <span className="text-[#cfd6e3]">·</span>
              <span className="th text-[12.5px] text-[#0B1220] font-semibold">
                {pageEnd > pageStart + 1
                  ? <>ข้อ {pageStart + 1}–{pageEnd} / {totalQ}</>
                  : <>ข้อ {pageStart + 1} / {totalQ}</>}
                {totalPages > 1 && <span className="ml-2 text-[#8A93A6] font-normal">· หน้า {pageNum}/{totalPages}</span>}
              </span>
            </div>
            <div className="flex items-center gap-2">
              <span className="th text-[12px] text-[#6B7588]">ตอบแล้ว <b className="text-[#0B1220]">{answeredCount}</b> / {totalQ}</span>
              <span className="text-[#cfd6e3]">·</span>
              <span className={`text-[14px] font-bold tabular-nums ${tLeft != null && tLeft < 60 ? "text-[#DC2626]" : "text-[#0B1220]"}`}>
                {fmtSecs(tLeft)}
              </span>
            </div>
          </div>

          <div className="grid grid-cols-12 gap-5">
            {/* Question pane — N questions per page (1 on mobile, 3 on tablet+) */}
            <div className="col-span-12 lg:col-span-8 flex flex-col gap-4">
              {pageQs.map((q, pageQIdx) => {
                const globalIdx = pageStart + pageQIdx
                const choices   = Array.isArray(q?.choices) ? q.choices : []
                const picked    = picks[q?.id]
                const qRevealed = revealed.has(q?.id)
                const qReveal   = revealData[q?.id]
                return (
                  <Card key={q.id} className="p-7">
                    <div className="flex items-center gap-2 mb-2">
                      <span className="th inline-flex items-center justify-center min-w-7 h-7 px-2 rounded-full text-[13px] font-bold" style={{ background:"#0B1220", color:"white" }}>{globalIdx + 1}</span>
                      {q?.tag && <div className="th inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-[10.5px] font-semibold uppercase tracking-wider" style={{ background:"#EAF2FF", color:"#0857d6" }}>{q.tag}</div>}
                    </div>
                    <div className="text-[15.5px] leading-[1.65] text-[#0B1220] whitespace-pre-wrap">{q?.stem || '—'}</div>
                    {q?.image_url && <img src={q.image_url} alt="" className="mt-4 max-w-full rounded-xl" style={{ maxHeight: 320 }}/>}
                    {q?.audio_url && <audio src={q.audio_url} controls className="mt-4 w-full"/>}

                    <div className="mt-5 flex flex-col gap-2.5">
                      {choices.map((c, i) => {
                        const cid   = c.id || c.key || String.fromCharCode(65 + i)
                        const text  = c.text ?? c.label ?? c.value ?? String(c)
                        const sel   = picked === cid
                        const isCorrect   = qRevealed && qReveal && cid === qReveal.correct
                        const isWrongPick = qRevealed && qReveal && sel && cid !== qReveal.correct
                        let cls = 'border-[#E6EAF1] bg-white hover:border-[#8A93A6]'
                        let badgeCls = 'bg-[#F6F8FB] text-[#2B3445]'
                        if (sel && !qRevealed) { cls = 'border-[#0A6CFF] bg-[#EFF6FF]'; badgeCls = 'bg-[#0A6CFF] text-white' }
                        if (isCorrect)         { cls = 'border-[#16A34A] bg-[#DCFCE7]'; badgeCls = 'bg-[#16A34A] text-white' }
                        if (isWrongPick)       { cls = 'border-[#DC2626] bg-[#FEE2E2]'; badgeCls = 'bg-[#DC2626] text-white' }
                        return (
                          <button key={cid} onClick={() => onPick(q, cid)} disabled={qRevealed}
                            className={`th text-left w-full p-4 rounded-2xl border transition disabled:cursor-default ${cls}`}>
                            <div className="flex items-start gap-3">
                              <span className={`shrink-0 w-7 h-7 rounded-full flex items-center justify-center text-[12.5px] font-bold ${badgeCls}`}>{cid}</span>
                              <span className="text-[14.5px] text-[#0B1220] leading-snug flex-1">{text}</span>
                              {isCorrect   && <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#16A34A" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" className="shrink-0 mt-0.5"><polyline points="20 6 9 17 4 12"/></svg>}
                              {isWrongPick && <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#DC2626" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" className="shrink-0 mt-0.5"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>}
                            </div>
                          </button>
                        )
                      })}
                    </div>

                    {/* Inline practice-mode feedback — only this question's row is affected */}
                    {qRevealed && qReveal && (
                      <div className="mt-5 p-5 rounded-2xl border" style={{ background: qReveal.is_correct ? '#F0FDF4' : '#FEF2F2', borderColor: qReveal.is_correct ? '#86EFAC' : '#FCA5A5' }}>
                        <div className="flex items-start gap-3">
                          <div className="shrink-0 w-9 h-9 rounded-full grid place-items-center" style={{ background: qReveal.is_correct ? '#16A34A' : '#DC2626', color: 'white' }}>
                            {qReveal.is_correct
                              ? <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
                              : <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>}
                          </div>
                          <div className="flex-1 min-w-0">
                            <div className="th text-[14.5px] font-bold" style={{ color: qReveal.is_correct ? '#15803D' : '#B91C1C' }}>
                              {qReveal.is_correct ? 'ตอบถูก!' : <>ไม่ถูก — เฉลย <span className="font-bold">{qReveal.correct || '?'}</span></>}
                            </div>
                            {qReveal.explanation && (
                              <div className="th text-[13.5px] text-[#2B3445] mt-1.5 leading-relaxed whitespace-pre-wrap">{qReveal.explanation}</div>
                            )}
                          </div>
                        </div>
                      </div>
                    )}
                  </Card>
                )
              })}

              <div className="mt-2 flex items-center justify-between gap-3">
                <button onClick={goPrev} disabled={pageStart === 0}
                  className="th inline-flex items-center gap-2 h-11 px-5 rounded-full bg-white border border-[#E6EAF1] text-[14px] text-[#2B3445] hover:bg-[#F6F8FB] disabled:opacity-40 transition">
                  <ChevronLeft size={15} strokeWidth={2.2}/> ก่อนหน้า
                </button>
                {!isLastPage ? (
                  <button onClick={goNext}
                    className="th inline-flex items-center gap-2 h-11 px-5 rounded-full text-white text-[14px] font-semibold transition hover:opacity-90"
                    style={{ background:"#0A6CFF" }}>
                    ถัดไป <ChevronRight size={15} strokeWidth={2.2}/>
                  </button>
                ) : (
                  <button onClick={() => setConfirmFinish(true)}
                    className="th inline-flex items-center gap-2 h-11 px-6 rounded-full text-white text-[14px] font-semibold transition hover:opacity-90"
                    style={{ background:"#16A34A" }}>
                    ส่งข้อสอบ
                  </button>
                )}
              </div>
            </div>

            {/* Navigator */}
            <div className="col-span-12 lg:col-span-4">
              <Card className="p-5 sticky top-24">
                <div className="th text-[12px] text-[#6B7588] uppercase tracking-wider font-semibold mb-3">รายการข้อ</div>
                <div className="grid grid-cols-6 gap-1.5">
                  {session.questions.map((qq, i) => {
                    const answered = !!picks[qq.id]
                    const here     = i >= pageStart && i < pageEnd
                    return (
                      <button key={qq.id} onClick={() => setIdx(i)}
                        className={`th h-9 rounded-lg text-[12.5px] font-semibold transition ${
                          here ? "bg-[#0B1220] text-white" :
                          answered ? "bg-[#EAF2FF] text-[#0857d6]" :
                          "bg-[#F6F8FB] text-[#2B3445] hover:bg-[#E6EAF1]"
                        }`}>{i + 1}</button>
                    )
                  })}
                </div>
                <div className="th mt-4 text-[11.5px] text-[#6B7588] flex flex-wrap gap-x-3 gap-y-1">
                  <span><span className="inline-block w-2 h-2 rounded-full bg-[#EAF2FF] mr-1"/>ตอบแล้ว</span>
                  <span><span className="inline-block w-2 h-2 rounded-full bg-[#F6F8FB] mr-1"/>ยังไม่ตอบ</span>
                  <span><span className="inline-block w-2 h-2 rounded-full bg-[#0B1220] mr-1"/>กำลังดู</span>
                </div>
              </Card>
            </div>
          </div>

          {/* Confirm finish modal */}
          {confirmFinish && (
            <div className="fixed inset-0 z-40 flex items-center justify-center p-4" style={{ background:"rgba(15,23,42,0.5)" }}
                 onClick={() => !busy && setConfirmFinish(false)}>
              <div className="bg-white rounded-3xl p-7 max-w-sm w-full" onClick={e => e.stopPropagation()}>
                <h3 className="th text-[18px] font-bold text-[#0B1220]">ส่งข้อสอบเลย?</h3>
                <p className="th text-[13.5px] text-[#2B3445] mt-2">
                  ตอบแล้ว <b>{answeredCount}</b> / {totalQ} ข้อ{answeredCount < totalQ && ` — เหลืออีก ${totalQ - answeredCount} ข้อที่ยังไม่ได้ตอบ`}
                </p>
                {error && <div className="th mt-3 p-2.5 rounded-lg text-[12.5px]" style={{ background:"#FEE2E2", color:"#991B1B" }}>{error}</div>}
                <div className="mt-5 flex items-center gap-2 justify-end">
                  <button onClick={() => setConfirmFinish(false)} disabled={busy}
                    className="th h-10 px-4 rounded-full text-[13.5px] text-[#2B3445] hover:bg-[#F6F8FB] transition">ยกเลิก</button>
                  <button onClick={() => doFinish(false)} disabled={busy}
                    className="th h-10 px-5 rounded-full text-white text-[13.5px] font-semibold transition hover:opacity-90 disabled:opacity-50"
                    style={{ background:"#16A34A" }}>
                    {busy ? 'กำลังส่ง...' : 'ส่งเลย'}
                  </button>
                </div>
              </div>
            </div>
          )}
        </div>
      </SimulatorShell>
    )
  }

  // ── Results ──────────────────────────────────────────────────────────
  if (phase === 'results' && results) {
    const correctCount = results.filter(r => r.is_correct).length
    const total = results.length
    const pct = total > 0 ? Math.round((correctCount / total) * 100) : 0
    // Tag breakdown
    const byTag = {}
    results.forEach(r => {
      const t = r.question.tag || 'ไม่ระบุ'
      byTag[t] = byTag[t] || { correct: 0, total: 0 }
      byTag[t].total += 1
      if (r.is_correct) byTag[t].correct += 1
    })
    const tagRows = Object.entries(byTag).sort((a,b) => b[1].total - a[1].total)

    return (
      <SimulatorShell onBack={onBack} avatarId={avatarId} badge="Results">
        <div className="px-6 lg:px-10 py-8 max-w-4xl mx-auto">
          <div className="th text-[11px] uppercase tracking-[0.16em] text-[#6B7588] font-medium">สรุปผล</div>
          <h1 className="th mt-2 text-[34px] text-[#0B1220] leading-tight" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight:500, letterSpacing:"-0.02em" }}>
            {pct >= 80 ? 'เก่งมาก!' : pct >= 60 ? 'ทำได้ดี' : pct >= 40 ? 'พอใช้ได้' : 'ลองอีกรอบนะ'}
          </h1>

          <div className="mt-6 grid grid-cols-2 md:grid-cols-4 gap-3">
            <Card className="p-4">
              <div className="th text-[11px] text-[#6B7588] uppercase tracking-wider">คะแนน</div>
              <div className="text-[28px] text-[#0B1220] mt-1" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight:500 }}>{correctCount}<span className="text-[16px] text-[#6B7588]"> / {total}</span></div>
            </Card>
            <Card className="p-4">
              <div className="th text-[11px] text-[#6B7588] uppercase tracking-wider">ความถูกต้อง</div>
              <div className="text-[28px] text-[#0A6CFF] mt-1" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight:500 }}>{pct}%</div>
            </Card>
            <Card className="p-4">
              <div className="th text-[11px] text-[#6B7588] uppercase tracking-wider">ข้อที่ตอบถูก</div>
              <div className="text-[28px] mt-1" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight:500, color:"#16A34A" }}>{correctCount}</div>
            </Card>
            <Card className="p-4">
              <div className="th text-[11px] text-[#6B7588] uppercase tracking-wider">ข้อที่ตอบผิด</div>
              <div className="text-[28px] mt-1" style={{ fontFamily: "Plus Jakarta Sans, sans-serif", fontWeight:500, color:"#DC2626" }}>{total - correctCount}</div>
            </Card>
          </div>

          {tagRows.length > 0 && (
            <Card className="p-6 mt-5">
              <div className="th text-[14px] font-semibold text-[#0B1220] mb-3">แยกตามหมวด</div>
              <div className="space-y-2.5">
                {tagRows.map(([tag, v]) => {
                  const p = Math.round((v.correct / v.total) * 100)
                  return (
                    <div key={tag}>
                      <div className="flex items-center justify-between mb-1">
                        <span className="th text-[13px] text-[#0B1220] font-medium">{tag}</span>
                        <span className="th text-[12px] text-[#6B7588] tabular-nums">{v.correct}/{v.total} · {p}%</span>
                      </div>
                      <div className="h-2 rounded-full bg-[#F6F8FB] overflow-hidden">
                        <div className="h-full rounded-full" style={{ width: p + '%', background: p >= 70 ? '#16A34A' : p >= 40 ? '#F59E0B' : '#DC2626' }}/>
                      </div>
                    </div>
                  )
                })}
              </div>
            </Card>
          )}

          {/* Per-question review */}
          <div className="mt-6">
            <div className="th text-[14px] font-semibold text-[#0B1220] mb-3">ทบทวนรายข้อ</div>
            <div className="space-y-3">
              {results.map((r, i) => {
                const choices = Array.isArray(r.question.choices) ? r.question.choices : []
                return (
                  <Card key={r.question.id} className="p-5">
                    <div className="flex items-start gap-3">
                      <span className={`shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-[12px] font-bold ${r.is_correct ? 'bg-[#DCFCE7] text-[#16A34A]' : 'bg-[#FEE2E2] text-[#DC2626]'}`}>
                        {r.is_correct ? '✓' : '✗'}
                      </span>
                      <div className="flex-1 min-w-0">
                        <div className="th text-[11.5px] text-[#6B7588] uppercase tracking-wider">ข้อ {i + 1}{r.question.tag ? ` · ${r.question.tag}` : ''}</div>
                        <div className="th mt-1 text-[14px] text-[#0B1220] whitespace-pre-wrap">{r.question.stem}</div>
                        <div className="mt-3 grid gap-1.5">
                          {choices.map((c, ci) => {
                            const cid  = c.id || c.key || String.fromCharCode(65 + ci)
                            const text = c.text ?? c.label ?? c.value ?? String(c)
                            const isPicked  = r.picked === cid
                            const isCorrect = r.correct === cid
                            const cls = isCorrect ? 'bg-[#DCFCE7] border-[#16A34A]' :
                                        isPicked  ? 'bg-[#FEE2E2] border-[#DC2626]' :
                                                    'bg-white border-[#E6EAF1]'
                            return (
                              <div key={cid} className={`th p-2.5 rounded-xl border text-[13.5px] flex items-center gap-2 ${cls}`}>
                                <span className="shrink-0 w-6 h-6 rounded-full bg-white flex items-center justify-center text-[11px] font-bold text-[#2B3445]">{cid}</span>
                                <span className="text-[#0B1220]">{text}</span>
                                {isCorrect && <span className="ml-auto th text-[11px] font-semibold text-[#16A34A]">เฉลย</span>}
                                {isPicked && !isCorrect && <span className="ml-auto th text-[11px] font-semibold text-[#DC2626]">คุณตอบ</span>}
                              </div>
                            )
                          })}
                        </div>
                        {r.explanation && (
                          <div className="mt-3 p-3 rounded-xl text-[13px] th leading-relaxed" style={{ background:"#F6F8FB", color:"#2B3445" }}>
                            <b className="text-[#0B1220]">เฉลย: </b>{r.explanation}
                          </div>
                        )}
                      </div>
                    </div>
                  </Card>
                )
              })}
            </div>
          </div>

          <div className="mt-7 flex items-center gap-3">
            <button onClick={onBack}
              className="th h-12 px-7 rounded-full text-white text-[15px] font-semibold transition hover:opacity-90"
              style={{ background:"#0A6CFF" }}>
              กลับแดชบอร์ด
            </button>
          </div>
        </div>
      </SimulatorShell>
    )
  }

  // Fallback (shouldn't normally hit) — back to dashboard
  return <SimulatorShell onBack={onBack} avatarId={avatarId}><div className="p-10 th text-[14px] text-[#2B3445]">กำลังเตรียม...</div></SimulatorShell>
}

/* ── Vocab section (flashcards) ─────────────────────────────────────────── */
const STRIPE_BG = {
  "stripe-blue":    { bg:"#EAF2FF", img:"repeating-linear-gradient(135deg, rgba(10,108,255,0.10) 0 8px, transparent 8px 18px)" },
  "stripe-violet":  { bg:"#EEE7FF", img:"repeating-linear-gradient(135deg, rgba(91,33,182,0.10) 0 8px, transparent 8px 18px)" },
  "stripe-emerald": { bg:"#DCFCE7", img:"repeating-linear-gradient(135deg, rgba(6,95,70,0.10) 0 8px, transparent 8px 18px)" },
  "stripe-rose":    { bg:"#FFE4E6", img:"repeating-linear-gradient(135deg, rgba(159,18,57,0.10) 0 8px, transparent 8px 18px)" },
  "stripe-yellow":  { bg:"#FEF3C7", img:"repeating-linear-gradient(135deg, rgba(146,64,14,0.10) 0 8px, transparent 8px 18px)" },
}
const STRIPE_FG = { "var(--brand-700)":"#0857d6", "var(--violet-fg)":"#5B21B6", "var(--emerald-fg)":"#065F46", "var(--rose-fg)":"#9F1239", "var(--warm-fg)":"#92400E" }

function VocabSection() {
  const [view, setView] = useStateApp("decks") // "decks" | "study" | "queue"
  const [decks, setDecks] = useStateApp([])
  const [loading, setLoading] = useStateApp(true)
  const [err, setErr] = useStateApp(null)
  const [activeDeck, setActiveDeck] = useStateApp(null)

  const loadDecks = async () => {
    setLoading(true); setErr(null)
    try {
      const r = await apiFetch("/vocab/decks")
      if (!r.ok) throw new Error("โหลด deck ไม่สำเร็จ (" + r.status + ")")
      setDecks(await r.json())
    } catch (e) { setErr(e.message) }
    finally { setLoading(false) }
  }
  useEffectApp(() => { loadDecks() }, [])

  const totalReview = decks.reduce((s, d) => s + (d.in_review || 0), 0)

  if (view === "queue") {
    return <VocabStudy source={{ kind: "queue" }} onExit={() => { setView("decks"); loadDecks() }} />
  }
  if (view === "study" && activeDeck) {
    return <VocabStudy source={{ kind: "deck", deckMeta: activeDeck }} onExit={() => { setView("decks"); setActiveDeck(null); loadDecks() }} />
  }

  return (
    <div className="anim-in" style={{ animation: "popup-slide-up 0.28s cubic-bezier(.2,.8,.2,1) both" }}>
      <div className="mb-7 flex items-end justify-between gap-4 flex-wrap">
        <div>
          <div className="th text-[11px] uppercase tracking-[0.12em] font-bold mb-1.5" style={{ color:"#0A6CFF" }}>Vocabulary</div>
          <h1 className="th text-[32px] font-extrabold text-[#0B1220]" style={{ letterSpacing:"-0.02em" }}>คำศัพท์ภาษาอังกฤษ</h1>
          <p className="th text-[14px] text-[#6B7588] mt-1.5">เลือกระดับที่ตรงกับคอร์ส — กดบัตรเพื่อพลิกดูคำแปล + ประโยคตัวอย่าง</p>
        </div>
        <button onClick={() => setView("queue")}
          className="th h-12 px-6 rounded-xl text-white font-semibold text-[14px] flex items-center gap-2 transition hover:opacity-90"
          style={{ background:"#0A6CFF" }}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-3.07"/></svg>
          ทบทวนคำที่ค้าง{totalReview > 0 && <span className="bg-white/25 px-2 py-0.5 rounded-full text-[12px] font-bold">{totalReview}</span>}
        </button>
      </div>

      {loading ? <Card className="p-12 text-center th text-[14px] text-[#6B7588]">กำลังโหลด...</Card>
      : err ? <Card className="p-10 text-center th text-[14px] text-[#DC2626]">{err}</Card>
      : (
        <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5">
          {decks.map(d => {
            const pct = d.total > 0 ? Math.round((d.learned / d.total) * 100) : 0
            const stripe = STRIPE_BG[d.stripe] || STRIPE_BG["stripe-blue"]
            const fg = STRIPE_FG[d.fg] || "#0857d6"
            const isEmpty = d.total === 0
            return (
              <button key={d.id} disabled={isEmpty} onClick={() => { setActiveDeck(d); setView("study") }}
                className="text-left bg-white rounded-2xl border border-[#E6EAF1] overflow-hidden transition hover:shadow-lg hover:-translate-y-0.5 hover:border-[#cfd6e3] disabled:opacity-60 disabled:cursor-not-allowed">
                <div className="h-28 relative flex items-end p-4" style={{ backgroundColor: stripe.bg, backgroundImage: stripe.img }}>
                  <span className="th inline-flex items-center px-3 py-1 rounded-full text-[12px] font-semibold" style={{ background:"rgba(255,255,255,0.85)", color: fg }}>
                    {d.title.split(" — ")[0]}
                  </span>
                </div>
                <div className="p-5">
                  <div className="th text-[17px] font-bold text-[#0B1220]">{d.title}</div>
                  <div className="th text-[13px] text-[#6B7588] mt-1">{d.desc}</div>
                  <div className="flex items-center justify-between mt-4 mb-1.5 th text-[12.5px]">
                    <span className="text-[#6B7588]">เรียนแล้ว <span className="font-semibold text-[#0B1220]">{d.learned}</span> / {d.total}</span>
                    <span className="font-bold" style={{ color: fg }}>{pct}%</span>
                  </div>
                  <div className="w-full h-2 rounded-full overflow-hidden bg-[#E6EAF1]">
                    <div className="h-full transition-all" style={{ width: pct + "%", background: fg }} />
                  </div>
                  {isEmpty && <div className="mt-3 th text-[12px] text-[#92400E] bg-[#FEF3C7] rounded-md px-2 py-1.5 text-center">ยังไม่มีคำศัพท์ในระดับนี้</div>}
                </div>
              </button>
            )
          })}
        </div>
      )}
    </div>
  )
}

function VocabStudy({ source, onExit }) {
  const isQueue = source?.kind === "queue"
  const deckMeta = source?.deckMeta || null
  const [words, setWords] = useStateApp([])
  const [loading, setLoading] = useStateApp(true)
  const [err, setErr] = useStateApp(null)
  const [idx, setIdx] = useStateApp(0)
  const [flipped, setFlipped] = useStateApp(false)
  const [timerOn, setTimerOn] = useStateApp(false)
  const PER_SIDE = 10  // seconds per card-side; auto flip then auto next
  const [secondsLeft, setSecondsLeft] = useStateApp(PER_SIDE)
  const [round, setRound] = useStateApp(1)
  const [roundStats, setRoundStats] = useStateApp({ mastered: 0, review: 0 })
  const [startTime, setStartTime] = useStateApp(() => Date.now())
  const [showSummary, setShowSummary] = useStateApp(false)

  useEffectApp(() => {
    (async () => {
      setLoading(true); setErr(null)
      try {
        const path = isQueue ? "/vocab/queue?limit=30" : "/vocab/deck/" + deckMeta.level
        const r = await apiFetch(path)
        if (!r.ok) throw new Error("โหลดคำศัพท์ไม่สำเร็จ")
        const d = await r.json()
        setWords(isQueue ? (d.queue || []) : (d.words || []))
      } catch (e) { setErr(e.message) }
      finally { setLoading(false) }
    })()
  }, [isQueue, deckMeta?.level])

  const total = words.length
  const card  = words[idx]
  const fg    = isQueue ? "#0A6CFF" : (STRIPE_FG[deckMeta?.fg] || "#0857d6")
  const titleText = isQueue ? "ทบทวนคำที่ค้าง" : (deckMeta?.title || "")

  const next = () => {
    if (idx >= total - 1) { setShowSummary(true); return }
    setFlipped(false); setSecondsLeft(PER_SIDE); setIdx(i => i + 1)
  }
  const prev = () => { setFlipped(false); setSecondsLeft(PER_SIDE); setIdx(i => Math.max(i - 1, 0)) }
  const flip = () => { setFlipped(f => !f); setSecondsLeft(PER_SIDE) }
  const mark = async (status) => {
    if (!card) return
    try {
      await apiFetch("/vocab/progress", { method: "POST", body: { word_id: card.id, status } })
      setWords(ws => ws.map((w, i) => i === idx ? { ...w, status } : w))
    } catch {}
    setRoundStats(s => ({ ...s, [status]: (s[status] || 0) + 1 }))
    setTimeout(next, 200)
  }
  const restartRound = (onlyReview) => {
    if (onlyReview) {
      const filtered = words.filter(w => w.status !== "mastered")
      if (filtered.length === 0) { onExit(); return }
      setWords(filtered)
    }
    setIdx(0); setFlipped(false); setSecondsLeft(PER_SIDE)
    setRound(r => r + 1)
    setRoundStats({ mastered: 0, review: 0 })
    setStartTime(Date.now())
    setShowSummary(false)
  }
  const speak = (e) => {
    e?.stopPropagation()
    if (window.speechSynthesis && card) {
      const u = new SpeechSynthesisUtterance(card.en)
      u.lang = "en-US"; u.rate = 0.9
      window.speechSynthesis.speak(u)
    }
  }

  // Countdown — auto-flip on first 0, auto-next on second 0; pauseable
  useEffectApp(() => {
    if (!timerOn || !card) return
    if (secondsLeft <= 0) {
      if (!flipped) { setFlipped(true); setSecondsLeft(PER_SIDE) }
      else next()
      return
    }
    const t = setTimeout(() => setSecondsLeft(s => s - 1), 1000)
    return () => clearTimeout(t)
  }, [timerOn, secondsLeft, flipped, card, idx])

  // Reset timer when card changes
  useEffectApp(() => { setSecondsLeft(PER_SIDE) }, [idx])

  // Keyboard navigation
  useEffectApp(() => {
    const onKey = (e) => {
      if (!card) return
      if (e.target && (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA")) return
      if (e.key === "ArrowRight") { e.preventDefault(); next() }
      else if (e.key === "ArrowLeft") { e.preventDefault(); prev() }
      else if (e.key === " " || e.key === "Enter") { e.preventDefault(); flip() }
      else if (e.key === "1" || e.key.toLowerCase() === "n") { e.preventDefault(); mark("review") }
      else if (e.key === "2" || e.key.toLowerCase() === "y") { e.preventDefault(); mark("mastered") }
      else if (e.key.toLowerCase() === "p") { e.preventDefault(); setTimerOn(t => !t) }
      else if (e.key.toLowerCase() === "s") { e.preventDefault(); speak() }
    }
    window.addEventListener("keydown", onKey)
    return () => window.removeEventListener("keydown", onKey)
  }, [card, idx, total, flipped])

  return (
    <div className="anim-in" style={{ animation: "popup-slide-up 0.28s cubic-bezier(.2,.8,.2,1) both", maxWidth: 760, margin: "0 auto" }}>
      <button onClick={onExit} className="th inline-flex items-center gap-2 mb-4 text-[14px] font-medium text-[#2B3445] hover:text-[#0A6CFF] transition">
        ← กลับเลือก deck
      </button>

      <div className="th flex items-center justify-between mb-3 text-[13px]">
        <span className="font-bold text-[#0B1220]">{titleText}</span>
        <div className="flex items-center gap-3">
          <button onClick={() => setTimerOn(t => !t)}
            className="th inline-flex items-center gap-1.5 h-7 px-3 rounded-full text-[12px] font-semibold border transition"
            style={{ background: timerOn ? "#0A6CFF" : "#fff", color: timerOn ? "#fff" : "#2B3445", borderColor: timerOn ? "#0A6CFF" : "#E6EAF1" }}
            title="P เพื่อ pause/resume">
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
            {timerOn ? `${secondsLeft}s` : "Timer off"}
          </button>
          <span className="th inline-flex items-center px-2.5 py-0.5 rounded-full text-[11px] font-semibold" style={{ background: "#EAF2FF", color: "#0857d6" }}>รอบที่ {round}</span>
          <span className="text-[#6B7588] num">{total > 0 ? `${idx + 1} / ${total}` : "0 / 0"}</span>
        </div>
      </div>
      <div className="w-full h-2 rounded-full overflow-hidden bg-[#E6EAF1] mb-2">
        <div className="h-full transition-all" style={{ width: total > 0 ? ((idx + 1) / total * 100) + "%" : "0%", background: fg }} />
      </div>
      {timerOn && (
        <div className="w-full h-1 rounded-full overflow-hidden bg-[#E6EAF1] mb-5">
          <div className="h-full transition-all" style={{ width: ((secondsLeft / PER_SIDE) * 100) + "%", background: secondsLeft <= 3 ? "#DC2626" : fg }} />
        </div>
      )}
      {!timerOn && <div className="mb-5" />}

      {showSummary ? (() => {
        const totalMarked = roundStats.mastered + roundStats.review
        const acc = totalMarked > 0 ? Math.round(roundStats.mastered / totalMarked * 100) : 0
        const elapsedSec = Math.round((Date.now() - startTime) / 1000)
        const mm = String(Math.floor(elapsedSec / 60)).padStart(2, "0")
        const ss = String(elapsedSec % 60).padStart(2, "0")
        const accColor = acc >= 80 ? "#16A34A" : acc >= 50 ? "#0A6CFF" : "#DC2626"
        return (
          <Card className="p-8" style={{ background: "#fff" }}>
            <div className="text-center">
              <div className="th text-[12px] uppercase tracking-[0.12em] font-bold mb-2" style={{ color: fg }}>จบรอบที่ {round}</div>
              <h3 className="th text-[24px] font-extrabold text-[#0B1220] mb-1" style={{ letterSpacing: "-0.01em" }}>{titleText}</h3>
              <p className="th text-[13px] text-[#6B7588]">เวลา {mm}:{ss} · จด {totalMarked} ใน {total} ใบ</p>
            </div>
            <div className="my-7 grid grid-cols-3 gap-3 text-center">
              <div className="p-4 rounded-2xl" style={{ background: "#F6F8FB" }}>
                <div className="num text-[34px] font-extrabold" style={{ color: accColor, lineHeight: 1 }}>{acc}<span className="text-[18px]">%</span></div>
                <div className="th text-[11px] text-[#6B7588] uppercase tracking-wider font-semibold mt-2">ความแม่น</div>
              </div>
              <div className="p-4 rounded-2xl" style={{ background: "#DCFCE7" }}>
                <div className="num text-[34px] font-extrabold text-[#16A34A]" style={{ lineHeight: 1 }}>{roundStats.mastered}</div>
                <div className="th text-[11px] text-[#15803D] uppercase tracking-wider font-semibold mt-2">จำได้</div>
              </div>
              <div className="p-4 rounded-2xl" style={{ background: "#FEE2E2" }}>
                <div className="num text-[34px] font-extrabold text-[#DC2626]" style={{ lineHeight: 1 }}>{roundStats.review}</div>
                <div className="th text-[11px] text-[#B91C1C] uppercase tracking-wider font-semibold mt-2">ทบทวน</div>
              </div>
            </div>
            <div className="grid grid-cols-1 md:grid-cols-3 gap-2.5">
              {roundStats.review > 0 && (
                <button onClick={() => restartRound(true)} className="th h-12 rounded-xl text-white font-semibold text-[14px]" style={{ background: "#DC2626" }}>
                  ทบทวนคำที่จำไม่ได้ ({roundStats.review})
                </button>
              )}
              <button onClick={() => restartRound(false)} className="th h-12 rounded-xl text-white font-semibold text-[14px]" style={{ background: "#0A6CFF" }}>
                ทำซ้ำทั้ง deck (รอบ {round + 1})
              </button>
              <button onClick={onExit} className="th h-12 rounded-xl bg-white border border-[#E6EAF1] text-[#2B3445] hover:bg-[#F6F8FB] font-semibold text-[14px]">
                กลับเลือก deck
              </button>
            </div>
            {acc >= 80 && roundStats.review === 0 && (
              <div className="th text-center mt-6 text-[14px] font-semibold" style={{ color: "#16A34A" }}>
                แม่นทุกคำในรอบนี้
              </div>
            )}
          </Card>
        )
      })()
      : loading ? <Card className="p-12 text-center th text-[14px] text-[#6B7588]">กำลังโหลดคำศัพท์...</Card>
      : err ? <Card className="p-10 text-center th text-[14px] text-[#DC2626]">{err}</Card>
      : total === 0 ? <Card className="p-10 text-center th text-[14px] text-[#6B7588]">ยังไม่มีคำศัพท์ในระดับนี้ — รอ admin เพิ่ม</Card>
      : (
        <>
          <div style={{ perspective:"1600px", height: 440 }} onClick={() => setFlipped(f => !f)}>
            <div style={{ position:"relative", width:"100%", height:"100%", transformStyle:"preserve-3d", transition:"transform 0.6s cubic-bezier(.2,.8,.2,1)", transform: flipped ? "rotateY(180deg)" : "" }}>
              {/* Front */}
              <div style={{ position:"absolute", inset:0, backfaceVisibility:"hidden", WebkitBackfaceVisibility:"hidden", background:"#fff", border:"1px solid #E6EAF1", borderRadius:24, boxShadow:"0 12px 40px -16px rgba(11,18,32,0.16)", display:"flex", flexDirection:"column", cursor:"pointer" }}>
                <div className="px-7 pt-6 pb-3 flex items-center justify-between">
                  <span className="th inline-flex items-center px-3 py-1 rounded-full text-[12px] font-semibold" style={{ background:"#EAF2FF", color:"#0857d6" }}>คำศัพท์</span>
                  <span className="th text-[12px] text-[#6B7588] uppercase tracking-wider font-semibold">คลิกเพื่อดูความหมาย</span>
                </div>
                <div className="flex-1 px-8 pb-6 flex flex-col justify-center text-center">
                  {card.pos && <div className="th text-[12px] uppercase tracking-[0.18em] text-[#6B7588] font-semibold mb-3">{card.pos}</div>}
                  <h2 className="text-[56px] sm:text-[72px] font-extrabold text-[#0B1220]" style={{ letterSpacing:"-0.03em", lineHeight: 1 }}>{card.en}</h2>
                  {card.ipa && <div className="text-[18px] text-[#6B7588] mt-4 italic">{card.ipa}</div>}
                  <button onClick={speak} className="mt-6 inline-flex items-center justify-center w-12 h-12 rounded-full bg-[#EAF2FF] text-[#0857d6] hover:bg-[#0A6CFF] hover:text-white transition mx-auto">
                    <Volume2 size={18} />
                  </button>
                </div>
              </div>
              {/* Back */}
              <div style={{ position:"absolute", inset:0, backfaceVisibility:"hidden", WebkitBackfaceVisibility:"hidden", transform:"rotateY(180deg)", background:"#EAF2FF", border:"1px solid #E6EAF1", borderRadius:24, boxShadow:"0 12px 40px -16px rgba(11,18,32,0.16)", display:"flex", flexDirection:"column", cursor:"pointer" }}>
                <div className="px-7 pt-6 pb-3 flex items-center justify-between">
                  <span className="th inline-flex items-center px-3 py-1 rounded-full text-[12px] font-semibold" style={{ background:"rgba(255,255,255,0.85)", color:"#0857d6" }}>ความหมาย</span>
                  <button onClick={speak} className="inline-flex items-center justify-center w-9 h-9 rounded-full bg-white text-[#0857d6] hover:bg-[#0A6CFF] hover:text-white transition">
                    <Volume2 size={16} />
                  </button>
                </div>
                <div className="flex-1 px-8 pb-6 flex flex-col justify-center">
                  <div className="th text-[26px] font-bold text-[#0B1220] mb-2">{card.th}</div>
                  <div className="th text-[14px] text-[#6B7588] mb-5">
                    <span className="font-semibold">{card.en}</span>{card.ipa ? ` · ${card.ipa}` : ""}{card.pos ? ` · ${card.pos}` : ""}
                  </div>
                  {card.example && (
                    <div className="bg-white rounded-xl border border-[#E6EAF1] p-4">
                      <div className="th text-[11px] text-[#6B7588] uppercase tracking-wider font-semibold mb-2">ตัวอย่าง</div>
                      <div className="text-[15px] text-[#0B1220] mb-1">{card.example}</div>
                      {card.example_th && <div className="th text-[14px] text-[#6B7588]">{card.example_th}</div>}
                    </div>
                  )}
                </div>
              </div>
            </div>
          </div>

          <div className="mt-7 grid grid-cols-1 md:grid-cols-[auto_1fr_auto] gap-3 items-center">
            <button onClick={prev} disabled={idx === 0} className="th h-12 px-5 rounded-xl bg-white border border-[#E6EAF1] text-[#2B3445] hover:bg-[#F6F8FB] disabled:opacity-40 font-semibold text-[14px]">← ก่อนหน้า</button>
            <div className="flex items-center justify-center gap-3">
              <button onClick={() => mark("review")} className="th h-12 px-5 rounded-xl font-semibold text-[14px]" style={{ background:"#FEE2E2", color:"#DC2626" }}>ยังไม่จำได้</button>
              <button onClick={() => mark("mastered")} className="th h-12 px-5 rounded-xl text-white font-semibold text-[14px]" style={{ background:"#16A34A" }}>จำได้แล้ว</button>
            </div>
            <button onClick={next} disabled={idx === total - 1} className="th h-12 px-5 rounded-xl bg-white border border-[#E6EAF1] text-[#2B3445] hover:bg-[#F6F8FB] disabled:opacity-40 font-semibold text-[14px]">ถัดไป →</button>
          </div>
          <div className="mt-5 flex flex-wrap gap-2 justify-center">
            {[
              { keys: ["←", "→"], icon: <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 5 5 12 12 19"/></svg> },
              { keys: ["Space"], icon: <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 8v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8"/></svg> },
              { keys: ["1"], icon: <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#DC2626" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> },
              { keys: ["2"], icon: <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#16A34A" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg> },
              { keys: ["P"], icon: <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> },
              { keys: ["S"], icon: <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/></svg> },
            ].map((s, i) => (
              <span key={i} className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md bg-[#F6F8FB] border border-[#E6EAF1]">
                {s.keys.map((k, j) => (
                  <kbd key={j} style={{ fontFamily:"'JetBrains Mono', ui-monospace, monospace", fontSize:10.5, fontWeight:600, color:"#2B3445", background:"#fff", border:"1px solid #cfd6e3", borderBottomWidth:"2px", borderRadius:4, padding:"1px 5px", lineHeight:1.1 }}>{k}</kbd>
                ))}
                <span className="text-[#6B7588]">{s.icon}</span>
              </span>
            ))}
          </div>
        </>
      )}
    </div>
  )
}

// ── PaymentModal ──────────────────────────────────────────────────────────
const OMISE_PKEY      = 'pkey_test_67ie8qqenrtwaab9vbu'
const PROMPTPAY_NUMBER = '0889013975'

function generatePromptPayPayload(phone, amount) {
  const normalized = '0066' + phone.replace(/\D/g, '').replace(/^0/, '')
  const tlv = (tag, val) => tag + String(val.length).padStart(2, '0') + val
  const accountInfo = tlv('00', 'A000000677010111') + tlv('01', normalized)
  let p = tlv('00', '01') + tlv('01', '12') + tlv('29', accountInfo) +
          tlv('53', '764') + (amount > 0 ? tlv('54', amount.toFixed(2)) : '') +
          tlv('58', 'TH') + '6304'
  let crc = 0xFFFF
  for (let i = 0; i < p.length; i++) {
    crc ^= p.charCodeAt(i) << 8
    for (let j = 0; j < 8; j++) crc = (crc & 0x8000) ? ((crc << 1) ^ 0x1021) & 0xFFFF : (crc << 1) & 0xFFFF
  }
  return p + crc.toString(16).toUpperCase().padStart(4, '0')
}

function PaymentModal({ item, type, finalPrice, promoCode, onClose, onSuccess }) {
  const [tab,      setTab]      = useStateApp('card')
  const [loading,  setLoading]  = useStateApp(false)
  const [error,    setError]    = useStateApp('')
  // card fields
  const [cardName, setCardName] = useStateApp('')
  const [cardNum,  setCardNum]  = useStateApp('')
  const [expiry,   setExpiry]   = useStateApp('')
  const [cvv,      setCvv]      = useStateApp('')
  // promptpay fields
  const [qrUrl,    setQrUrl]    = useStateApp(null)
  const [slip,     setSlip]     = useStateApp(null)
  const [ppDone,   setPpDone]   = useStateApp(false)

  useEffectApp(() => {
    if (window.Omise) window.Omise.setPublicKey(OMISE_PKEY)
  }, [])

  useEffectApp(() => {
    if (tab !== 'promptpay' || !window.QRCode) return
    const payload = generatePromptPayPayload(PROMPTPAY_NUMBER, finalPrice || 0)
    window.QRCode.toDataURL(payload, { width: 220, margin: 2, color: { dark: '#0B1220', light: '#ffffff' } },
      (err, url) => { if (!err) setQrUrl(url) })
  }, [tab])

  const fmtCard = v => v.replace(/\D/g,'').slice(0,16).replace(/(.{4})/g,'$1 ').trim()
  const fmtExp  = v => { const d = v.replace(/\D/g,''); return d.length >= 3 ? d.slice(0,2)+'/'+d.slice(2,4) : d }
  const canPay  = cardName.trim() && cardNum.replace(/\s/g,'').length === 16 && expiry.length === 5 && cvv.length >= 3

  const submitCard = () => {
    if (!canPay || loading) return
    setLoading(true); setError('')
    const [mon, yr] = expiry.split('/')
    window.Omise.createToken('card', {
      name: cardName.trim(), number: cardNum.replace(/\s/g,''),
      expiration_month: parseInt(mon,10), expiration_year: parseInt('20'+yr,10), security_code: cvv,
    }, async (status, resp) => {
      if (resp.object === 'error') { setError(resp.message || 'ข้อมูลบัตรไม่ถูกต้อง'); setLoading(false); return }
      try {
        const token = resp.id
        const body = type === 'package'
          ? { package_id: item.id, omise_token: token }
          : { set_id: item.id, omise_token: token, ...(promoCode ? { promo_code: promoCode } : {}) }
        const endpoint = type === 'package' ? '/payments/buy-package' : '/payments/buy'
        const r = await apiFetch(endpoint, { method: 'POST', body })
        if (!r.ok) { const e = await r.json().catch(()=>({})); setError(e.detail || 'ชำระเงินไม่สำเร็จ') }
        else onSuccess()
      } catch(e) { setError('Network error: '+e.message) }
      setLoading(false)
    })
  }

  const handleFile = e => {
    const f = e.target.files?.[0]; if (!f) return
    if (f.size > 5*1024*1024) { setError('ไฟล์ใหญ่เกิน 5MB'); return }
    const r = new FileReader(); r.onload = () => setSlip(r.result); r.readAsDataURL(f)
  }

  const submitSlip = async () => {
    if (!slip || loading) return
    setLoading(true); setError('')
    try {
      const createRes = await apiFetch('/payments/promptpay/create', {
        method: 'POST',
        body: { item_type: type === 'package' ? 'package' : 'set', item_id: item.id, ...(promoCode ? { promo_code: promoCode } : {}) }
      })
      if (!createRes.ok) { const e = await createRes.json().catch(()=>({})); setError(e.detail || 'เกิดข้อผิดพลาด'); setLoading(false); return }
      const { order_id } = await createRes.json()
      const slipRes = await apiFetch(`/payments/promptpay/${order_id}/slip`, { method: 'POST', body: { slip_data: slip } })
      if (!slipRes.ok) { const e = await slipRes.json().catch(()=>({})); setError(e.detail || 'อัปโหลดสลิปไม่สำเร็จ'); setLoading(false); return }
      setPpDone(true)
    } catch(e) { setError('Network error: '+e.message) }
    setLoading(false)
  }

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4" style={{background:'rgba(11,18,32,0.6)'}}>
      <div className="bg-white rounded-2xl shadow-2xl w-full max-w-md">
        <div className="flex items-center justify-between px-5 pt-5 pb-4 border-b border-[#E6EAF1]">
          <div>
            <div className="font-bold text-[15px] truncate max-w-[280px]">{item?.name || item?.title}</div>
            <div className="th text-[13px] text-[#6B7588] mt-0.5">ชำระ <span className="font-bold text-[#0B1220]">฿{(finalPrice||0).toLocaleString()}</span></div>
          </div>
          <button onClick={onClose} className="p-2 rounded-lg hover:bg-[#F6F8FB]"><XIcon size={18}/></button>
        </div>

        {/* Tabs */}
        <div className="flex border-b border-[#E6EAF1] px-5">
          {[['card','บัตรเครดิต / เดบิต'],['promptpay','PromptPay']].map(([k,l]) => (
            <button key={k} onClick={() => { setTab(k); setError('') }}
              className={`py-3 mr-5 text-[13px] font-semibold border-b-2 transition ${tab===k ? 'border-[#0A6CFF] text-[#0A6CFF]' : 'border-transparent text-[#6B7588] hover:text-[#0B1220]'}`}>
              {l}
            </button>
          ))}
        </div>

        <div className="p-5 space-y-4">
          {error && <div className="th p-3 rounded-lg bg-red-50 border border-red-200 text-[13px] text-red-700">{error}</div>}

          {tab === 'card' && <>
            <div>
              <label className="th block text-[12px] text-[#6B7588] font-semibold mb-1">ชื่อบนบัตร</label>
              <input value={cardName} onChange={e=>setCardName(e.target.value)} placeholder="JOHN DOE"
                className="w-full border border-[#E6EAF1] rounded-xl px-3 py-2.5 text-[14px] outline-none focus:border-[#0A6CFF] transition"/>
            </div>
            <div>
              <label className="th block text-[12px] text-[#6B7588] font-semibold mb-1">หมายเลขบัตร</label>
              <input value={cardNum} onChange={e=>setCardNum(fmtCard(e.target.value))}
                placeholder="0000 0000 0000 0000" maxLength={19}
                className="w-full border border-[#E6EAF1] rounded-xl px-3 py-2.5 text-[14px] outline-none focus:border-[#0A6CFF] transition font-mono tracking-widest"/>
            </div>
            <div className="grid grid-cols-2 gap-3">
              <div>
                <label className="th block text-[12px] text-[#6B7588] font-semibold mb-1">หมดอายุ</label>
                <input value={expiry} onChange={e=>setExpiry(fmtExp(e.target.value))} placeholder="MM/YY" maxLength={5}
                  className="w-full border border-[#E6EAF1] rounded-xl px-3 py-2.5 text-[14px] outline-none focus:border-[#0A6CFF] transition font-mono"/>
              </div>
              <div>
                <label className="th block text-[12px] text-[#6B7588] font-semibold mb-1">CVV</label>
                <input value={cvv} onChange={e=>setCvv(e.target.value.replace(/\D/g,'').slice(0,4))}
                  placeholder="123" type="password" maxLength={4}
                  className="w-full border border-[#E6EAF1] rounded-xl px-3 py-2.5 text-[14px] outline-none focus:border-[#0A6CFF] transition font-mono"/>
              </div>
            </div>
            <button onClick={submitCard} disabled={!canPay || loading}
              className="w-full py-3 rounded-xl text-white font-bold th text-[15px] transition disabled:opacity-40"
              style={{background: (!canPay||loading) ? '#9CA3AF' : '#0A6CFF'}}>
              {loading ? 'กำลังชำระ...' : `ชำระ ฿${(finalPrice||0).toLocaleString()}`}
            </button>
            <div className="th text-[11px] text-center text-[#6B7588]">ข้อมูลบัตรเข้ารหัสโดย Omise · PCI DSS Level 1</div>
          </>}

          {tab === 'promptpay' && <>
            {ppDone ? (
              <div className="py-6 text-center space-y-2">
                <div className="text-[32px]">✓</div>
                <div className="th font-bold text-[15px] text-[#16A34A]">ส่งสลิปเรียบร้อยแล้ว</div>
                <div className="th text-[13px] text-[#6B7588]">ทีมงานจะตรวจสอบและเปิดสิทธิ์ให้ภายใน 24 ชั่วโมง</div>
                <button onClick={onClose} className="mt-4 px-6 py-2 rounded-xl bg-[#F6F8FB] text-[13px] font-semibold th">ปิด</button>
              </div>
            ) : (
              <>
                <div className="flex flex-col items-center gap-3 py-2">
                  {qrUrl
                    ? <img src={qrUrl} alt="PromptPay QR" style={{width:200,height:200,borderRadius:12,border:'1px solid #E6EAF1'}}/>
                    : <div style={{width:200,height:200,borderRadius:12,background:'#F6F8FB',display:'flex',alignItems:'center',justifyContent:'center'}} className="th text-[13px] text-[#6B7588]">กำลังโหลด QR...</div>
                  }
                  <div className="th text-center">
                    <div className="text-[12px] text-[#6B7588] mb-0.5">PromptPay</div>
                    <div className="font-bold text-[18px] tracking-widest font-mono">{PROMPTPAY_NUMBER}</div>
                    <div className="th text-[13px] text-[#6B7588] mt-0.5">จำนวน <span className="font-bold text-[#0B1220]">฿{(finalPrice||0).toLocaleString()}</span></div>
                  </div>
                </div>

                <div className="border-t border-[#E6EAF1] pt-4">
                  <div className="th text-[12px] font-semibold text-[#6B7588] mb-2">อัปโหลดสลิปการโอน</div>
                  {slip
                    ? <div className="relative">
                        <img src={slip} alt="slip" className="w-full rounded-xl object-cover" style={{maxHeight:160}}/>
                        <button onClick={()=>setSlip(null)} className="absolute top-2 right-2 bg-white rounded-full p-1 shadow"><XIcon size={14}/></button>
                      </div>
                    : <label className="flex flex-col items-center justify-center gap-2 border-2 border-dashed border-[#E6EAF1] rounded-xl py-6 cursor-pointer hover:border-[#0A6CFF] transition">
                        <input type="file" accept="image/*" className="hidden" onChange={handleFile}/>
                        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6B7588" strokeWidth="1.5"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
                        <span className="th text-[13px] text-[#6B7588]">คลิกเพื่อเลือกรูปสลิป</span>
                      </label>
                  }
                </div>

                <button onClick={submitSlip} disabled={!slip || loading}
                  className="w-full py-3 rounded-xl text-white font-bold th text-[15px] transition disabled:opacity-40"
                  style={{background: (!slip||loading) ? '#9CA3AF' : '#0A6CFF'}}>
                  {loading ? 'กำลังส่ง...' : 'ส่งสลิป'}
                </button>
                <div className="th text-[11px] text-center text-[#6B7588]">ทีมงานจะตรวจสอบและเปิดสิทธิ์ภายใน 24 ชั่วโมง</div>
              </>
            )}
          </>}
        </div>
      </div>
    </div>
  )
}

/* mount */
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<App />)
