// MLB park factors (Session 15) — pin the membership list + assert
// the expected magnitude of the headline parks. Park factors shift
// year-to-year but the directional signal (Coors hot, Oracle cold)
// is stable; the test catches obvious typos.

const pf = require('../../src/data/parkFactors');

describe('parkFactors', () => {
  test('covers all 30 MLB teams', () => {
    const codes = Object.keys(pf.PARK_FACTORS);
    expect(codes.length).toBe(30);
  });

  test('every entry has hr/h/r as finite numbers', () => {
    for (const [code, vals] of Object.entries(pf.PARK_FACTORS)) {
      expect(typeof code).toBe('string');
      expect(code).toMatch(/^[A-Z]{2,3}$/);
      expect(Number.isFinite(vals.hr)).toBe(true);
      expect(Number.isFinite(vals.h)).toBe(true);
      expect(Number.isFinite(vals.r)).toBe(true);
    }
  });

  test('Coors Field is the most extreme HR park', () => {
    const hrs = Object.values(pf.PARK_FACTORS).map((p) => p.hr);
    const maxHr = Math.max(...hrs);
    expect(pf.PARK_FACTORS.COL.hr).toBe(maxHr);
    expect(pf.PARK_FACTORS.COL.hr).toBeGreaterThan(120);
  });

  test('Oracle Park (SF) suppresses HRs heavily', () => {
    expect(pf.PARK_FACTORS.SF.hr).toBeLessThan(95);
  });

  test('Coors also boosts hits and runs', () => {
    expect(pf.PARK_FACTORS.COL.h).toBeGreaterThan(100);
    expect(pf.PARK_FACTORS.COL.r).toBeGreaterThan(100);
  });

  test('most parks land within ±15% of neutral', () => {
    // Coors + SF are deliberate outliers; check the rest cluster.
    const codes = Object.keys(pf.PARK_FACTORS).filter((c) => c !== 'COL' && c !== 'SF');
    for (const code of codes) {
      const { hr, h, r } = pf.PARK_FACTORS[code];
      expect(hr).toBeGreaterThanOrEqual(85);
      expect(hr).toBeLessThanOrEqual(115);
      expect(h).toBeGreaterThanOrEqual(90);
      expect(h).toBeLessThanOrEqual(110);
      expect(r).toBeGreaterThanOrEqual(90);
      expect(r).toBeLessThanOrEqual(110);
    }
  });
});

describe('getParkFactor', () => {
  test('returns the row for known teams', () => {
    expect(pf.getParkFactor('NYY').hr).toBe(pf.PARK_FACTORS.NYY.hr);
    expect(pf.getParkFactor('COL').hr).toBeGreaterThan(120);
  });

  test('case-insensitive', () => {
    expect(pf.getParkFactor('nyy').hr).toBeGreaterThan(0);
    expect(pf.getParkFactor('Col')).toBe(pf.PARK_FACTORS.COL);
  });

  test('whitespace-tolerant', () => {
    expect(pf.getParkFactor(' COL ')).toBeTruthy();
  });

  test('returns null for unknown / empty', () => {
    expect(pf.getParkFactor('XYZ')).toBeNull();
    expect(pf.getParkFactor('')).toBeNull();
    expect(pf.getParkFactor(null)).toBeNull();
    expect(pf.getParkFactor(undefined)).toBeNull();
  });
});

describe('getParkFactorOrNeutral', () => {
  test('returns the row when known', () => {
    expect(pf.getParkFactorOrNeutral('NYY')).toBe(pf.PARK_FACTORS.NYY);
  });
  test('falls back to neutral 100s when unknown', () => {
    expect(pf.getParkFactorOrNeutral('XYZ')).toEqual({ hr: 100, h: 100, r: 100 });
    expect(pf.getParkFactorOrNeutral(null)).toEqual({ hr: 100, h: 100, r: 100 });
  });
});

describe('immutability', () => {
  test('PARK_FACTORS is frozen at the top level', () => {
    expect(Object.isFrozen(pf.PARK_FACTORS)).toBe(true);
  });
  test('NEUTRAL is frozen', () => {
    expect(Object.isFrozen(pf.NEUTRAL)).toBe(true);
  });
});
