import { useEffect, useRef, useState } from "react";
import { ChevronDown, ChevronRight, Gauge, Map } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { useAuthStore } from "../stores/authStore";
import {
  useQuestStore,
  getQuestSummary,
  getCrewRank,
} from "../stores/useQuestStore";
import type {
  QuestNode,
  QuestArc,
  ClaimedRewardResponse,
} from "../types/quest";
import { CREW_RANKS } from "../types/quest";
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
import { Drawer, DrawerContent, DrawerTrigger } from "./ui/drawer";
import { PredictedScoreCard } from "./PredictedScoreCard";
import { ChestOpenModal } from "./ChestOpenModal";
// Re-use the same theme generator that QuestMap uses so arc colours are consistent
import { generateArcTheme } from "../pages/student/QuestMap";
import { InventoryButton } from "./InventoryButton";

// ─── Requirement helpers (mirrors QuestMap) ───────────────────────────────────
const REQ_EMOJI: Record<string, string> = {
  questions: "❓",
  accuracy: "🎯",
  streak: "🔥",
  sessions: "📚",
  topics: "🗺️",
  xp: "⚡",
  leaderboard: "🏆",
};

const REQ_LABEL: Record<string, string> = {
  questions: "questions answered",
  accuracy: "% accuracy",
  streak: "day streak",
  sessions: "sessions",
  topics: "topics covered",
  xp: "XP earned",
  leaderboard: "leaderboard rank",
};

// ─── Styles ───────────────────────────────────────────────────────────────────
const STYLES = `
  @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@700;800;900&family=Nunito+Sans:wght@400;600;700&family=Cinzel:wght@700;900&display=swap');

  /* ════ SHARED ANIMATION ════ */
  @keyframes hcIn {
    from { opacity:0; transform:translateY(10px) scale(0.97); }
    to   { opacity:1; transform:translateY(0) scale(1); }
  }

  /* ════ WHITE CARD (DEFAULT / LEVEL / QUEST_COMPACT) ════ */
  .hc-card {
    background: white;
    border: 2.5px solid #f3f4f6;
    border-radius: 26px;
    box-shadow: 0 4px 20px rgba(0,0,0,0.06);
    overflow: hidden;
    animation: hcIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both;
  }

  /* Identity */
  .hc-top {
    display: flex; align-items: center;
    justify-content: space-between; gap: 0.75rem;
    padding: 1.1rem 1.2rem 0.9rem;
  }
  .hc-identity { display: flex; align-items: center; gap: 0.7rem; flex: 1; min-width: 0; }
  .hc-av-wrap  { position: relative; flex-shrink: 0; }
  .hc-av-pip {
    position: absolute; bottom: -3px; right: -3px;
    min-width: 18px; height: 18px; border-radius: 9px; padding: 0 4px;
    background: linear-gradient(135deg, #a855f7, #7c3aed);
    border: 2px solid white;
    display: flex; align-items: center; justify-content: center;
    font-family: 'Nunito', sans-serif;
    font-size: 0.55rem; font-weight: 900; color: white;
  }
  .hc-nameblock { flex: 1; min-width: 0; }
  .hc-greeting {
    font-family: 'Nunito', sans-serif;
    font-size: 0.98rem; font-weight: 900; color: #1e1b4b;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.2;
  }
  .hc-greeting em { font-style: normal; color: #a855f7; }
  .hc-role {
    font-family: 'Nunito Sans', sans-serif;
    font-size: 0.63rem; font-weight: 700; letter-spacing: 0.09em;
    text-transform: uppercase; color: #9ca3af; margin-top: 0.05rem;
  }
  .hc-score-btn {
    display: flex; align-items: center; gap: 0.3rem;
    background: #f7ffe4; border: 2px solid #d9f99d; border-radius: 100px;
    padding: 0.42rem 0.72rem; font-family: 'Nunito', sans-serif;
    font-size: 0.76rem; font-weight: 800; color: #65a30d;
    cursor: pointer; flex-shrink: 0;
    transition: transform 0.15s, box-shadow 0.15s;
    box-shadow: 0 2px 8px rgba(0,0,0,0.04);
  }
  .hc-score-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0,0,0,0.07); }
  .hc-sep { height: 1px; margin: 0 1.2rem; background: #f3f4f6; }

  /* XP bar */
  .hc-xp-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.85rem 1.2rem; }
  .hc-lvl-tag {
    font-family: 'Nunito', sans-serif; font-size: 0.7rem; font-weight: 900;
    color: #a855f7; flex-shrink: 0; background: #f3e8ff;
    border-radius: 8px; padding: 0.22rem 0.5rem; white-space: nowrap;
  }
  .hc-bar-wrap { flex: 1; display: flex; flex-direction: column; gap: 0.22rem; }
  .hc-track { height: 8px; background: #f3f4f6; border-radius: 100px; overflow: hidden; }
  .hc-fill {
    height: 100%; border-radius: 100px;
    background: linear-gradient(90deg, #a855f7, #f97316);
    transition: width 1.1s cubic-bezier(0.34,1.56,0.64,1);
    position: relative; overflow: hidden;
  }
  .hc-fill::after {
    content: ''; position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
    transform: translateX(-100%);
    animation: hcShimmer 2.6s ease-in-out 1s infinite;
  }
  @keyframes hcShimmer { to { transform: translateX(200%); } }
  .hc-xp-label {
    font-family: 'Nunito Sans', sans-serif;
    font-size: 0.6rem; font-weight: 700; color: #9ca3af;
    display: flex; justify-content: space-between;
  }
  .hc-xp-label span:first-child { color: #a855f7; font-weight: 900; }

  /* Rank row (compact) */
  .hc-rank-row {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.75rem 1.2rem; cursor: pointer;
    transition: background 0.15s; border-top: 1px solid #f3f4f6;
  }
  .hc-rank-row:first-child { border-top: none; }
  .hc-rank-row:hover { background: #fafafa; }
  .hc-rank-emoji { font-size: 1.15rem; flex-shrink: 0; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1)); }
  .hc-rank-text { flex: 1; min-width: 0; }
  .hc-rank-name {
    font-family: 'Cinzel', serif; font-size: 0.8rem; font-weight: 700; color: #1e1b4b;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .hc-rank-progress-label {
    font-family: 'Nunito Sans', sans-serif; font-size: 0.58rem; font-weight: 700;
    color: #9ca3af; margin-top: 0.08rem;
  }
  .hc-rank-right { display: flex; align-items: center; gap: 0.4rem; flex-shrink: 0; }
  .hc-streak-pill {
    display: flex; align-items: center; gap: 0.22rem;
    background: #fff5f5; border: 1.5px solid #fecaca; border-radius: 100px;
    padding: 0.2rem 0.5rem; font-family: 'Nunito', sans-serif;
    font-size: 0.7rem; font-weight: 900; color: #ef4444;
  }
  .hc-chest-badge {
    display: flex; align-items: center; gap: 0.18rem;
    background: #fef3c7; border: 1.5px solid #fde68a; border-radius: 100px;
    padding: 0.2rem 0.5rem; font-family: 'Nunito', sans-serif;
    font-size: 0.7rem; font-weight: 900; color: #b45309;
    animation: hcPop 1.8s ease-in-out infinite;
  }
  @keyframes hcPop { 0%,100%{transform:scale(1);} 50%{transform:scale(1.07);} }
  .hc-chevron { color: #d1d5db; transition: transform 0.3s cubic-bezier(0.34,1.56,0.64,1), color 0.2s; }
  .hc-chevron.open { transform: rotate(180deg); color: #a855f7; }

  /* Collapsible quest panel */
  .hc-quests-wrap {
    overflow: hidden; max-height: 0;
    transition: max-height 0.38s cubic-bezier(0.4,0,0.2,1);
    background: #fafafa; border-top: 1px solid #f3f4f6;
  }
  .hc-quests-wrap.open { max-height: 480px; }
  .hc-quest-list { display: flex; flex-direction: column; padding: 0.35rem 0; }
  .hc-quest-row {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.65rem 1.2rem; cursor: pointer; transition: background 0.13s; position: relative;
  }
  .hc-quest-row:hover { background: #f3f4f6; }
  .hc-quest-row::before {
    content: ''; position: absolute; left: 0; top: 20%; bottom: 20%;
    width: 3px; border-radius: 0 3px 3px 0; background: var(--ac);
  }
  .hc-q-icon {
    width: 34px; height: 34px; border-radius: 10px; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 1rem; background: white; border: 1.5px solid #f3f4f6;
    transition: transform 0.15s;
  }
  .hc-quest-row:hover .hc-q-icon { transform: scale(1.08) rotate(-4deg); }
  .hc-q-icon.claimable { background: #fef3c7; border-color: #fde68a; animation: hcWiggle 2s ease-in-out infinite; }
  @keyframes hcWiggle { 0%,100%{transform:rotate(0);} 30%{transform:rotate(-7deg) scale(1.05);} 70%{transform:rotate(7deg) scale(1.05);} }
  .hc-q-body { flex: 1; min-width: 0; }
  .hc-q-name { font-family: 'Nunito', sans-serif; font-size: 0.8rem; font-weight: 800; color: #1e1b4b; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .hc-q-sub  { font-family: 'Nunito Sans', sans-serif; font-size: 0.62rem; font-weight: 600; color: #9ca3af; margin-top: 0.1rem; }
  .hc-q-claimable { font-family: 'Nunito Sans', sans-serif; font-size: 0.62rem; font-weight: 700; color: #d97706; margin-top: 0.1rem; }
  .hc-claim-btn {
    padding: 0.28rem 0.62rem; border: none; border-radius: 100px; cursor: pointer;
    background: linear-gradient(135deg, #fbbf24, #f59e0b);
    font-family: 'Nunito', sans-serif; font-size: 0.65rem; font-weight: 900; color: #1a0800;
    box-shadow: 0 2px 0 #d97706; flex-shrink: 0; transition: all 0.12s;
  }
  .hc-claim-btn:hover  { transform: translateY(-1px); }
  .hc-claim-btn:active { transform: translateY(1px); }
  .hc-empty { padding: 1rem 1.2rem; text-align: center; font-family: 'Nunito', sans-serif; font-size: 0.82rem; font-weight: 700; color: #9ca3af; }
  .hc-map-link {
    display: flex; align-items: center; justify-content: center; gap: 0.3rem;
    padding: 0.6rem 1.2rem; border-top: 1px solid #f3f4f6;
    cursor: pointer; transition: background 0.13s;
    font-family: 'Nunito', sans-serif; font-size: 0.7rem; font-weight: 800; color: #a855f7;
  }
  .hc-map-link:hover { background: #fdf4ff; }

  /* ════ DARK OCEAN CARD (QUEST_EXTENDED) ════ */
  .hc-ext {
    background: linear-gradient(160deg, #0b1a35 0%, #060e1f 55%, #0d1530 100%);
    border-radius: 26px; overflow: hidden; position: relative;
    box-shadow: 0 8px 32px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.06);
    animation: hcIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both;
    margin-bottom: 12px;
  }
  .hc-ext::before {
    content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 0;
    background:
      repeating-linear-gradient(105deg, transparent 55%, rgba(56,189,248,0.018) 56%, transparent 57%),
      repeating-linear-gradient(75deg,  transparent 70%, rgba(56,189,248,0.012) 71%, transparent 72%);
    background-size: 320% 320%, 260% 260%;
    animation: hcExtSea 14s ease-in-out infinite alternate;
  }
  @keyframes hcExtSea {
    0%   { background-position: 0% 0%, 100% 0%; }
    100% { background-position: 100% 100%, 0% 100%; }
  }
  .hc-ext::after {
    content: ''; position: absolute; top: -40px; right: -30px; z-index: 0;
    width: 180px; height: 180px; border-radius: 50%;
    background: radial-gradient(circle, rgba(251,191,36,0.1), transparent 70%);
    pointer-events: none;
  }
  .hc-ext-header {
    position: relative; z-index: 2;
    display: flex; align-items: center; justify-content: space-between;
    padding: 1rem 1.2rem 0.3rem;
  }
  .hc-ext-title {
    font-family: 'Cinzel', serif; font-size: 0.6rem; font-weight: 700;
    letter-spacing: 0.2em; text-transform: uppercase; color: rgba(251,191,36,0.65);
  }
  .hc-ext-earned {
    font-family: 'Nunito', sans-serif; font-size: 0.7rem; font-weight: 900;
    color: #fbbf24; background: rgba(251,191,36,0.1);
    border: 1px solid rgba(251,191,36,0.18); border-radius: 100px;
    padding: 0.2rem 0.6rem;
  }
  .hc-ext-scroll {
    position: relative; z-index: 2;
    overflow-x: auto; overflow-y: hidden;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    cursor: grab; padding: 1.0rem 1.0rem 0.8rem;
  }
  .hc-ext-scroll::-webkit-scrollbar { display: none; }
  .hc-ext-scroll:active { cursor: grabbing; }
  .hc-ext-inner {
    display: flex; align-items: flex-end;
    position: relative;
    height: 110px;
  }
  .hc-ext-baseline {
    position: absolute;
    top: 56px; left: 26px; right: 26px; height: 2px;
    background: rgba(255,255,255,0.07);
    border-radius: 2px; z-index: 0;
  }
  .hc-ext-progress-line {
    position: absolute;
    top: 56px; left: 26px; height: 2px;
    background: linear-gradient(90deg, #fbbf24, #f59e0b);
    box-shadow: 0 0 10px rgba(251,191,36,0.5);
    border-radius: 2px; z-index: 1;
    transition: width 1.2s cubic-bezier(0.34,1.56,0.64,1);
  }
  .hc-ext-ship-wrap {
    position: absolute;
    top: 25px; z-index: 10; pointer-events: none;
    display: flex; flex-direction: column; align-items: center; gap: 0px;
    transition: left 1.2s cubic-bezier(0.34,1.56,0.64,1);
    transform: translateX(-50%);
  }
  .hc-ext-ship {
    font-size: 1.5rem;
    filter: drop-shadow(0 2px 12px rgba(251,191,36,0.6));
    animation: hcShipBob 2.8s ease-in-out infinite;
    display: block;
  }
  @keyframes hcShipBob {
    0%,100% { transform: translateY(0) rotate(-3deg); }
    50%     { transform: translateY(-6px) rotate(3deg); }
  }
  .hc-ext-ship-tether {
    width: 1px; height: 14px;
    background: linear-gradient(to bottom, rgba(251,191,36,0.5), transparent);
  }
  .hc-ext-col {
    display: flex; flex-direction: column; align-items: center;
    position: relative; z-index: 2;
    width: 88px; flex-shrink: 0;
  }
  .hc-ext-col:first-child,
  .hc-ext-col:last-child { width: 52px; }
  .hc-ext-node {
    width: 52px; height: 52px; border-radius: 50%; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 1.4rem; position: relative; z-index: 2;
    margin-top: 42px;
  }
  .hc-ext-node.reached {
    background: linear-gradient(145deg, #1e0e4a, #3730a3);
    border: 2px solid rgba(251,191,36,0.45);
    box-shadow: 0 0 18px rgba(251,191,36,0.2), 0 4px 0 rgba(20,10,50,0.7);
  }
  .hc-ext-node.current {
    background: linear-gradient(145deg, #6d28d9, #a855f7);
    border: 2.5px solid #fbbf24;
    box-shadow:
      0 0 0 4px rgba(251,191,36,0.12),
      0 0 22px rgba(168,85,247,0.45),
      0 4px 0 rgba(80,30,150,0.5);
    animation: hcExtNodePulse 2.2s ease-in-out infinite;
  }
  @keyframes hcExtNodePulse {
    0%,100% { box-shadow: 0 0 0 4px rgba(251,191,36,0.12), 0 0 22px rgba(168,85,247,0.45), 0 4px 0 rgba(80,30,150,0.5); }
    50%     { box-shadow: 0 0 0 7px rgba(251,191,36,0.06), 0 0 30px rgba(168,85,247,0.6),  0 4px 0 rgba(80,30,150,0.5); }
  }
  .hc-ext-node.locked {
    background: rgba(0,0,0,0.4);
    border: 2px solid rgba(255,255,255,0.09);
    filter: grayscale(0.7) opacity(0.45);
  }
  .hc-ext-label {
    margin-top: 7px;
    display: flex; flex-direction: column; align-items: center; gap: 2px;
  }
  .hc-ext-label-name {
    font-family: 'Cinzel', serif; font-size: 0.48rem; font-weight: 700;
    text-align: center; line-height: 1.3; letter-spacing: 0.03em; max-width: 70px;
  }
  .hc-ext-label-name.reached { color: #fbbf24; }
  .hc-ext-label-name.current { color: #c084fc; }
  .hc-ext-label-name.locked  { color: rgba(255,255,255,0.2); }
  .hc-ext-label-xp {
    font-family: 'Nunito Sans', sans-serif; font-size: 0.42rem; font-weight: 700;
    text-align: center;
  }
  .hc-ext-label-xp.reached { color: rgba(251,191,36,0.4); }
  .hc-ext-label-xp.current { color: rgba(192,132,252,0.6); }
  .hc-ext-label-xp.locked  { color: rgba(255,255,255,0.15); }
  .hc-ext-footer {
    position: relative; z-index: 2;
    display: flex; align-items: center; justify-content: center; gap: 0.3rem;
    padding: 0.5rem 1.2rem 0.85rem; margin-top: 0.2rem;
    border-top: 1px solid rgba(255,255,255,0.06);
    cursor: pointer; transition: opacity 0.15s;
    font-family: 'Nunito', sans-serif; font-size: 0.68rem; font-weight: 800;
    color: rgba(251,191,36,0.55); letter-spacing: 0.04em;
  }
  .hc-ext-footer:hover { opacity: 0.75; }
`;

// ─── Helpers ──────────────────────────────────────────────────────────────────
function getActiveQuests(arcs: QuestArc[]) {
  const out: { node: QuestNode; arc: QuestArc }[] = [];
  for (const arc of arcs)
    for (const node of arc.nodes)
      if (node.status === "claimable" || node.status === "active")
        out.push({ node, arc });
  // Claimable nodes bubble to the top
  out.sort((a, b) =>
    a.node.status === "claimable" && b.node.status !== "claimable"
      ? -1
      : b.node.status === "claimable" && a.node.status !== "claimable"
        ? 1
        : 0,
  );
  return out.slice(0, 2);
}

const SEG_W = 88;
const EDGE_W = 52;
function nodeX(i: number, total: number): number {
  if (i === 0) return EDGE_W / 2;
  if (i === total - 1) return EDGE_W / 2 + SEG_W * (total - 2) + EDGE_W / 2;
  return EDGE_W + SEG_W * (i - 1) + SEG_W / 2;
}

// ─── QUEST_EXTENDED sub-component ────────────────────────────────────────────
const RankLadder = ({
  earnedXP,
}: {
  earnedXP: number;
  onViewAll: () => void;
}) => {
  const scrollRef = useRef<HTMLDivElement>(null);
  const ladder = [...CREW_RANKS] as typeof CREW_RANKS;
  const N = ladder.length;

  let currentIdx = 0;
  for (let i = N - 1; i >= 0; i--) {
    if (earnedXP >= ladder[i].xpRequired) {
      currentIdx = i;
      break;
    }
  }

  const current = ladder[currentIdx];
  const nextRank = ladder[currentIdx + 1] ?? null;
  const progressToNext = nextRank
    ? Math.min(
        1,
        (earnedXP - current.xpRequired) /
          (nextRank.xpRequired - current.xpRequired),
      )
    : 1;

  const shipX = nextRank
    ? nodeX(currentIdx, N) +
      (nodeX(currentIdx + 1, N) - nodeX(currentIdx, N)) * progressToNext
    : nodeX(currentIdx, N);
  const progressLineW = shipX;
  const totalW = EDGE_W + SEG_W * (N - 2) + EDGE_W;

  const [animated, setAnimated] = useState(false);
  useEffect(() => {
    const id = requestAnimationFrame(() =>
      requestAnimationFrame(() => setAnimated(true)),
    );
    return () => cancelAnimationFrame(id);
  }, []);

  useEffect(() => {
    if (!scrollRef.current) return;
    const el = scrollRef.current;
    el.scrollTo({
      left: Math.max(0, shipX - el.offsetWidth / 2),
      behavior: "smooth",
    });
  }, [shipX]);

  const rankPct = nextRank ? Math.round(progressToNext * 100) : 100;
  const nextLabel = nextRank
    ? `${rankPct}% · ${nextRank.xpRequired - earnedXP} XP to ${nextRank.label}`
    : "Maximum rank achieved";

  return (
    <div className="hc-ext">
      <div className="hc-ext-header">
        <span className="hc-ext-title">⚓ Crew Rank</span>
        <span className="hc-ext-earned">{earnedXP.toLocaleString()} XP</span>
      </div>

      <div
        style={{
          position: "relative",
          zIndex: 2,
          padding: "0 1.2rem 0.1rem",
          display: "flex",
          alignItems: "baseline",
          gap: "0.4rem",
        }}
      >
        <span
          style={{
            fontFamily: "'Cinzel', serif",
            fontSize: "1.05rem",
            fontWeight: 900,
            color: "#fbbf24",
            textShadow: "0 0 18px rgba(251,191,36,0.4)",
          }}
        >
          {current.emoji} {current.label}
        </span>
        <span
          style={{
            fontFamily: "'Nunito Sans', sans-serif",
            fontSize: "0.6rem",
            fontWeight: 700,
            color: "rgba(255,255,255,0.3)",
          }}
        >
          {nextLabel}
        </span>
      </div>

      <div className="hc-ext-scroll" ref={scrollRef}>
        <div className="hc-ext-inner" style={{ width: totalW }}>
          <div className="hc-ext-baseline" />
          <div
            className="hc-ext-progress-line"
            style={{ width: animated ? progressLineW : 26 }}
          />
          <div
            className="hc-ext-ship-wrap"
            style={{ left: animated ? shipX : nodeX(0, N) }}
          >
            <span className="hc-ext-ship" role="img" aria-label="ship">
              ⛵
            </span>
            <div className="hc-ext-ship-tether" />
          </div>
          {ladder.map((r, i) => {
            const state =
              i < currentIdx
                ? "reached"
                : i === currentIdx
                  ? "current"
                  : "locked";
            return (
              <div key={r.id} className="hc-ext-col">
                <div className={`hc-ext-node ${state}`}>{r.emoji}</div>
                <div className="hc-ext-label">
                  <span className={`hc-ext-label-name ${state}`}>
                    {r.label}
                  </span>
                  <span className={`hc-ext-label-xp ${state}`}>
                    {r.xpRequired === 0
                      ? "Start"
                      : `${r.xpRequired.toLocaleString()} XP`}
                  </span>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
};

// ─── Props ────────────────────────────────────────────────────────────────────
type Mode = "DEFAULT" | "LEVEL" | "QUEST_COMPACT" | "QUEST_EXTENDED";
interface Props {
  onViewAll?: () => void;
  mode?: Mode;
}

// ─── Main component ───────────────────────────────────────────────────────────
export const InfoHeader = ({ onViewAll, mode = "DEFAULT" }: Props) => {
  const navigate = useNavigate();
  const user = useAuthStore((s) => s.user);

  // Select all needed store slices — earnedXP and earnedTitles are now first-class state
  const arcs = useQuestStore((s) => s.arcs);
  const earnedXP = user?.total_xp ?? 0;
  const earnedTitles = useQuestStore((s) => s.earnedTitles);
  const claimNode = useQuestStore((s) => s.claimNode);

  // Updated signatures: getQuestSummary needs earnedXP + earnedTitles,
  // getCrewRank takes earnedXP directly (no longer iterates nodes)
  const summary = getQuestSummary(arcs, earnedXP, earnedTitles);
  const rank = getCrewRank(earnedXP);
  const activeQuests = getActiveQuests(arcs);

  const u = user as any;
  const level = u?.current_level ?? 1;
  const totalXP = u?.total_xp ?? 5;
  const levelStart = u?.current_level_start ?? u?.level_min_xp ?? 0;
  const levelEnd =
    u?.next_level_threshold ?? u?.level_max_xp ?? levelStart + 1000;
  const streak = u?.streak ?? u?.current_streak ?? 0;
  const firstName = user?.name?.split(" ")[0] || "there";
  const roleLabel =
    u?.role === "ADMIN"
      ? "Admin"
      : u?.role === "TEACHER"
        ? "Teacher"
        : "Student";
  const hour = new Date().getHours();
  const timeLabel = hour < 12 ? "morning" : hour < 17 ? "afternoon" : "evening";

  const levelRange = Math.max(levelEnd - levelStart, 1);
  const xpIntoLevel = Math.max(totalXP - levelStart, 0);
  const rawPct = Math.min(Math.round((xpIntoLevel / levelRange) * 100), 100);
  const xpToGo = Math.max(levelEnd - totalXP, 0);

  const [barPct, setBarPct] = useState(0);
  useEffect(() => {
    const id = requestAnimationFrame(() =>
      requestAnimationFrame(() => setBarPct(rawPct)),
    );
    return () => cancelAnimationFrame(id);
  }, [rawPct]);

  const [open, setOpen] = useState(false);
  const [claimingNode, setClaimingNode] = useState<{
    node: QuestNode;
    arcId: string;
  } | null>(null);
  // Holds the API response from the claim call so ChestOpenModal can display real rewards
  const [claimResult, setClaimResult] = useState<ClaimedRewardResponse | null>(
    null,
  );

  const handleViewAll = () => {
    if (onViewAll) onViewAll();
    else navigate("/student/quests");
  };

  const handleClaim = (node: QuestNode, arcId: string) => {
    setClaimResult(null); // clear any previous result before opening
    setClaimingNode({ node, arcId });
  };

  const handleChestClose = () => {
    if (!claimingNode) return;
    claimNode(
      claimingNode.arcId,
      claimingNode.node.node_id, // node_id replaces old id
      claimResult?.xp_awarded ?? 0,
      claimResult?.title_unlocked.map((t) => t.name) ?? [],
    );
    setClaimingNode(null);
    setClaimResult(null);
  };

  const rankProgress = Math.round(rank.progressToNext * 100);
  const nextLabel = rank.next
    ? `${rankProgress}% to ${rank.next.label}`
    : "Max rank";

  const showIdentity = mode === "DEFAULT";
  const showLevel = mode === "DEFAULT" || mode === "LEVEL";
  const showQuestCompact = mode === "DEFAULT" || mode === "QUEST_COMPACT";
  const showQuestExtended = mode === "QUEST_EXTENDED";

  if (showQuestExtended) {
    return (
      <>
        <style>{STYLES}</style>
        <RankLadder earnedXP={earnedXP} onViewAll={handleViewAll} />
        {claimingNode && (
          <ChestOpenModal
            node={claimingNode.node}
            claimResult={claimResult}
            onClose={handleChestClose}
          />
        )}
      </>
    );
  }

  return (
    <>
      <style>{STYLES}</style>

      <div className="hc-card">
        {/* Identity — DEFAULT only */}
        {showIdentity && (
          <>
            <div className="hc-top">
              <div className="hc-identity">
                <div className="hc-av-wrap">
                  <Avatar style={{ width: 46, height: 46, display: "block" }}>
                    <AvatarImage src={u?.avatar_url} />
                    <AvatarFallback
                      style={{
                        fontWeight: 900,
                        fontSize: "1rem",
                        color: "white",
                        textTransform: "uppercase",
                        background: "linear-gradient(135deg,#a855f7,#7c3aed)",
                      }}
                    >
                      {user?.name?.slice(0, 1)}
                    </AvatarFallback>
                  </Avatar>
                  <div className="hc-av-pip">{level}</div>
                </div>
                <div className="hc-nameblock">
                  <p className="hc-greeting">
                    Good {timeLabel}, <em>{firstName}</em> 👋
                  </p>
                  <p className="hc-role">{roleLabel}</p>
                </div>
              </div>
              <InventoryButton label="Inventory" />
              <Drawer direction="top">
                <DrawerTrigger asChild>
                  <button className="hc-score-btn">
                    <Gauge size={14} />
                  </button>
                </DrawerTrigger>
                <DrawerContent>
                  <PredictedScoreCard />
                </DrawerContent>
              </Drawer>
            </div>

            <div className="hc-sep" />
          </>
        )}

        {/* XP bar — DEFAULT + LEVEL */}
        {showLevel && (
          <div className="hc-xp-row">
            <span className="hc-lvl-tag">Lv {level}</span>
            <div className="hc-bar-wrap">
              <div className="hc-track">
                <div className="hc-fill" style={{ width: `${barPct}%` }} />
              </div>
              <div className="hc-xp-label">
                <span>{totalXP.toLocaleString()} XP</span>
                <span>{xpToGo.toLocaleString()} to go</span>
              </div>
            </div>
          </div>
        )}

        {/* Rank + collapsible quests — DEFAULT + QUEST_COMPACT */}
        {showQuestCompact && (
          <>
            <div className="hc-rank-row" onClick={() => setOpen((o) => !o)}>
              <span className="hc-rank-emoji">{rank.emoji}</span>
              <div className="hc-rank-text">
                <p className="hc-rank-name">{rank.label}</p>
                <p className="hc-rank-progress-label">{nextLabel}</p>
              </div>
              <div className="hc-rank-right">
                {streak > 0 && (
                  <span className="hc-streak-pill">🔥 {streak}</span>
                )}
                {summary.claimableNodes > 0 && (
                  <span className="hc-chest-badge">
                    📦 {summary.claimableNodes}
                  </span>
                )}
                <ChevronDown
                  size={16}
                  className={`hc-chevron${open ? " open" : ""}`}
                />
              </div>
            </div>

            <div className={`hc-quests-wrap${open ? " open" : ""}`}>
              <div className="hc-quest-list">
                {activeQuests.length === 0 ? (
                  <p className="hc-empty">⚓ All caught up — keep sailing!</p>
                ) : (
                  activeQuests.map(({ node, arc }) => {
                    // Progress uses new field names
                    const pct = Math.min(
                      100,
                      Math.round((node.current_value / node.req_target) * 100),
                    );
                    const isClaimable = node.status === "claimable";
                    // Arc accent colour via theme generator — arc.accentColor no longer exists
                    const accentColor = generateArcTheme(arc).accent;
                    // Node icon derived from req_type — node.emoji no longer exists
                    const nodeEmoji = REQ_EMOJI[node.req_type] ?? "🏝️";
                    // Progress label derived from req_type — node.requirement.label no longer exists
                    const reqLabel = REQ_LABEL[node.req_type] ?? node.req_type;

                    return (
                      <div
                        key={node.node_id} // node_id replaces old id
                        className="hc-quest-row"
                        style={{ "--ac": accentColor } as React.CSSProperties}
                        onClick={() => !isClaimable && handleViewAll()}
                      >
                        <div
                          className={`hc-q-icon${isClaimable ? " claimable" : ""}`}
                        >
                          {isClaimable ? "📦" : nodeEmoji}
                        </div>
                        <div className="hc-q-body">
                          {/* node.name replaces old node.title */}
                          <p className="hc-q-name">{node.name ?? "—"}</p>
                          {isClaimable ? (
                            <p className="hc-q-claimable">✨ Ready to claim!</p>
                          ) : (
                            <p className="hc-q-sub">
                              {/* current_value / req_target replace old progress / requirement.target */}
                              {node.current_value}/{node.req_target} {reqLabel}{" "}
                              · {pct}%
                            </p>
                          )}
                        </div>
                        {isClaimable ? (
                          <button
                            className="hc-claim-btn"
                            onClick={(e) => {
                              e.stopPropagation();
                              handleClaim(node, arc.id);
                            }}
                          >
                            Open
                          </button>
                        ) : (
                          <ChevronRight size={14} color="#d1d5db" />
                        )}
                      </div>
                    );
                  })
                )}
              </div>
              <div className="hc-map-link" onClick={handleViewAll}>
                <Map size={13} /> View quest map
              </div>
            </div>
          </>
        )}
      </div>

      {claimingNode && (
        <ChestOpenModal
          node={claimingNode.node}
          claimResult={claimResult}
          onClose={handleChestClose}
        />
      )}
    </>
  );
};
