/**
 * depthChartService — lineup / depth-chart / cascade foundation (Session 43).
 *
 * Provides the data model + graceful aggregation for:
 *   - getLineup(sport, team)        — tonight's projected lineup / starters
 *   - getDepthChart(sport, team)    — starters + backups per position
 *   - getCascadeProjection(...)     — "when X is OUT, teammate Y gets +delta"
 *
 * Sources (all best-effort, injectable for tests, never throws):
 *   - mlbStatsAdapter.getScheduleWithPitchers → probable pitchers (MLB)
 *   - scheduleService.getGameSummary          → ESPN injuries / leaders
 *
 * This is the FOUNDATION: contracts + real data where it's freely available,
 * graceful empty defaults elsewhere. The minutes/usage projection model and
 * full batting orders arrive with the Session-44/45 pipelines.
 */

const norm = (s) => String(s == null ? '' : s).trim().toUpperCase();

/** Today's UTC date (YYYY-MM-DD). Injectable for deterministic tests. */
function todayISO(now) {
  return (now || new Date()).toISOString().slice(0, 10);
}

/**
 * Tonight's projected lineup for a team. MLB returns the probable starting
 * pitcher (the one lineup slot the free schedule feed exposes); other sports
 * fall back to the ESPN summary leaders when a game is found. Always returns an
 * array (possibly empty).
 */
async function getLineup(sport, team, opts = {}) {
  const sp = String(sport || 'nba').toLowerCase();
  const t = norm(team);
  if (!t) return [];
  try {
    if (sp === 'mlb') {
      const mlb = opts.mlbAdapter || require('./adapters/mlbStatsAdapter');
      const date = opts.date || todayISO(opts.now);
      const games = await mlb.getScheduleWithPitchers(date);
      const game = (games || []).find((g) => matchesTeam(g.home, t) || matchesTeam(g.away, t));
      if (!game) return [];
      const side = matchesTeam(game.home, t) ? game.home : game.away;
      const out = [];
      if (side?.probablePitcher?.name) {
        out.push({ player: side.probablePitcher.name, position: 'SP', battingOrder: null, projectedMinutes: null });
      }
      return out;
    }
  } catch (err) {
    console.warn('[depthChart] getLineup failed:', sport, team, err.message);
  }
  return [];
}

function matchesTeam(side, t) {
  if (!side || !t) return false;
  const name = norm(side.team || side.name);
  if (!name) return false;
  const lastWord = name.split(' ').pop();
  return name === t || name.includes(t) || (lastWord.length >= 3 && t.includes(lastWord));
}

/**
 * Depth chart for a team: positions with starter/backup/thirdString. When no
 * roster source is wired, returns a valid empty structure (graceful default).
 * Pass opts.roster ([{player, position, depth}]) to build a real chart.
 */
async function getDepthChart(sport, team, opts = {}) {
  const sp = String(sport || 'nba').toLowerCase();
  const t = norm(team);
  const base = { sport: sp, team: t, positions: [] };
  const roster = Array.isArray(opts.roster) ? opts.roster : null;
  if (!roster) return base;
  const byPos = {};
  for (const r of roster) {
    const pos = norm(r.position) || 'UTIL';
    (byPos[pos] = byPos[pos] || []).push(r);
  }
  base.positions = Object.entries(byPos).map(([position, players]) => {
    const sorted = players.slice().sort((a, b) => (a.depth || 99) - (b.depth || 99));
    return {
      position,
      starter: sorted[0]?.player || null,
      backup: sorted[1]?.player || null,
      thirdString: sorted[2]?.player || null,
    };
  });
  return base;
}

/**
 * Cascade projection: what happens to teammates' production when `player` is
 * OUT. Foundation heuristic — uses the design's archetype cascade weights
 * (usage sponges benefit most). Returns [] when we can't establish that the
 * player is actually out or have no teammates to project onto.
 * opts.teammates: [{player, archetype, baseUsage}] — injected by the caller.
 */
async function getCascadeProjection(sport, player, team, opts = {}) {
  const teammates = Array.isArray(opts.teammates) ? opts.teammates : [];
  if (!player || teammates.length === 0) return [];
  // Distribute a fixed usage pool across teammates, weighting usage sponges and
  // high-usage creators heavier (mirrors the design's cascade framing).
  const POOL = Number.isFinite(opts.usagePool) ? opts.usagePool : 12; // %
  // VYNDR Originals (Session 44): SURGE (usage sponge) benefits most; primary
  // creators (TORCH/DUAL THREAT/SWITCHBOARD) next; low-usage glue least.
  const weightFor = (a) => {
    const k = norm(a);
    if (k === 'SURGE' || k.includes('USAGE SPONGE')) return 3;
    if (['TORCH', 'DUAL THREAT', 'SWITCHBOARD', 'IGNITER'].includes(k) || k.includes('VOLUME') || k.includes('COMBO') || k.includes('POINT FORWARD')) return 2;
    if (k === 'CONNECTOR' || k === 'LOCKDOWN' || k.includes('ROLE GLUE') || k.includes('SPECIALIST')) return 0.5;
    return 1;
  };
  const weights = teammates.map((m) => weightFor(m.archetype));
  const total = weights.reduce((s, w) => s + w, 0) || 1;
  return teammates.map((m, i) => {
    const delta = +((POOL * weights[i]) / total).toFixed(1);
    return {
      player: m.player,
      stat: 'usage',
      delta: `+${delta}%`,
      reason: `${player} OUT → ${m.player} absorbs touches`,
    };
  }).sort((a, b) => parseFloat(b.delta) - parseFloat(a.delta));
}

module.exports = {
  getLineup,
  getDepthChart,
  getCascadeProjection,
  _internals: { norm, matchesTeam, todayISO },
};
