/* ============================================================
   VYNDR — RECORD BY GRADE TIER (Wave 3, Addition 2).
   Plain CommonJS so the TierRecord.tsx component imports it AND
   the Jest suite can require it directly (no TS/Babel transform).

   The credibility of the model IS the tier calibration: A+ wins
   more than A, A more than B, and so on. A single blended "67%"
   hides that gradient. This module turns a `by_tier` map (from
   ledgerService.getModelAggregate) into ordered, display-ready
   rows so EVERY surface (dashboard, /u, ledger) renders the same
   honest table.

   HONESTY RULES (do not soften):
   - The W-L record (counts) is shown ALWAYS, even at small n —
     an honest 3-1 beats hiding it.
   - The hit-% is shown ONLY when that tier's `hit_pct != null`
     (the n≥20 gate is already applied UPSTREAM in
     getModelAggregate — never re-derive it here). Below the gate:
     "RECORD BUILDING · N settled".
   - A+ stands alone; A/B/C/D/F bucket by first letter (mirrors
     outcomeService + getModelAggregate's `tierOf`).
   ============================================================ */

/* Canonical render order. A+ is its own bucket; the rest are
   first-letter buckets. Mirrors getModelAggregate.tierOf. */
const TIER_ORDER = ['A+', 'A', 'B', 'C', 'D', 'F'];

/* A-tier (A+ / A) is the only bucket that earns the green edge
   color — "GLOW RESERVED FOR A-TIER ONLY" (design reference §05).
   Everything below A stays neutral/muted; red is reserved for
   settled-negative outcomes, never a tier label. */
function isEdgeTier(tier) {
  return tier === 'A+' || tier === 'A';
}

/* One tier's display row. `hitPct` is passed through verbatim —
   null means "below the n≥20 gate" and the row renders BUILDING. */
function buildTierRow(tier, bucket, minSample) {
  const settled = Number(bucket && bucket.settled) || 0;
  const hits = Number(bucket && bucket.hits) || 0;
  const misses = Number(bucket && bucket.misses) || 0;
  const pushes = Number(bucket && bucket.pushes) || 0;
  // hit_pct is the gated value from the server — strict null check
  // (Number(null) === 0 would fabricate a 0% record).
  const hitPct = bucket && bucket.hit_pct != null ? Number(bucket.hit_pct) : null;
  const ready = hitPct != null;
  return {
    tier,
    settled,
    hits,
    misses,
    pushes,
    record: `${hits}-${misses}`,
    hitPct,
    ready,
    edge: isEdgeTier(tier),
    // BUILDING copy the UI shows in place of a small-sample %.
    buildingLabel: `RECORD BUILDING · ${settled} settled`,
    minSample: Number(minSample) || 20,
  };
}

/**
 * Turn a `by_tier` map into ordered rows, one per tier that has
 * at least one settled read. Order is always A+ → F. Tiers with
 * zero settles are omitted (nothing to prove yet — never a fake
 * empty row).
 *
 * @param {Object} byTier  getModelAggregate().by_tier
 * @param {number} [minSample=20]
 * @returns {Array} ordered display rows
 */
function buildTierRows(byTier, minSample = 20) {
  const map = byTier && typeof byTier === 'object' ? byTier : {};
  const rows = [];
  for (const tier of TIER_ORDER) {
    const bucket = map[tier];
    if (!bucket) continue;
    const row = buildTierRow(tier, bucket, minSample);
    if (row.settled <= 0) continue; // no settled reads → not shown
    rows.push(row);
  }
  return rows;
}

/** True when at least one tier has a real (gated) hit-% to show —
 *  a surface can use this to decide whether to show the "PROVEN"
 *  framing vs the plain building table. */
function anyTierReady(byTier) {
  return buildTierRows(byTier).some((r) => r.ready);
}

module.exports = {
  TIER_ORDER,
  isEdgeTier,
  buildTierRow,
  buildTierRows,
  anyTierReady,
};
