// DS2 (Design v2) — Dashboard Slate Rebuild. The founder's named #1 rebuild:
// real entities per card, ONE bold hero, collapse pending-filler (#14), and the
// NEVER-EMPTY hero (#1, Part 6) — first paint ALWAYS proves the model. The pure
// engine lives in web/src/lib/slateAdapter.js (unit-tested directly); the .tsx
// surfaces are asserted against source (plain-JS Jest) — same pattern as
// ds4Billboards / vyndrParityQA.

const fs = require('fs');
const path = require('path');

const ROOT = path.join(__dirname, '..', '..');
const WEB = path.join(ROOT, 'web', 'src');
const read = (rel) => fs.readFileSync(path.join(WEB, rel), 'utf8');
const adapter = require('../../web/src/lib/slateAdapter');

// ── 1. selectTopGrades — ranking VARIES on a real signal (#13) ─────────────
describe('selectTopGrades — tier first, then the varying signal (confidence/edge)', () => {
  test('orders by grade tier, then confidence desc, then edge desc', () => {
    const grades = [
      { player: 'C-low', grade: 'B', confidence: 40, edge: 1 },
      { player: 'A-hiconf', grade: 'A', confidence: 90, edge: 2 },
      { player: 'A-loconf', grade: 'A', confidence: 55, edge: 9 },
      { player: 'Aplus', grade: 'A+', confidence: 10, edge: 0 },
    ];
    const out = adapter.selectTopGrades(grades, 10).map((g) => g.player);
    expect(out).toEqual(['Aplus', 'A-hiconf', 'A-loconf', 'C-low']);
  });

  test('ties on grade break on confidence — rows are NOT identical-order noise', () => {
    const grades = [
      { player: 'X', grade: 'B', confidence: 30 },
      { player: 'Y', grade: 'B', confidence: 80 },
      { player: 'Z', grade: 'B', confidence: 55 },
    ];
    expect(adapter.selectTopGrades(grades, 10).map((g) => g.player)).toEqual(['Y', 'Z', 'X']);
  });

  test('honors the limit and drops gradeless rows; empty in → empty out', () => {
    const grades = [{ grade: 'A', confidence: 1 }, { grade: 'A', confidence: 2 }, { confidence: 9 }];
    expect(adapter.selectTopGrades(grades, 1)).toHaveLength(1);
    expect(adapter.selectTopGrades([], 5)).toEqual([]);
    expect(adapter.selectTopGrades(null, 5)).toEqual([]);
  });
});

// ── 2. buildHeroReceipts — yesterday's A-tier settled HITS (#1) ────────────
describe('buildHeroReceipts — proven A-tier receipts, misses excluded', () => {
  const rows = [
    { player_name: 'Aaron Judge', stat: 'total_bases', line: 1.5, side: 'over', grade: 'A+', outcome: 'hit', actual_value: 3, clv_result: 'beat', sport: 'mlb' },
    { player_name: 'Missed Guy', stat: 'hits', line: 1.5, side: 'over', grade: 'A', outcome: 'miss', actual_value: 0 },
    { player_name: 'B Guy', stat: 'runs', line: 0.5, side: 'over', grade: 'B', outcome: 'hit', actual_value: 1 },
    { player_name: 'Aminus Hit', stat: 'rbi', line: 0.5, side: 'over', grade: 'A-', outcome: 'hit', actual_value: 2 },
  ];

  test('keeps only A-family grades that HIT (a miss never becomes proof)', () => {
    const out = adapter.buildHeroReceipts(rows, 8);
    const names = out.map((r) => r.player);
    expect(names).toContain('Aaron Judge');
    expect(names).toContain('Aminus Hit');
    expect(names).not.toContain('Missed Guy'); // A-tier but a MISS
    expect(names).not.toContain('B Guy'); // a hit but not A-tier
  });

  test('carries the real result (actual + grade) so the receipt is verifiable', () => {
    const judge = adapter.buildHeroReceipts(rows, 8).find((r) => r.player === 'Aaron Judge');
    expect(judge).toMatchObject({ grade: 'A+', outcome: 'hit', actual: 3 });
  });

  test('best-grade first, honors the limit, empty/absent → []', () => {
    expect(adapter.buildHeroReceipts(rows, 8)[0].grade).toBe('A+');
    expect(adapter.buildHeroReceipts(rows, 1)).toHaveLength(1);
    expect(adapter.buildHeroReceipts([], 5)).toEqual([]);
    expect(adapter.buildHeroReceipts(null, 5)).toEqual([]);
  });
});

// ── 3. heroFallbackState — the NEVER-EMPTY hero engine (#1, Part 6) ────────
describe('heroFallbackState — tonight wins, else receipts, never empty when proof exists', () => {
  const settled = [{ player_name: 'Judge', stat: 'total_bases', line: 1.5, side: 'over', grade: 'A', outcome: 'hit', actual_value: 3 }];

  test('tonight has graded props → mode tonight', () => {
    const s = adapter.heroFallbackState([{ player: 'P', grade: 'A', confidence: 70 }], settled, 10);
    expect(s.mode).toBe('tonight');
    expect(s.items).toHaveLength(1);
  });

  test('tonight EMPTY but yesterday has A-tier hits → mode receipts (proof, not empty)', () => {
    const s = adapter.heroFallbackState([], settled, 10);
    expect(s.mode).toBe('receipts');
    expect(s.items[0].player).toBe('Judge');
  });

  test('only truly empty when BOTH are absent', () => {
    expect(adapter.heroFallbackState([], [], 10).mode).toBe('empty');
    expect(adapter.heroFallbackState(null, null, 10).mode).toBe('empty');
  });
});

// ── 4. pendingSummary — collapse the dead "Grades post" repetition (#14) ──
describe('pendingSummary — six pending rows collapse to ONE line', () => {
  test('all-awaiting card → one summary counting props + players', () => {
    const strips = [
      { player: 'One', props: [{ stat: 'TB', line: 1.5, awaiting: true }, { stat: 'HR', line: 0.5, awaiting: true }] },
      { player: 'Two', props: [{ stat: 'Hits', line: 0.5, awaiting: true }] },
    ];
    const s = adapter.pendingSummary(strips);
    expect(s).not.toBeNull();
    expect(s.count).toBe(3);
    expect(s.players).toBe(2);
  });

  test('any graded prop present → no collapse (the card has real reads to show)', () => {
    const strips = [
      { player: 'One', props: [{ stat: 'TB', line: 1.5, grade: 'A' }] },
      { player: 'Two', props: [{ stat: 'Hits', line: 0.5, awaiting: true }] },
    ];
    expect(adapter.pendingSummary(strips)).toBeNull();
  });

  test('no awaiting props at all → null', () => {
    expect(adapter.pendingSummary([{ player: 'One', props: [] }])).toBeNull();
    expect(adapter.pendingSummary([])).toBeNull();
    expect(adapter.pendingSummary(null)).toBeNull();
  });
});

// ── 5. topReadForCard — the ONE bold hero per card (#2) ────────────────────
describe('topReadForCard — the single best graded prop leads the card', () => {
  const strips = [
    { player: 'Mid', team: 'NYY', props: [{ stat: 'Hits', line: 0.5, side: 'O', grade: 'B' }] },
    { player: 'Best', team: 'BOS', archetype: { primary: 'BOMBER' }, props: [{ stat: 'TB', line: 1.5, side: 'O', grade: 'A+' }] },
    { player: 'Dead', team: 'LAD', props: [{ stat: 'HR', line: 0.5, side: 'O', grade: 'A+', dead: true }] },
  ];

  test('picks the highest-tier live grade (dead reads never lead)', () => {
    const top = adapter.topReadForCard(strips);
    expect(top).not.toBeNull();
    expect(top.player).toBe('Best');
    expect(top.grade).toBe('A+');
    expect(top.stat).toBe('TB');
  });

  test('no graded props → null (nothing to promote)', () => {
    expect(adapter.topReadForCard([{ player: 'X', props: [{ stat: 'TB', line: 1, awaiting: true }] }])).toBeNull();
    expect(adapter.topReadForCard([])).toBeNull();
  });
});

// ── 6. GameCard.tsx — one hero + pending collapse + real entities ─────────
describe('GameCard.tsx wires the DS2 rebuild', () => {
  const src = read('components/vyndr/GameCard.tsx');

  test('leads with the real team entity (logo) + a team-colored accent edge', () => {
    expect(src).toContain('<TeamLogo');
    expect(src).toContain('accentColor(g.home.abbr');
    expect(src).toContain('borderLeft');
  });

  test('promotes ONE bold hero read via topReadForCard (large mono/tabular grade)', () => {
    expect(src).toContain('topReadForCard');
    expect(src).toMatch(/fontVariantNumeric:\s*'tabular-nums'/);
    // the hero grade badge is the largest on the card (strips use size 'sm').
    expect(src).toMatch(/size=\{48\}|size="lg"/);
  });

  test('collapses all-awaiting cards to ONE pending line (kills the #14 repetition)', () => {
    expect(src).toContain('pendingSummary');
    expect(src).toContain('nextRunLabelET');
    expect(src).toContain('props pending');
  });
});

// ── 7. dashboard/page.tsx — the never-empty hero + varying rankings ────────
describe('dashboard Top grades tonight — proof-first, ranked, honest', () => {
  const src = read('app/dashboard/page.tsx');

  test('uses the never-empty engine (heroFallbackState / buildHeroReceipts)', () => {
    expect(src).toMatch(/heroFallbackState|buildHeroReceipts/);
  });

  test('ranks tonight grades by the varying signal (selectTopGrades) and shows confidence', () => {
    expect(src).toContain('selectTopGrades');
    expect(src).toContain('confidence');
  });

  test('falls back to yesterday PROVEN A-tier receipts (with the settled result)', () => {
    expect(src).toContain('/api/ledger/model');
    expect(src).toMatch(/HIT|PROVEN|YESTERDAY/);
  });

  test('still keeps the honest schedule-derived waiting copy for the truly-empty case (QA.22)', () => {
    expect(src).toContain('nextRunLabelET');
  });
});
