'use strict';

/**
 * PARK BASE — the SOURCE-PLUGGABLE environment coefficient (Layer 3, Step 4b).
 *
 * One slot, two possible sources, identical output shape:
 *
 *   'public'  — the static FanGraphs table (src/data/parkFactors.js, S15).
 *               COMMODITY. A stable public number anyone can look up.
 *   'derived' — our own multi-season derivation (src/services/parkFactors.js,
 *               S73). Not yet nominated: it needs the game-level accrual that
 *               `gameContext` starts collecting this session.
 *
 * The pluggability is the point. When the self-derived factors clear their
 * sample floor and beat the public base on the instrument, they swap into this
 * same slot — no rearchitecting downstream, because everything above consumes
 * `resolveParkBase()` and never a source directly.
 *
 * ── THE HONESTY LINE ─────────────────────────────────────────────────────
 * The public base is COMMODITY. It must never be marketed or described as a
 * proprietary park factor — it is a number anyone can read off FanGraphs. The
 * proprietary claim is reserved for the self-derived, mechanism-conditioned
 * version, and only once it beats this base on the settled ledger.
 * `PROVENANCE` below carries that label so any surface rendering a park effect
 * can state which it is.
 */

const publicTable = require('../data/parkFactors');

const PROVENANCE = Object.freeze({
  public: {
    source: 'fangraphs_public',
    label: 'PUBLIC PARK FACTOR',
    proprietary: false,
    note: 'Commodity: a public number. Not a VYNDR derivation.',
  },
  derived: {
    source: 'vyndr_derived',
    label: 'VYNDR PARK FACTOR',
    proprietary: true,
    note: 'Self-derived from captured game context. Nominated only after it beats the public base.',
  },
});

/**
 * Parks whose PUBLIC factor is itself thin or unstable — new builds, relocated
 * clubs, temporary venues. A public number for a park with one season behind it
 * is no more trustworthy than our own would be, so it is HONEST-ABSENT rather
 * than emitted with false confidence.
 *
 * ATH: the Athletics' temporary Sacramento venue (2025–). SAC/LV as the club
 * moves. Extend this list when a park opens or a club relocates — the cost of a
 * missing entry is a confident factor for a stadium nobody has data on.
 */
const UNSTABLE_PUBLIC = Object.freeze(new Set(['ATH', 'OAK', 'SAC', 'LV']));

/** Domes + retractable roofs — reused from the derived service so the two
 *  sources agree on which venues weather cannot touch. */
const { DOME_VENUES } = require('./parkFactors');
const DOME_TEAMS = Object.freeze(new Set(['TB', 'TOR', 'ARI', 'HOU', 'MIL', 'TEX', 'MIA', 'SEA']));

const num = (v) => {
  if (v == null || v === '') return null;
  const n = typeof v === 'number' ? v : Number(v);
  return Number.isFinite(n) ? n : null;
};

/** The public table is indexed to 100. The composable architecture wants a
 *  multiplier around 1.0, so 128 → 1.28. */
const toMultiplier = (indexed) => {
  const n = num(indexed);
  return n == null || n <= 0 ? null : Math.round((n / 100) * 1000) / 1000;
};

/**
 * resolveParkBase({ teamAbbr, venueName, source, derived }) — the ONE accessor.
 *
 * Returns the same record shape whichever source answers, so callers never
 * branch on provenance:
 *   { state, hr_base, run_base, venue, weather_na, provenance, source }
 * with `state` one of 'present' | 'absent' — and `weather_na` orthogonal to
 * both, exactly as the S73 derivation defined it (a dome's park factor still
 * applies; the flag only tells the weather arc to stand down).
 */
function resolveParkBase({ teamAbbr, venueName, source = 'public', derived = null } = {}) {
  const abbr = String(teamAbbr || '').toUpperCase();
  const weatherNa = DOME_TEAMS.has(abbr) || (venueName ? DOME_VENUES.has(venueName) : false);

  const absent = (reason, prov) => ({
    state: 'absent', hr_base: null, run_base: null,
    venue: venueName || abbr || null, weather_na: weatherNa,
    provenance: prov, source: prov.source, reason,
  });

  // ── self-derived, when it has been nominated ──────────────────────────
  if (source === 'derived') {
    const prov = PROVENANCE.derived;
    if (!derived || derived.state !== 'present') {
      return absent('self-derived factor not available for this venue', prov);
    }
    return {
      state: 'present',
      hr_base: num(derived.hr_base),
      run_base: num(derived.run_base),
      venue: derived.venue || venueName || null,
      weather_na: derived.weather_na ?? weatherNa,
      provenance: prov, source: prov.source,
      games: derived.games ?? null, seasons: derived.seasons ?? null,
      regime_start: derived.regime_start ?? null,
      reason: null,
    };
  }

  // ── public (commodity) base ───────────────────────────────────────────
  const prov = PROVENANCE.public;
  if (!abbr) return absent('no team/venue to resolve', prov);
  if (UNSTABLE_PUBLIC.has(abbr)) {
    // Even the PUBLIC number is thin here — a new or temporary park. Emitting
    // it would dress a shaky number as a settled one.
    return absent('public factor is itself thin/unstable for this venue', prov);
  }
  const row = publicTable.getParkFactor(abbr);
  if (!row) return absent('no public factor for this team', prov);

  const hr = toMultiplier(row.hr);
  const run = toMultiplier(row.r);
  if (hr == null && run == null) return absent('public factor unparseable', prov);

  return {
    state: 'present',
    hr_base: hr, run_base: run,
    venue: venueName || abbr, weather_na: weatherNa,
    provenance: prov, source: prov.source, reason: null,
  };
}

module.exports = {
  resolveParkBase,
  PROVENANCE,
  UNSTABLE_PUBLIC,
  DOME_TEAMS,
  __internals: { toMultiplier, num },
};
