'use client';

import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/contexts/AuthContext';
import { useParlay } from '@/contexts/ParlayContext';
import { GradePill } from '@/components/GradeCard';
// Session 13 — The Slate is the new browse-first lead surface. The
// existing dashboard sections (Most Parlayed, Recent Reads) stay
// below as intelligence layers on top of the raw odds.
import Slate from '@/components/Slate';
// Session 55 — the self-learning loop's track record, live in the header.
import { AccuracyBadge, Skeleton, SkeletonList, TierRecord } from '@/components/vyndr';
// Session 57 (Phase 0) — honest per-sport empty-slate copy.
import { emptyStateCopy } from '@/lib/emptyState';
// Session 59 (work-order 2.3) — the real pipeline schedule for waiting states.
import { nextRunLabelET } from '@/lib/pipelineSchedule';
// DS2 (Design v2) — the never-empty hero engine: rank tonight's grades on a
// varying signal, and fall back to yesterday's PROVEN A-tier receipts so first
// paint ALWAYS proves the model (#1, #13, Part 6).
import { selectTopGrades, buildHeroReceipts } from '@/lib/slateAdapter';
// Wave 4A (Step 3) — OUTLOOK MODE: the "Today's games" empty branch is never a
// dead-end CTA; it shows yesterday's proven receipts + tomorrow's real schedule.
import { buildOutlook } from '@/lib/outlook';
import GradeBadge from '@/components/vyndr/GradeBadge';
import { currentAccessToken } from '@/lib/authToken';

type Sport = 'NBA' | 'MLB' | 'WNBA';

interface Game {
  id: string;
  away: string;
  home: string;
  start_time: string;
  sport: Sport;
  status: 'scheduled' | 'live' | 'final';
  prop_count?: number;
  ab_grade_count?: number;
  injury_note?: string;
}

// Session 24 — shape returned by the free ESPN schedule endpoint
// (/api/schedule/:sport), used as a fallback when the odds slate is empty.
interface ScheduleApiGame {
  id: string;
  homeTeam?: { name?: string | null; abbreviation?: string | null };
  awayTeam?: { name?: string | null; abbreviation?: string | null };
  gameTime?: string | null;
  status?: 'pre' | 'in' | 'post' | null;
}

interface TopGrade {
  player: string;
  stat: string;
  line: number;
  direction: 'over' | 'under';
  sport: Sport;
  grade: string;
  confidence?: number;
}

interface ParlayLegStat {
  player: string;
  stat: string;
  line: number;
  direction: 'over' | 'under';
  sport: Sport;
  grade?: string;
  parlay_count: number;
}

// DS2 — a PROVEN receipt (yesterday's settled A-tier hit) for the never-empty hero.
interface HeroReceipt {
  player: string;
  stat: string;
  line: number;
  side: string;
  grade: string;
  sport: string;
  outcome: string;
  actual: number | null;
  clvResult: string | null;
}

// The /api/ledger/model settled-row shape (subset we read for receipts).
interface ModelEntry {
  player_name?: string;
  sport?: string;
  stat?: string;
  line?: number;
  side?: string;
  grade?: string;
  outcome?: string | null;
  actual_value?: number | null;
  clv_result?: string | null;
}

interface RecentScan {
  id: string;
  player_name: string;
  stat: string;
  line: number;
  direction: 'over' | 'under';
  grade: string;
  sport: Sport;
  created_at: string;
}

const SPORT_COLOR: Record<Sport, string> = {
  NBA: '#E94B3C',
  MLB: '#1E90FF',
  WNBA: '#FFB347',
};

/**
 * Wave 4A (Step 3) — the dashboard OUTLOOK surface for an empty slate. Replaces
 * the old "NO SLATE" dead-end CTA: the month-aware header + yesterday's PROVEN
 * A-tier receipts + tomorrow's date-pinned schedule preview (all REAL,
 * always-available data — never an invented line). Self-fetches both; the
 * header always renders, so the grid is never blank.
 */
function DashboardOutlook({ sport }: { sport: Sport }) {
  const [settled, setSettled] = useState<ModelEntry[] | null>(null);
  const [tomorrow, setTomorrow] = useState<ScheduleApiGame[] | null>(null);

  useEffect(() => {
    let active = true;
    fetch(`/api/ledger/model?sport=${sport.toLowerCase()}&limit=80`)
      .then((r) => (r.ok ? r.json() : null))
      .then((d) => { if (active) setSettled(Array.isArray(d?.entries) ? d.entries : []); })
      .catch(() => { if (active) setSettled([]); });
    return () => { active = false; };
  }, [sport]);

  useEffect(() => {
    let active = true;
    // Tomorrow, ET — the schedule route is date-pinned (free / cached).
    const date = new Intl.DateTimeFormat('en-CA', { timeZone: 'America/New_York', year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date(Date.now() + 86_400_000));
    fetch(`/api/schedule/${sport.toLowerCase()}?date=${date}`)
      .then((r) => (r.ok ? r.json() : null))
      .then((d) => { if (active) setTomorrow((Array.isArray(d?.games) ? d.games : []).map((g: ScheduleApiGame) => ({ ...g, sport: sport.toLowerCase() }))); })
      .catch(() => { if (active) setTomorrow([]); });
    return () => { active = false; };
  }, [sport]);

  const outlook = buildOutlook({ gamesCount: 0, settledRows: settled || [], tomorrow: tomorrow || [] }) as { mode: string; receipts?: OutlookReceiptView[]; tomorrow?: OutlookGameView[] };
  const receipts: OutlookReceiptView[] = outlook.receipts ?? [];
  const preview: OutlookGameView[] = outlook.tomorrow ?? [];
  const { title, body } = emptyStateCopy(sport.toLowerCase());

  return (
    <div className="surface diagonal-cut tex-scan" style={{ padding: 24, display: 'grid', gap: 16 }}>
      <div>
        <p className="mono lbl" style={{ color: 'var(--amber)' }}>OUTLOOK</p>
        <h3 style={{ fontSize: 18, fontWeight: 700, marginTop: 4 }}>{title}</h3>
        <p style={{ color: 'var(--text-1)', fontSize: 14, maxWidth: 460 }}>{body}</p>
      </div>

      {receipts.length > 0 && (
        <div>
          <div className="mono lbl" style={{ color: 'var(--grade-a)', marginBottom: 10 }}>YESTERDAY · PROVEN</div>
          <div style={{ display: 'flex', gap: 10, overflowX: 'auto', paddingBottom: 4 }}>
            {receipts.map((r: OutlookReceiptView, i: number) => (
              <div key={`${r.player}-${r.stat}-${i}`} className="surface" style={{ minWidth: 190, padding: 14, border: '1px solid var(--grade-a)', borderRadius: 12 }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
                  <span className="mono" style={{ fontSize: 9, fontWeight: 800, letterSpacing: '0.1em', color: 'var(--grade-a)' }}>{String(r.sport).toUpperCase()}</span>
                  <GradeBadge grade={r.grade} size={28} glow />
                </div>
                <div style={{ fontSize: 14, fontWeight: 700, marginBottom: 3 }}>{r.player}</div>
                <p className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'capitalize', fontVariantNumeric: 'tabular-nums' }}>
                  {String(r.side).toUpperCase().startsWith('U') ? 'under' : 'over'} {r.line} {String(r.stat).replace(/_/g, ' ')}
                </p>
                <p className="mono" style={{ fontSize: 12, fontWeight: 800, color: 'var(--grade-a)', marginTop: 6, fontVariantNumeric: 'tabular-nums' }}>
                  &#10003; HIT{r.actual != null ? ` (${r.actual})` : ''}
                </p>
              </div>
            ))}
          </div>
        </div>
      )}

      {preview.length > 0 && (
        <div>
          <div className="mono lbl" style={{ color: 'var(--text-tertiary)', marginBottom: 10 }}>TOMORROW · SCHEDULE — lines post on the day</div>
          <div style={{ display: 'grid', gap: 8 }}>
            {preview.map((g: OutlookGameView) => (
              <div key={g.id} className="mono" style={{ display: 'flex', gap: 10, alignItems: 'baseline', fontSize: 13, flexWrap: 'wrap' }}>
                <span style={{ fontWeight: 700 }}>{g.away} @ {g.home}</span>
                {g.time && <span style={{ color: 'var(--text-tertiary)', fontSize: 12 }}>{formatTime(g.time)}</span>}
              </div>
            ))}
          </div>
        </div>
      )}

      <div>
        <a href="/ledger" className="btn-primary" style={{ padding: '10px 18px' }}>View the Ledger →</a>
      </div>
    </div>
  );
}

// Render-only views of the buildOutlook output (the JS adapter has no TS types).
interface OutlookReceiptView { player: string; stat: string; line: number; side: string; grade: string; sport: string; actual: number | null }
interface OutlookGameView { id: string; away: string; home: string; time: string | null }

export default function DashboardPage() {
  const router = useRouter();
  const { user, session, tier, scansRemaining, loading: authLoading } = useAuth();
  const { addLeg, open } = useParlay();

  const [sport, setSport] = useState<Sport>('NBA');
  const [games, setGames] = useState<Game[] | null>(null);
  const [topGrades, setTopGrades] = useState<TopGrade[] | null>(null);
  // DS2 — yesterday's PROVEN receipts, fetched only when tonight has no grades
  // yet (the never-empty hero fallback). null = not yet loaded.
  const [heroReceipts, setHeroReceipts] = useState<HeroReceipt[] | null>(null);
  const [mostParlayed, setMostParlayed] = useState<ParlayLegStat[] | null>(null);
  const [recentScans, setRecentScans] = useState<RecentScan[] | null>(null);
  // Session 49 — the user's primary sport tab + preferred books from prefs.
  const [primaryTab, setPrimaryTab] = useState<'all' | 'nba' | 'mlb' | 'wnba' | 'soccer'>('all');
  const [prefBooks, setPrefBooks] = useState<string[]>([]);

  // Gate
  useEffect(() => {
    if (!authLoading && !user) router.replace('/login?next=/dashboard');
  }, [authLoading, user, router]);

  // Session 49 — onboarding gate + dashboard personalization. New users
  // (created after onboarding shipped) who haven't completed it are sent to
  // /onboarding; everyone else's saved preferences set the default sport tab.
  // Never fires while auth is loading (would bounce unauthenticated users).
  useEffect(() => {
    if (authLoading || !user || !session?.access_token) return;
    let active = true;
    fetch('/api/preferences', { headers: { Authorization: `Bearer ${session.access_token}` } })
      .then((r) => (r.ok ? r.json() : null))
      .then((prefs) => {
        if (!active || !prefs) return;
        const createdAt = session.user?.created_at ? new Date(session.user.created_at).getTime() : 0;
        const ONBOARDING_CUTOFF = new Date('2026-06-19T00:00:00Z').getTime();
        const isNewUser = createdAt >= ONBOARDING_CUTOFF;
        if (prefs.onboarding_complete !== true && isNewUser) {
          router.replace('/onboarding');
          return;
        }
        if (Array.isArray(prefs.sports) && prefs.sports[0]) setPrimaryTab(prefs.sports[0]);
        if (Array.isArray(prefs.books)) setPrefBooks(prefs.books);
      })
      .catch(() => { /* prefs are best-effort — never block the dashboard */ });
    return () => { active = false; };
  }, [authLoading, user, session, router]);

  // Fetch slate when sport changes
  useEffect(() => {
    let cancelled = false;
    setGames(null);
    setTopGrades(null);
    setHeroReceipts(null);

    Promise.all([
      fetch(`/api/games/tonight?sport=${sport}`).then((r) => r.json()).catch(() => ({ games: [] })),
      fetch(`/api/props/top-graded?sport=${sport}`).then((r) => r.json()).catch(() => ({ props: [] })),
    ]).then(async ([gamesData, gradesData]) => {
      if (cancelled) return;
      let list: Game[] = Array.isArray(gamesData?.games) ? gamesData.games : [];

      // Session 24 — when the odds-backed slate is empty (off-day or
      // odds-api quota exhausted), fall back to the FREE ESPN schedule so
      // the dashboard still shows today's matchups instead of "NO SLATE".
      if (list.length === 0) {
        try {
          const sched = await fetch(`/api/schedule/${sport.toLowerCase()}`).then((r) => r.json());
          const schedGames = Array.isArray(sched?.games) ? sched.games : [];
          list = schedGames.map((sg: ScheduleApiGame) => ({
            id: sg.id,
            away: sg.awayTeam?.name || sg.awayTeam?.abbreviation || 'Away',
            home: sg.homeTeam?.name || sg.homeTeam?.abbreviation || 'Home',
            start_time: sg.gameTime || '',
            sport,
            status: sg.status === 'in' ? 'live' : sg.status === 'post' ? 'final' : 'scheduled',
          }));
        } catch { /* schedule unavailable too — leave list empty */ }
      }

      if (cancelled) return;
      setGames(list);
      setTopGrades(Array.isArray(gradesData?.props) ? gradesData.props.slice(0, 10) : []);
    });

    return () => {
      cancelled = true;
    };
  }, [sport]);

  // DS2 (#1, Part 6) — the NEVER-EMPTY hero fallback. When tonight's slate has
  // no graded props yet, pull the model's most-recent settled reads and keep
  // only the PROVEN A-tier hits (buildHeroReceipts). First paint always proves
  // the model works (the real 55-19 / 74% record). Best-effort, self-hiding.
  useEffect(() => {
    if (topGrades === null || topGrades.length > 0) return; // tonight has grades → no fallback needed
    let cancelled = false;
    fetch(`/api/ledger/model?sport=${sport.toLowerCase()}&limit=80`)
      .then((r) => (r.ok ? r.json() : null))
      .then((data) => {
        if (cancelled) return;
        const entries: ModelEntry[] = Array.isArray(data?.entries) ? data.entries : [];
        setHeroReceipts(buildHeroReceipts(entries, 8) as HeroReceipt[]);
      })
      .catch(() => { if (!cancelled) setHeroReceipts([]); });
    return () => { cancelled = true; };
  }, [topGrades, sport]);

  // Most parlayed + recent scans don't depend on sport
  useEffect(() => {
    fetch('/api/props/most-parlayed')
      .then((r) => r.json())
      .then((data) => setMostParlayed(Array.isArray(data?.props) ? data.props.slice(0, 5) : []))
      .catch(() => setMostParlayed([]));

    if (user) {
      const token = currentAccessToken();
      fetch('/api/user/recent-scans', {
        headers: token ? { Authorization: `Bearer ${token}` } : {},
      })
        .then((r) => r.json())
        .then((data) => setRecentScans(Array.isArray(data?.scans) ? data.scans.slice(0, 5) : []))
        .catch(() => setRecentScans([]));
    }
  }, [user]);

  if (authLoading || !user) {
    // DS1 (§4) — layout-matched skeleton, never a text wall. Mirrors the real
    // dashboard: header strip + a stack of slate cards.
    return (
      <section style={{ maxWidth: 1100, margin: '0 auto', padding: '24px 16px 120px' }} aria-busy="true">
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24, gap: 16 }}>
          <Skeleton height={30} width={220} radius={10} />
          <Skeleton height={30} width={120} radius={999} />
        </div>
        <SkeletonList count={5} height={92} />
      </section>
    );
  }

  const isFirstTimer = recentScans?.length === 0;
  const slateEmpty = games?.length === 0;

  return (
    <section style={{ maxWidth: 1100, margin: '0 auto', padding: '24px 16px 120px' }}>
      {/* Top welcome strip */}
      <header style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 24, gap: 16, flexWrap: 'wrap' }}>
        <div>
          <h1 style={{ fontSize: 26, fontWeight: 700, letterSpacing: '-0.02em' }}>
            Tonight&apos;s slate
          </h1>
          <p className="mono" style={{ fontSize: 12, color: 'var(--text-tertiary)', letterSpacing: '0.05em', marginTop: 4 }}>
            {new Date().toLocaleDateString([], { weekday: 'long', month: 'short', day: 'numeric' }).toUpperCase()}
          </p>
        </div>
        {/* Session 55 — the system's live track record. Self-hides while it has
            no data; reads "LEARNING" until the settled sample is honest. */}
        <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
          <AccuracyBadge variant="chip" />
        {tier === 'free' && scansRemaining != null && (
          <div
            className="mono"
            style={{
              padding: '8px 14px',
              borderRadius: 999,
              background: 'var(--bg-surface)',
              border: '1px solid var(--border)',
              fontSize: 12,
              color: scansRemaining <= 1 ? 'var(--grade-c)' : 'var(--text-secondary)',
            }}
          >
            {scansRemaining}/5 READS · MO
          </div>
        )}
        </div>
      </header>

      {/* Session 13 — Browse-first slate. Owns its own sport-tab UI,
          search, and inline grading. Renders ABOVE the existing
          intelligence sections (Top Graded / Most Parlayed / Recent
          Reads) which serve as supplementary surfaces.
          Session 59 (work-order 2.1) — the Slate's tabs are THE filter
          (URL-driven, ?sport=). The legacy duplicate tab row below is GONE;
          the supplementary sections follow the same selection via
          onTabChange. Don't re-add a second tablist. */}
      <Slate
        tier={tier}
        initialTab={primaryTab}
        preferredBooks={prefBooks}
        key={primaryTab}
        onTabChange={(t) => {
          if (t === 'nba') setSport('NBA');
          else if (t === 'mlb') setSport('MLB');
          else if (t === 'wnba') setSport('WNBA');
          // 'all' / 'soccer' → the legacy sections keep their last sport.
        }}
      />

      {/* Wave 3 (Addition 2) — the model's record BY GRADE TIER. Self-fetches
          /api/ledger/model (scoped to the selected sport) and fully self-hides
          until a tier has a settled read — no empty header. The SAME shared
          TierRecord that renders on /u and the ledger; the calibration
          gradient is the proof higher grades win more. */}
      <div style={{ marginTop: 28 }}>
        <TierRecord sport={sport} title="MODEL RECORD BY GRADE" dense />
      </div>

      {/* Top grades tonight — DS2 (#1, #13, Part 6). Tonight's grades RANKED on
          the varying signal (selectTopGrades: tier → confidence → edge) so the
          row isn't identical-weight noise; when tonight is empty we fall back to
          yesterday's PROVEN A-tier receipts so first paint ALWAYS proves the
          model. Only the truly-empty case shows the honest schedule copy. */}
      {(() => {
        const ranked = topGrades ? selectTopGrades(topGrades, 10) as TopGrade[] : [];
        const proofMode = topGrades !== null && ranked.length === 0 && (heroReceipts?.length ?? 0) > 0;
        return (
          <Section
            title="Top grades tonight"
            subtitle={proofMode ? 'No slate graded yet — the model’s most recent proven A-tier reads.' : 'The A-tier picks, ranked by confidence. Tap to read or add to parlay.'}
          >
            {topGrades === null ? (
              <SkeletonRow />
            ) : ranked.length > 0 ? (
              <div
                style={{
                  display: 'flex',
                  gap: 12,
                  overflowX: 'auto',
                  padding: '4px 4px 12px',
                  scrollSnapType: 'x mandatory',
                }}
              >
                {ranked.map((g, i) => (
                  <button
                    key={`${g.player}-${g.stat}-${i}`}
                    onClick={() => router.push(`/scan?sport=${g.sport}&player=${encodeURIComponent(g.player)}&stat=${g.stat}&line=${g.line}`)}
                    className="surface diagonal-cut surface-hover"
                    style={{
                      minWidth: 200,
                      padding: 16,
                      textAlign: 'left',
                      scrollSnapAlign: 'start',
                      cursor: 'pointer',
                      border: '1px solid var(--border)',
                      borderRadius: 16,
                      background: 'var(--bg-surface)',
                      color: 'inherit',
                      fontFamily: 'inherit',
                    }}
                  >
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 12 }}>
                      <SportPill sport={g.sport} />
                      <GradePill grade={g.grade} />
                    </div>
                    <h3 style={{ fontSize: 14, fontWeight: 600, marginBottom: 4 }}>{g.player}</h3>
                    <p className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'capitalize' }}>
                      {g.direction} {g.line} {g.stat.replace(/_/g, ' ')}
                    </p>
                    {/* #13 — surface the VARYING signal so ranked rows read as a
                        real ranking, not identical noise. */}
                    {g.confidence != null && (
                      <p className="mono" style={{ fontSize: 11, color: 'var(--text-tertiary)', letterSpacing: '0.04em', marginTop: 6, fontVariantNumeric: 'tabular-nums' }}>
                        {g.confidence}% CONF
                      </p>
                    )}
                  </button>
                ))}
              </div>
            ) : proofMode ? (
              // The never-empty proof: yesterday's PROVEN A-tier receipts.
              <div
                style={{
                  display: 'flex',
                  gap: 12,
                  overflowX: 'auto',
                  padding: '4px 4px 12px',
                  scrollSnapType: 'x mandatory',
                }}
              >
                {(heroReceipts || []).map((r, i) => (
                  <div
                    key={`${r.player}-${r.stat}-${i}`}
                    className="surface diagonal-cut"
                    style={{
                      minWidth: 210,
                      padding: 16,
                      scrollSnapAlign: 'start',
                      border: '1px solid var(--grade-a)',
                      borderRadius: 16,
                      background: 'var(--bg-surface)',
                    }}
                  >
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
                      <span className="mono" style={{ fontSize: 9, fontWeight: 800, letterSpacing: '0.1em', color: 'var(--grade-a)' }}>YESTERDAY · PROVEN</span>
                      <GradeBadge grade={r.grade} size={30} glow />
                    </div>
                    <h3 style={{ fontSize: 14, fontWeight: 700, marginBottom: 4 }}>{r.player}</h3>
                    <p className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'capitalize', fontVariantNumeric: 'tabular-nums' }}>
                      {String(r.side).toUpperCase().startsWith('U') ? 'under' : 'over'} {r.line} {String(r.stat).replace(/_/g, ' ')}
                    </p>
                    <p className="mono" style={{ fontSize: 12, fontWeight: 800, color: 'var(--grade-a)', marginTop: 8, fontVariantNumeric: 'tabular-nums' }}>
                      &#10003; HIT{r.actual != null ? ` (${r.actual})` : ''}
                      {/* Session 64 — legacy C4 CLV removed. It read row.clvResult,
                          derived from the overwritable closing_line (637/699 closing ==
                          locked), so "BEAT" was unreliable and "flat" mostly meant the
                          close was never captured. The verified directional badge is
                          Analyst+Desk on the ledger card; nothing is shown here. */}
                    </p>
                  </div>
                ))}
              </div>
            ) : heroReceipts === null ? (
              // tonight empty, fallback still loading → skeleton, never a text wall.
              <SkeletonRow />
            ) : (
              // Truly nothing yet — the honest schedule-derived waiting copy (QA.22).
              <p style={emptyCopy}>No grades yet. Grades post {nextRunLabelET() || 'on the next pipeline run'}.</p>
            )}
          </Section>
        );
      })()}

      {/* Tonight's games */}
      <Section title={`Today's ${sport} games`} subtitle={slateEmpty ? null : `${games?.length ?? 0} game${games?.length === 1 ? '' : 's'} today`}>
        {games === null ? (
          <SkeletonRow stacked />
        ) : games.length === 0 ? (
          // Wave 4A (Step 3) — OUTLOOK MODE: never a dead-end "NO SLATE" CTA.
          // Yesterday's proven receipts + tomorrow's real schedule fill the grid.
          <DashboardOutlook sport={sport} />
        ) : (
          <div style={{ display: 'grid', gap: 12 }}>
            {games.map((g, idx) => (
              <a
                key={g.id}
                href={`/game/${g.id}`}
                className={`surface surface-hover diagonal-cut animate-fade-up stagger-${(idx % 6) + 1}`}
                style={{
                  padding: 20,
                  textDecoration: 'none',
                  color: 'inherit',
                  display: 'grid',
                  gridTemplateColumns: '1fr auto',
                  gap: 12,
                  alignItems: 'center',
                  border: isFirstTimer && idx === 0 ? '1px solid var(--grade-a)' : '1px solid var(--border)',
                  boxShadow: isFirstTimer && idx === 0 ? '0 0 0 4px var(--accent-glow)' : 'none',
                }}
              >
                <div>
                  <div style={{ fontSize: 16, fontWeight: 600, marginBottom: 4 }}>
                    {g.away} @ {g.home}
                  </div>
                  <div className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)' }}>
                    {formatTime(g.start_time)}
                    {g.injury_note ? ` · ${g.injury_note}` : ' · Full squad'}
                    {g.ab_grade_count != null ? ` · ${g.ab_grade_count} A/B grades` : ''}
                  </div>
                </div>
                <div className="mono" style={{ fontSize: 11, color: 'var(--text-tertiary)', letterSpacing: '0.08em' }}>
                  {g.prop_count ?? '—'} props →
                </div>
              </a>
            ))}
          </div>
        )}
      </Section>

      {/* Most parlayed tonight — Session 26: auto-hide the whole section
          once loaded with no data. An empty "be the first" prompt on a
          fresh platform reads as dead space; show it only when there's
          real trending activity (or while still loading). */}
      {(mostParlayed === null || mostParlayed.length > 0) && (
      <Section title="Most parlayed tonight" subtitle="What other bettors are stacking." right={<span className="mono" style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>🔥 TRENDING</span>}>
        {mostParlayed === null ? (
          <SkeletonRow />
        ) : (
          <ul style={{ display: 'grid', gap: 8 }}>
            {mostParlayed.map((p, i) => (
              <li key={`${p.player}-${p.stat}-${i}`}
                className="surface"
                style={{
                  padding: 14,
                  display: 'grid',
                  gridTemplateColumns: '1fr auto auto',
                  gap: 12,
                  alignItems: 'center',
                }}
              >
                <div>
                  <div style={{ fontSize: 14, fontWeight: 600 }}>🔥 {p.player}</div>
                  <div className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'capitalize' }}>
                    {p.sport} · {p.direction} {p.line} {p.stat.replace(/_/g, ' ')}
                  </div>
                </div>
                {p.grade && <GradePill grade={p.grade} />}
                <button
                  className="btn-ghost"
                  style={{ padding: '6px 12px', fontSize: 12 }}
                  onClick={() => {
                    addLeg({
                      sport: p.sport,
                      player: p.player,
                      stat: p.stat,
                      line: p.line,
                      direction: p.direction,
                      grade: p.grade ?? 'B',
                      confidence: 55,
                    });
                    open();
                  }}
                >
                  + Parlay
                </button>
              </li>
            ))}
          </ul>
        )}
      </Section>
      )}

      {/* Recent scans OR first-timer onboarding */}
      <Section title={isFirstTimer ? 'Your first read' : 'Your recent reads'} subtitle={null}>
        {recentScans === null ? (
          <SkeletonRow />
        ) : recentScans.length === 0 ? (
          <div
            className="surface diagonal-cut-strong"
            style={{
              padding: 32,
              textAlign: 'center',
              border: '1px solid var(--accent-light)',
              background: 'var(--bg-elevated)',
            }}
          >
            <p className="mono" style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.08em', color: 'var(--grade-a)', marginBottom: 12 }}>
              WELCOME TO THE LEDGER
            </p>
            <h3 style={{ fontSize: 20, fontWeight: 700, marginBottom: 8 }}>
              {(games?.length ?? 0) > 0
                ? `Today's slate is loaded. ${games?.length} ${games?.length === 1 ? 'game' : 'games'} on the ${sport} board.`
                : 'Your ledger starts here.'}
            </h3>
            <p style={{ color: 'var(--text-secondary)', fontSize: 14, marginBottom: 20 }}>
              Pick a game and read your first prop — it&apos;s on us.
            </p>
            <a href="/scan" className="btn-primary" style={{ padding: '12px 24px' }}>
              Run a read →
            </a>
          </div>
        ) : (
          <ul style={{ display: 'grid', gap: 8 }}>
            {recentScans.map((s) => (
              <li key={s.id}
                className="surface"
                style={{
                  padding: 14,
                  display: 'grid',
                  gridTemplateColumns: '1fr auto',
                  gap: 12,
                  alignItems: 'center',
                }}
              >
                <div>
                  <div style={{ fontSize: 14, fontWeight: 600 }}>{s.player_name}</div>
                  <div className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'capitalize' }}>
                    {s.sport} · {s.direction} {s.line} {s.stat.replace(/_/g, ' ')}
                  </div>
                </div>
                <GradePill grade={s.grade} />
              </li>
            ))}
          </ul>
        )}
      </Section>
    </section>
  );
}

function Section({ title, subtitle, right, children }: { title: string; subtitle: string | null; right?: React.ReactNode; children: React.ReactNode }) {
  return (
    <section style={{ marginBottom: 40 }}>
      <header style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 12 }}>
        <div>
          <h2 style={{ fontSize: 16, fontWeight: 700, letterSpacing: '-0.01em' }}>{title}</h2>
          {subtitle && (
            <p className="mono" style={{ fontSize: 11, color: 'var(--text-tertiary)', letterSpacing: '0.05em', marginTop: 2 }}>
              {subtitle}
            </p>
          )}
        </div>
        {right}
      </header>
      {children}
    </section>
  );
}

function SportPill({ sport }: { sport: Sport }) {
  return (
    <span
      className="mono"
      style={{
        fontSize: 10,
        fontWeight: 700,
        padding: '2px 8px',
        borderRadius: 999,
        background: `${SPORT_COLOR[sport]}1F`,
        color: SPORT_COLOR[sport],
      }}
    >
      {sport}
    </span>
  );
}

function SkeletonRow({ stacked = false }: { stacked?: boolean }) {
  if (stacked) {
    return (
      <div style={{ display: 'grid', gap: 12 }}>
        {[0, 1, 2].map((i) => (
          <div
            key={i}
            style={{
              height: 80,
              borderRadius: 16,
              background: 'linear-gradient(90deg, var(--bg-surface) 0%, var(--bg-surface-hover) 50%, var(--bg-surface) 100%)',
              backgroundSize: '200% 100%',
              animation: 'shimmer 1.5s linear infinite',
            }}
          />
        ))}
      </div>
    );
  }
  return (
    <div style={{ display: 'flex', gap: 12 }}>
      {[0, 1, 2, 3].map((i) => (
        <div
          key={i}
          style={{
            minWidth: 200,
            height: 110,
            borderRadius: 16,
            background: 'linear-gradient(90deg, var(--bg-surface) 0%, var(--bg-surface-hover) 50%, var(--bg-surface) 100%)',
            backgroundSize: '200% 100%',
            animation: 'shimmer 1.5s linear infinite',
          }}
        />
      ))}
    </div>
  );
}

function formatTime(iso: string): string {
  try {
    return new Date(iso).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', timeZoneName: 'short' });
  } catch {
    return iso;
  }
}

const emptyCopy: React.CSSProperties = {
  padding: '24px 16px',
  textAlign: 'center',
  color: 'var(--text-secondary)',
  fontSize: 14,
  background: 'var(--bg-surface)',
  border: '1px solid var(--border)',
  borderRadius: 12,
};
