'use client';

import { useCallback, useEffect, useState } from 'react';
import Link from 'next/link';
import { GradePill } from '@/components/GradeCard';
import { groupIntoLadders } from '@/lib/playerGrouping';
import { gradeColor } from '@/lib/vyndrTokens';
import { useAuth } from '@/contexts/AuthContext';
import { Skeleton, EmptyState, ArchetypeBadge, BookWordmark, TierRecord } from '@/components/vyndr';
import { clvMode, CLV_FLAT_LINE } from '@/lib/clvDisplay';
import ClvBadge from '@/components/vyndr/ClvBadge';

/**
 * The Ledger (Session 58, Phase 1) — the truth surface, now backed by the
 * persistent `ledger_entries` table.
 *
 *   MY READS — the user's own scans, written the moment a scan completes.
 *   MODEL    — the PUBLIC pipeline record (every pre-grade, misses included).
 *
 * DATA SEMANTICS: lines/odds/books shown here are real captured book values
 * (mono, rendered as fact). model_value is VYNDR output and always carries
 * the MODEL label. The aggregate header NEVER renders a percentage under 20
 * settles — it shows the building record honestly instead.
 */

type Tab = 'mine' | 'model';
type SportFilter = 'ALL' | 'NBA' | 'MLB' | 'WNBA';
type TierFilter = 'ALL' | 'A' | 'B' | 'C' | 'D';

interface LedgerRow {
  id: string;
  player_name: string;
  sport: string;
  stat: string;
  line: number;
  side: 'over' | 'under';
  locked_odds?: string | null;
  book?: string | null;
  grade: string;
  edge?: number | null;
  confidence?: number | null;
  model_value?: number | null;
  graded_at: string;
  game_date: string;
  closing_line?: number | null;
  clv?: number | null;
  clv_result?: 'beat' | 'faded' | 'flat' | null;
  outcome?: 'hit' | 'miss' | 'push' | null;
  actual_value?: number | null;
  revised_from_grade?: string | null;
  // DS5 (Part 5) — the player's locked archetype, when the pipeline supplies it.
  // Optional + self-hiding: absent beats fabricated.
  archetype?: string | null;
}

interface TierRecord { settled: number; hits: number; misses: number; hit_pct: number | null }
// S6 (A1 board) — settled-CLV distribution bucket. Server-side only when the
// n≥20 gate passes (null below — the gate lives in getModelAggregate).
interface ClvBucket { label: string; count: number; side: 'beat' | 'faded' | 'flat' }
interface ModelAggregate {
  settled: number;
  hits: number;
  misses: number;
  pushes: number;
  hit_pct: number | null;
  clv_sample: number;
  clv_beat: number;
  beat_close_pct: number | null;
  pending: number;
  min_sample?: number;
  // Session 60 (5.5) — calibration by grade tier (n≥20 rule per tier).
  by_tier?: Record<string, TierRecord>;
  clv_distribution?: ClvBucket[] | null;
}

const SPORT_COLOR: Record<string, string> = {
  nba: '#E94B3C',
  mlb: '#1E90FF',
  wnba: '#FFB347',
  soccer: '#7BC96F',
};

export default function LedgerPage() {
  const { session } = useAuth();
  const [tab, setTab] = useState<Tab>('mine');
  const [sport, setSport] = useState<SportFilter>('ALL');
  const [tier, setTier] = useState<TierFilter>('ALL');
  const [rows, setRows] = useState<LedgerRow[] | null>(null);
  const [aggregate, setAggregate] = useState<ModelAggregate | null>(null);
  const [minSample, setMinSample] = useState(20);

  const load = useCallback(async () => {
    setRows(null);
    const params = new URLSearchParams();
    if (sport !== 'ALL') params.set('sport', sport.toLowerCase());
    if (tier !== 'ALL') params.set('tier', tier);
    try {
      if (tab === 'mine') {
        const token = session?.access_token;
        if (!token) { setRows([]); return; }
        const data = await fetch(`/api/ledger/mine?${params}`, {
          headers: { Authorization: `Bearer ${token}` },
        }).then((r) => r.json());
        setRows(Array.isArray(data?.entries) ? data.entries : []);
      } else {
        const data = await fetch(`/api/ledger/model?${params}`).then((r) => r.json());
        setRows(Array.isArray(data?.entries) ? data.entries : []);
        setAggregate(data?.aggregate ?? null);
        if (Number(data?.min_sample) > 0) setMinSample(Number(data.min_sample));
      }
    } catch {
      setRows([]);
    }
  }, [tab, sport, tier, session]);

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

  return (
    <section style={{ maxWidth: 1100, margin: '0 auto', padding: '32px 16px 120px' }}>
      <header style={{ marginBottom: 24 }}>
        <h1 style={{ fontSize: 32, fontWeight: 700, letterSpacing: '-0.03em', marginBottom: 6 }}>
          The Ledger.
        </h1>
        <p style={{ color: 'var(--text-secondary)', fontSize: 16 }}>
          Every grade. Every result. No hiding. No deleting.
        </p>
      </header>

      {/* Tabs */}
      <div role="tablist" aria-label="Ledger view" style={{ display: 'flex', gap: 4, marginBottom: 20, borderBottom: '1px solid var(--border)' }}>
        {([['mine', 'MY READS'], ['model', 'MODEL']] as [Tab, string][]).map(([id, label]) => {
          const active = tab === id;
          return (
            <button
              key={id}
              role="tab"
              aria-selected={active}
              onClick={() => setTab(id)}
              className="mono"
              style={{
                padding: '12px 20px', background: 'transparent', border: 'none',
                borderBottom: `2px solid ${active ? 'var(--g-a, #00D4A0)' : 'transparent'}`,
                color: active ? 'var(--text-primary)' : 'var(--text-secondary)',
                fontWeight: 700, fontSize: 12, letterSpacing: '0.08em', cursor: 'pointer', marginBottom: -1,
              }}
            >
              {label}
            </button>
          );
        })}
      </div>

      {/* MODEL aggregate header — never a percentage under min_sample. */}
      {tab === 'model' && aggregate && (
        <ModelHeader agg={aggregate} minSample={minSample} />
      )}

      {/* Filters */}
      <div style={{ display: 'flex', gap: 16, marginBottom: 16, flexWrap: 'wrap' }}>
        <FilterRow label="Sport" value={sport} onChange={(v) => setSport(v as SportFilter)} options={['ALL', 'NBA', 'MLB', 'WNBA']} />
        <FilterRow label="Grade" value={tier} onChange={(v) => setTier(v as TierFilter)} options={['ALL', 'A', 'B', 'C', 'D']} />
      </div>

      {rows === null ? (
        // DS1 (§4) — grid of card skeletons matching the ledger's real layout.
        <div style={{ display: 'grid', gap: 12, gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))' }} aria-busy="true">
          {Array.from({ length: 6 }).map((_, i) => (
            <Skeleton key={i} height={132} radius={14} />
          ))}
        </div>
      ) : rows.length === 0 ? (
        <EmptyLedger tab={tab} />
      ) : (
        <div style={{ display: 'grid', gap: 12, gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))' }}>
          {groupIntoLadders(rows).map((g, i) => (
            <LedgerCard key={(g.primary as LedgerRow).id} row={g.primary as LedgerRow} ladder={g.ladder as LedgerRow[]} index={i} />
          ))}
        </div>
      )}
    </section>
  );
}

/** S6 (A1 board) · reframed DS4 — settled-CLV display. The n≥20 gate lives
 *  in the server (clv_distribution set). DESIGN-SPEC Part 3 #16: a near-flat
 *  distribution renders as one giant bar + six empty stubs — it reads as
 *  BROKEN. When CLV is flat we SAY so in one confident VOICE line (the record
 *  is the best data on the site; it must look the most premium, never errored)
 *  and show the bars only when there is real spread. */
function ClvDistribution({ agg }: { agg: ModelAggregate }) {
  const dist = agg.clv_distribution;
  const { mode } = clvMode(dist as Parameters<typeof clvMode>[0]);
  if (mode === 'none') return null;

  if (mode === 'flat') {
    // Flat CLV is a FEATURE, not an error: the model grades outcomes, not the
    // close. State it plainly — premium, confident, no broken histogram.
    return (
      <div style={{ marginTop: 14, paddingTop: 12, borderTop: '1px solid var(--border)' }}>
        <div className="mono" style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: '0.1em', color: 'var(--text-tertiary)', marginBottom: 6 }}>
          CLOSING-LINE VALUE
        </div>
        <p className="mono" style={{ fontSize: 14, fontWeight: 600, color: 'var(--text-primary)', margin: 0, letterSpacing: '0.01em' }}>
          {CLV_FLAT_LINE}
        </p>
      </div>
    );
  }

  const distArr = dist as ClvBucket[];
  const total = distArr.reduce((n, b) => n + b.count, 0);
  if (total === 0) return null;
  const max = Math.max(...distArr.map((b) => b.count));
  const color = (side: ClvBucket['side']) =>
    side === 'beat' ? 'var(--g-a, #00D4A0)' : side === 'faded' ? 'var(--miss, #FF6B6B)' : 'var(--text-tertiary)';
  return (
    <div style={{ marginTop: 14, paddingTop: 12, borderTop: '1px solid var(--border)' }}>
      <div className="mono" style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: '0.1em', color: 'var(--text-tertiary)', marginBottom: 8 }}>
        CLV DISTRIBUTION · {total} SETTLED
      </div>
      <div style={{ display: 'flex', gap: 8, alignItems: 'flex-end', maxWidth: 420 }}>
        {distArr.map((b) => (
          <div key={b.label} title={`${b.label}: ${b.count}`} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4, minWidth: 0 }}>
            <span className="mono" style={{ fontSize: 10, fontVariantNumeric: 'tabular-nums', color: b.count > 0 ? 'var(--text-secondary)' : 'var(--text-tertiary)' }}>
              {b.count}
            </span>
            <div
              aria-label={`${b.label}: ${b.count} settled`}
              style={{
                width: '100%',
                height: Math.max(3, Math.round((b.count / max) * 40)),
                background: b.count > 0 ? color(b.side) : 'var(--border)',
                borderRadius: 2,
                opacity: b.count > 0 ? 0.9 : 0.6,
              }}
            />
            <span className="mono" style={{ fontSize: 8.5, letterSpacing: '0.02em', color: 'var(--text-tertiary)', whiteSpace: 'nowrap' }}>
              {b.label}
            </span>
          </div>
        ))}
      </div>
    </div>
  );
}

function ModelHeader({ agg, minSample }: { agg: ModelAggregate; minSample: number }) {
  const ready = agg.settled >= minSample && agg.hit_pct != null;
  return (
    <div
      className="surface diagonal-cut"
      style={{ padding: 20, marginBottom: 20, border: `1px solid ${ready ? 'var(--g-a, #00D4A0)' : 'var(--border)'}` }}
    >
      {ready ? (
        <div style={{ display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'baseline' }}>
          <span className="mono" style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', color: 'var(--text-tertiary)' }}>
            MODEL · LAST 30D
          </span>
          <span className="mono" style={{ fontSize: 20, fontWeight: 800, color: 'var(--g-a, #00D4A0)' }}>
            {agg.hits}-{agg.misses} · {agg.hit_pct}% HIT
          </span>
          {agg.beat_close_pct != null && (
            <span className="mono" style={{ fontSize: 20, fontWeight: 800, color: 'var(--text-primary)' }}>
              {agg.beat_close_pct}% BEAT CLOSE
            </span>
          )}
          <span className="mono" style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>
            {agg.pending} pending
          </span>
        </div>
      ) : (
        <div>
          <p className="mono" style={{ fontSize: 13, fontWeight: 800, letterSpacing: '0.1em', color: 'var(--amber, #FFB347)', marginBottom: 6 }}>
            RECORD BUILDING
          </p>
          <p style={{ fontSize: 14, color: 'var(--text-secondary)' }}>
            Every read settles here, misses included.{' '}
            <span className="mono" style={{ color: 'var(--text-primary)' }}>
              {agg.pending} read{agg.pending === 1 ? '' : 's'} pending settlement
            </span>
            {agg.settled > 0 && (
              <span className="mono" style={{ color: 'var(--text-tertiary)' }}> · {agg.settled} settled</span>
            )}
          </p>
        </div>
      )}
      <ClvDistribution agg={agg} />
      {/* Wave 3 (Addition 2) — the ONE shared record-by-grade-tier table,
          identical here, on the dashboard, and on /u. */}
      <TierRecord byTier={agg.by_tier} minSample={minSample} />
      {/* Wave 5A (D2) — the shareable house/model profile. Same record, a
          public page you can hand a partner. */}
      <div style={{ marginTop: 14, textAlign: 'right' }}>
        <Link
          href="/u/vyndr"
          className="mono"
          style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.08em', color: 'var(--g-a, #00D4A0)', textDecoration: 'none' }}
        >
          VIEW AS PUBLIC PAGE →
        </Link>
      </div>
    </div>
  );
}

function OutcomeChip({ row }: { row: LedgerRow }) {
  if (!row.outcome) {
    return <span className="mono" style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>PENDING</span>;
  }
  const color = row.outcome === 'hit' ? 'var(--g-a, #00D4A0)'
    : row.outcome === 'miss' ? 'var(--miss, #FF6B6B)' : 'var(--text-secondary)';
  const mark = row.outcome === 'hit' ? '✓ HIT' : row.outcome === 'miss' ? '✕ MISS' : '– PUSH';
  return (
    <span className="mono" style={{ fontSize: 12, fontWeight: 700, color }}>
      {mark}{row.actual_value != null ? ` (${row.actual_value})` : ''}
    </span>
  );
}

function LedgerCard({ row, index, ladder }: { row: LedgerRow; index: number; ladder?: LedgerRow[] }) {
  const sportColor = SPORT_COLOR[row.sport] || 'var(--text-secondary)';
  const rungs = Array.isArray(ladder) ? ladder : [row];
  const isLadder = rungs.length > 1;
  return (
    <article className={`surface diagonal-cut animate-fade-up stagger-${(index % 6) + 1}`} style={{ padding: 12 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 5 }}>
        <span className="mono" style={{ fontSize: 10, fontWeight: 700, padding: '2px 8px', borderRadius: 999, background: `${sportColor}1F`, color: sportColor }}>
          {row.sport.toUpperCase()}
        </span>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
          {/* Phase 2.5 — a revised grade is public, never silent. */}
          {row.revised_from_grade && (
            <span className="mono" style={{ fontSize: 11, color: 'var(--text-tertiary)', textDecoration: 'line-through' }}>
              {row.revised_from_grade}
            </span>
          )}
          <GradePill grade={row.grade} />
        </span>
      </div>
      <h3 style={{ fontSize: 14, fontWeight: 600, marginBottom: 2 }}>{row.player_name}</h3>
      {/* Part 5 — the archetype glyph+chip, propagated (self-hides when absent).
          showDesc renders INLINE (same row), so it costs no vertical height —
          kept for its one-line meaning. P2-10 density comes from the tighter
          padding/margins, not from stripping this. */}
      {row.archetype && (
        <div style={{ marginBottom: 4 }}>
          <ArchetypeBadge archetype={row.archetype} size="sm" showDesc />
        </div>
      )}
      {/* P0-3 — ONE card per player+market. Multiple lines nest as a ladder of
          rungs (each its own side/line + grade) instead of N separate cards. */}
      {isLadder ? (
        <>
          <p className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'capitalize', marginBottom: 6 }}>
            {row.stat.replace(/_/g, ' ')} <span style={{ color: 'var(--text-tertiary)' }}>· {rungs.length} lines</span>
          </p>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 8 }}>
            {rungs.map((rung) => (
              <span key={rung.id} className="mono" style={{ fontSize: 10.5, display: 'inline-flex', alignItems: 'center', gap: 5, padding: '3px 8px', borderRadius: 6, background: 'var(--bg-2)', border: '1px solid var(--border)' }}>
                {rung.side}{rung.line}
                <span style={{ fontWeight: 800, color: gradeColor(rung.grade) }}>{rung.grade}</span>
              </span>
            ))}
          </div>
        </>
      ) : (
        <p className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'capitalize', marginBottom: 4 }}>
          {row.side} {row.line} {row.stat.replace(/_/g, ' ')}
        </p>
      )}
      <p className="mono" style={{ fontSize: 11, color: 'var(--text-tertiary)', marginBottom: 8 }}>
        {row.book ? <BookWordmark book={row.book} size={11} /> : '—'}{row.locked_odds ? ` · ${row.locked_odds}` : ''} · {row.game_date}
        {/* model_value is MODEL output — always labeled, never blended with market numbers. */}
        {row.model_value != null && <span> · MODEL {row.model_value}</span>}
      </p>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 8, flexWrap: 'wrap' }}>
        <OutcomeChip row={row} />
        {/* Session 64 — the RESOLVED-AT-CLOSE chapter. Three time states on this
            row read distinctly: entry (locked_odds, at-grade) · close (this badge,
            at-close) · outcome (hit/miss, final). The RESULT stays the hero; this
            is supporting context. Absent dclv renders NOTHING — not a "flat",
            which is the legacy chip's lie in subtler form. */}
        <ClvBadge read={row as unknown as Record<string, unknown>} />
      </div>
    </article>
  );
}

function EmptyLedger({ tab }: { tab: Tab }) {
  // DS5 (#20) — the ONE unified empty surface, inline variant.
  return tab === 'mine' ? (
    <EmptyState
      inline
      wordmark={false}
      code="LEDGER EMPTY"
      title="No reads yet."
      message="Read your first prop to start building your Ledger. Every grade lands here the moment it completes — and settles against the real result."
      actions={[{ label: 'Read a Prop →', href: '/scan', primary: true }]}
    />
  ) : (
    <EmptyState
      inline
      wordmark={false}
      code="MODEL RECORD BUILDING"
      title="The record starts with the next pipeline run."
      message="Every pre-graded prop lands here — hits, misses, pushes, and closing-line value. Nothing is deleted."
    />
  );
}

function FilterRow({ label, value, onChange, options }: { label: string; value: string; onChange: (v: string) => void; options: string[] }) {
  return (
    <div>
      <span className="mono" style={{ fontSize: 11, color: 'var(--text-tertiary)', letterSpacing: '0.08em', marginRight: 12 }}>
        {label.toUpperCase()}
      </span>
      <span style={{ display: 'inline-flex', gap: 4 }}>
        {options.map((o) => {
          const active = o === value;
          return (
            <button
              key={o}
              onClick={() => onChange(o)}
              className={active ? 'btn-primary' : 'btn-ghost'}
              style={{ padding: '6px 12px', fontSize: 11 }}
            >
              {o}
            </button>
          );
        })}
      </span>
    </div>
  );
}
