// Settings-aware overall board. This keeps the shared 949 player evaluation
// intact, but derives draftable positions and replacement levels from league
// settings instead of fixed overall baselines.

function isDraftBoardEligible(player, settings) {
  if (window.isExcludedFrom2026Draft?.(player)) return false;
  if (!settings.rosterable.has(player.pos)) return false;
  if (settings.includeFreeAgents) return true;
  const rosterStatus = player.rosterStatus || (player.team === "FA" ? "free_agent" : "active");
  if (rosterStatus === "active" && player.team !== "FA") return true;
  return !!(window.isAdpBoardRookie?.(player) || player.rookie);
}

function readDraftSettings({ teamCount = 12, rosterConfig = window.DEFAULT_ROSTER_CONFIG, scoring = "ppr", includeFreeAgents = true } = {}) {
  const slots = { ...(rosterConfig || window.DEFAULT_ROSTER_CONFIG) };
  const flexEligible = ["RB", "WR", "TE"];
  const superflexEligible = ["QB", "RB", "WR", "TE"];
  const rosterable = new Set();

  ["QB", "RB", "WR", "TE", "DST", "K"].forEach(pos => {
    const dedicated = slots[pos] || 0;
    const viaFlex = flexEligible.includes(pos) && (slots.FLEX || 0) > 0;
    const viaSuperflex = superflexEligible.includes(pos) && (slots.SUPERFLEX || 0) > 0;
    const viaBench = (slots.BENCH || 0) > 0 && (dedicated > 0 || viaFlex || viaSuperflex);
    if (dedicated > 0 || viaFlex || viaSuperflex || viaBench) rosterable.add(pos);
  });

  const startersPerTeam = ["QB", "RB", "WR", "TE", "FLEX", "SUPERFLEX", "DST", "K"]
    .reduce((sum, slot) => sum + (slots[slot] || 0), 0);

  return {
    teamCount,
    slots,
    scoring,
    includeFreeAgents,
    flexEligible,
    superflexEligible,
    rosterable,
    startersPerTeam,
    benchDepth: slots.BENCH || 0,
    totalRounds: startersPerTeam + (slots.BENCH || 0),
    isSuperflex: (slots.SUPERFLEX || 0) > 0 || (slots.QB || 0) > 1,
  };
}

function deriveBaselines(players, settings) {
  const { teamCount, slots, flexEligible, superflexEligible, rosterable } = settings;
  const pool = {};

  ["QB", "RB", "WR", "TE", "DST", "K"].forEach(pos => {
    pool[pos] = players
      .filter(p => p.pos === pos)
      .map(p => (p.adjusted || p.proj || 0) * 17)
      .sort((a, b) => b - a);
  });

  const used = {};
  Object.keys(pool).forEach(pos => { used[pos] = teamCount * (slots[pos] || 0); });

  [["FLEX", flexEligible], ["SUPERFLEX", superflexEligible]].forEach(([slotName, eligible]) => {
    const count = teamCount * (slots[slotName] || 0);
    for (let i = 0; i < count; i += 1) {
      let bestValue = -Infinity;
      let bestPos = null;
      eligible.forEach(pos => {
        const idx = used[pos] || 0;
        const value = pool[pos]?.[idx];
        if (value != null && value > bestValue) {
          bestValue = value;
          bestPos = pos;
        }
      });
      if (!bestPos) break;
      used[bestPos] += 1;
    }
  });

  const baselines = {};
  const replacementRanks = {};
  Object.keys(pool).forEach(pos => {
    if (!rosterable.has(pos)) {
      baselines[pos] = Infinity;
      replacementRanks[pos] = 0;
      return;
    }
    const neededDepth = Math.max((used[pos] || 0) + 6, MIN_POOL_DEPTH[pos] || 18);
    if (pool[pos].length < neededDepth) {
      baselines[pos] = referenceReplacementPoints(pos, settings.scoring);
      replacementRanks[pos] = used[pos] + 1;
      return;
    }
    const idx = Math.min(used[pos], Math.max(0, pool[pos].length - 1));
    baselines[pos] = pool[pos][idx] ?? 0;
    replacementRanks[pos] = used[pos] + 1;
  });

  return { baselines, replacementRanks, startersByPosition: used };
}

const MIN_POOL_DEPTH = { QB: 24, RB: 48, WR: 60, TE: 24, DST: 12, K: 12 };

const REAL_REPLACEMENT = { QB: 261.5, RB: 173.4, WR: 178.4, TE: 118.0, DST: 110, K: 125 };
const SCORING_SCALE = { ppr: 1.00, half: 0.80, std: 0.62 };
const POOL_CONFIDENCE = { QB: 1.0, RB: 1.0, WR: 1.0, TE: 1.0, DST: 0.15, K: 1.0 };
const POSITION_BOARD_FLOOR = {};

const REFERENCE_REPLACEMENT_POINTS = {
  std:  { QB: REAL_REPLACEMENT.QB, RB: REAL_REPLACEMENT.RB * SCORING_SCALE.std, WR: REAL_REPLACEMENT.WR * SCORING_SCALE.std, TE: REAL_REPLACEMENT.TE * SCORING_SCALE.std, DST: REAL_REPLACEMENT.DST, K: REAL_REPLACEMENT.K },
  half: { QB: REAL_REPLACEMENT.QB, RB: REAL_REPLACEMENT.RB * SCORING_SCALE.half, WR: REAL_REPLACEMENT.WR * SCORING_SCALE.half, TE: REAL_REPLACEMENT.TE * SCORING_SCALE.half, DST: REAL_REPLACEMENT.DST, K: REAL_REPLACEMENT.K },
  ppr:  { QB: REAL_REPLACEMENT.QB, RB: REAL_REPLACEMENT.RB, WR: REAL_REPLACEMENT.WR, TE: REAL_REPLACEMENT.TE, DST: REAL_REPLACEMENT.DST, K: REAL_REPLACEMENT.K },
};

function referenceReplacementPoints(pos, scoring = "ppr") {
  const table = REFERENCE_REPLACEMENT_POINTS[scoring] || REFERENCE_REPLACEMENT_POINTS.ppr;
  return table[pos] ?? 0;
}

function dampedExpectedSurplus(band, baseline, player = {}) {
  if (!Number.isFinite(baseline) || baseline <= 0) return 0;
  const median = Math.max(0, (band?.proj ?? 0) - baseline);
  const expected = window.expectedSurplus(band, baseline);
  const lambda = 0.30
    * Math.min(1, ((band?.seasons ?? player.seasons ?? 0) / 3))
    * (POOL_CONFIDENCE[player.pos] ?? 1)
    * (band?.isRookie || player.isRookie ? 0 : 1);
  return median + lambda * (expected - median);
}

function normalizedSurplus(band, baseline, player) {
  return dampedExpectedSurplus(band, baseline, player);
}

function overallImpactScoreFromSettings(player, baselines, settings) {
  if (!settings.rosterable.has(player.pos)) return -9999;

  const baseline = baselines[player.pos];
  const range = player.nine49Range || {
    floor: (player.floor || player.proj || 0) * 17,
    proj: (player.adjusted || player.proj || 0) * 17,
    ceiling: (player.ceiling || player.proj || 0) * 17,
  };

  const superflexQbLift = settings.isSuperflex && player.pos === "QB" ? 1.35 : 1;
  const surplus = normalizedSurplus(range, baseline, player) * positionalSurplusMultiplier(player.pos, settings.scoring) * superflexQbLift;
  const valueLift = player.valueIndex ? Math.max(-10, Math.min(22, (player.valueIndex - 1) * 24)) : 0;
  const riskPenalty = player.risk === "high" ? 12 : player.risk === "med" ? 5 : 0;
  return surplus + valueLift - riskPenalty;
}

function positionalSurplusMultiplier(pos, scoring = "ppr") {
  const table = {
    std:  { QB: 0.72, RB: 1.00, WR: 1.00, TE: 0.72, DST: 0.20, K: 0.12 },
    half: { QB: 0.72, RB: 1.00, WR: 1.00, TE: 0.72, DST: 0.20, K: 0.12 },
    ppr:  { QB: 0.72, RB: 1.00, WR: 1.00, TE: 0.72, DST: 0.20, K: 0.12 },
  }[scoring] || {};
  return table[pos] ?? 1;
}

function starterDemandGuardrail(player, positionRank, settings, replacementRanks) {
  const replacement = replacementRanks[player.pos] || 1;
  if (!replacement) return 0;

  const depth = Math.min(1, (positionRank || 999) / replacement);
  const leagueStarters = settings.teamCount * (
    (settings.slots[player.pos] || 0)
    + (settings.flexEligible.includes(player.pos) ? (settings.slots.FLEX || 0) / 3 : 0)
    + (settings.slots.SUPERFLEX || 0) * (player.pos === "QB" ? 0.85 : settings.superflexEligible.includes(player.pos) ? 0.05 : 0)
  );
  const scarcityOfSlots = leagueStarters > 0 ? Math.max(0, 1 - leagueStarters / 36) : 1;
  return depth * scarcityOfSlots * 42;
}

function applyOverallTheoryFromSettings(players, baselines, replacementRanks, scoring, settings) {
  const positionBuckets = {};
  players.forEach(player => {
    if (!positionBuckets[player.pos]) positionBuckets[player.pos] = [];
    positionBuckets[player.pos].push(player);
  });

  const positionRanks = {};
  Object.entries(positionBuckets).forEach(([position, list]) => {
    list.slice()
      .sort((a, b) => window.compareByFormatPoints(a, b, scoring))
      .forEach((player, index, sorted) => {
        const baselinePpg = (baselines[position] || 0) / 17;
        const projectedPpg = (typeof window.formatSeasonPoints === "function"
          ? window.formatSeasonPoints(player, scoring)
          : (player.adjusted || player.proj || 0) * 17) / 17;
        const nextPpg = sorted[index + 1]
          ? ((typeof window.formatSeasonPoints === "function"
            ? window.formatSeasonPoints(sorted[index + 1], scoring)
            : (sorted[index + 1].adjusted || sorted[index + 1].proj || 0) * 17) / 17)
          : baselinePpg;
        const replacement = replacementRanks[position] || 12;
        positionRanks[player.id] = {
          positionRank: index + 1,
          aboveBaselinePpg: Math.max(0, projectedPpg - baselinePpg),
          localTierGap: Math.max(0, projectedPpg - nextPpg),
          rankScarcity: Math.max(0, (replacement - index - 1) / replacement),
        };
      });
  });

  return players.map(player => {
    const rankData = positionRanks[player.id] || {};
    const blueChipScore = (
      rankData.aboveBaselinePpg * 9 +
      rankData.localTierGap * 7 +
      rankData.rankScarcity * 28
    ) * ((typeof window.formatOverallWeights === "function"
      ? window.formatOverallWeights(scoring).blueChip?.[player.pos]
      : null) || 0.5);
    const floorEdge = Math.max(0, (player.floor || player.proj || 0) - ((baselines[player.pos] || 0) / 17));
    const floorScore = floorEdge * 0.5;
    const guardrail = starterDemandGuardrail(player, rankData.positionRank, settings, replacementRanks);

    return {
      ...player,
      positionRank: rankData.positionRank || null,
      overallTheoryScore: (player.overallImpact || 0) * 1.08 + blueChipScore + floorScore - guardrail,
      overallTheory: {
        ...(player.overallTheory || {}),
        blueChipScore,
        floorScore,
        guardrail,
        replacementRank: replacementRanks[player.pos] || null,
        positionRank: rankData.positionRank || null,
      },
    };
  });
}

function buildOverallDraftBoardFromSettings(
  players, scoring, draftedByPlayerId = {}, pickNumber = 1, nextUserPick = null, settingsInput = {}
) {
  const settings = readDraftSettings({ ...settingsInput, scoring });
  const draftable = players.filter((player) => isDraftBoardEligible(player, settings));
  const scored = (typeof window.applyFormatValueGrades === "function"
    ? window.applyFormatValueGrades(
      draftable.map(player => window.enrichDraftPlayer(player, scoring, draftedByPlayerId[player.id], pickNumber, nextUserPick, settings.teamCount)),
      scoring,
    )
    : draftable.map(player => window.enrichDraftPlayer(player, scoring, draftedByPlayerId[player.id], pickNumber, nextUserPick, settings.teamCount))
  ).map(player => ({
    ...player,
    nine49Range: scoringAdjustedRange(player, scoring),
  }));
  const { baselines, replacementRanks, startersByPosition } = deriveBaselines(scored, settings);
  const withImpact = scored.map(player => ({
    ...player,
    overallImpact: overallImpactScoreFromSettings(player, baselines, settings),
    replacementRank: replacementRanks[player.pos],
  }));
  const withTheory = applyOverallTheoryFromSettings(withImpact, baselines, replacementRanks, scoring, settings);

  const rookieIds = new Set(
    withTheory
      .filter((player) => player.pos !== "DST" && player.pos !== "K" && !player.unranked && window.isAdpBoardRookie(player))
      .map((player) => player.id)
  );
  const kickers = withTheory
    .filter((player) => player.pos === "K" && !player.unranked)
    .slice()
    .sort((a, b) => (b.boardScore || b.overallTheoryScore || b.overallImpact || 0) - (a.boardScore || a.overallTheoryScore || a.overallImpact || 0)
      || (a.adp || 999) - (b.adp || 999));
  const veterans = withTheory.filter((player) => !rookieIds.has(player.id) && player.pos !== "K");
  const rookies = withTheory.filter((player) => rookieIds.has(player.id));
  const sorted = applyPositionFloors(window.sortOverallDraftBoard(veterans, scoring), settings);
  const skill = sorted.filter(player => player.pos !== "DST" && player.pos !== "K" && !player.unranked);
  const placeholders = sorted.filter(player => player.pos === "DST" || player.unranked);
  const withRookies = typeof window.placeRookiesByAdp === "function"
    ? window.placeRookiesByAdp(skill, rookies, settings.teamCount || 12)
    : [...skill, ...rookies];
  const placed = typeof window.placeAfterOverallFloor === "function"
    ? window.placeAfterOverallFloor(withRookies, kickers, window.KICKER_OVERALL_FLOOR || 100)
    : [...withRookies, ...kickers];
  const ordered = [...placed, ...placeholders];
  const boardScores = skill.map(player => player.boardScore || player.overallTheoryScore || player.overallImpact || 0);
  const maxScore = boardScores.length ? Math.max(...boardScores) : 0;
  const minScore = boardScores.length ? Math.min(...boardScores) : 0;

  return ordered.map((player, index) => ({
    ...player,
    displayRank: player.unranked || player.pos === "DST" ? null : index + 1,
    overallRank: player.unranked || player.pos === "DST" ? null : index + 1,
    overallValueGrade: player.unranked || player.pos === "DST"
      ? { letter: "—", color: "var(--slate)", score: 0 }
      : overallBoardGrade(player, minScore, maxScore),
    boardSettings: { replacementRanks, startersByPosition, isSuperflex: settings.isSuperflex },
  }));
}

function scoringAdjustedRange(player, scoring) {
  if (player.nine49Range?.isRookie && typeof window.rookieBand === "function") {
    return window.rookieBand(player, scoring);
  }
  const formatTotal = typeof window.formatSeasonPoints === "function"
    ? window.formatSeasonPoints(player, scoring)
    : (player.adjusted || player.proj || 0) * 17;
  const base = player.nine49Range || {
    floor: (player.floor || player.proj || 0) * 17,
    proj: (player.proj || 0) * 17,
    ceiling: (player.ceiling || player.proj || 0) * 17,
  };
  const pprTotal = typeof window.formatSeasonPoints === "function"
    ? window.formatSeasonPoints(player, "ppr")
    : (player.proj || 0) * 17;
  const ratio = pprTotal > 0 ? formatTotal / pprTotal : 1;
  const proj = formatTotal || base.proj * ratio;
  const pprSd = typeof window.positionVolatilitySd === "function" ? window.positionVolatilitySd(player.pos, "ppr") : 0.25;
  const fmtSd = typeof window.positionVolatilitySd === "function" ? window.positionVolatilitySd(player.pos, scoring) : pprSd;
  const vol = pprSd > 0 ? fmtSd / pprSd : 1;
  const spreadUp = Math.max(0, (base.ceiling - base.proj) * ratio) * vol;
  const spreadDown = Math.max(0, (base.proj - base.floor) * ratio) * vol;
  return {
    ...base,
    proj,
    floor: Math.max(0, proj - spreadDown),
    ceiling: proj + spreadUp,
  };
}

function applyPositionFloors(board, settings) {
  const scoringKey = settings.isSuperflex ? "superflex" : settings.scoring;
  const floors = Object.entries(POSITION_BOARD_FLOOR);
  let nextBoard = board.slice();
  floors.forEach(([pos, byScoring]) => {
    const floor = byScoring[scoringKey] || byScoring[settings.scoring];
    if (!floor) return;
    const early = nextBoard.filter((player, index) => player.pos === pos && index + 1 < floor);
    if (!early.length) return;
    const earlyIds = new Set(early.map(player => player.id));
    const withoutEarly = nextBoard.filter(player => !earlyIds.has(player.id));
    const beforeFloor = withoutEarly.slice(0, floor - 1);
    const afterFloor = withoutEarly.slice(floor - 1);
    nextBoard = [...beforeFloor, ...early, ...afterFloor];
  });
  return nextBoard.map((player, index) => ({
    ...player,
    overallRank: index + 1,
    displayRank: index + 1,
    positionFloorNote: player.positionFloorNote,
  }));
}

function overallBoardGrade(player, minScore, maxScore) {
  const score = player.boardScore || player.overallTheoryScore || player.overallImpact || 0;
  const span = Math.max(1, maxScore - minScore);
  const pct = (score - minScore) / span;
  const letter = pct >= 0.92 ? "A+"
    : pct >= 0.80 ? "A"
    : pct >= 0.64 ? "B+"
    : pct >= 0.48 ? "B"
    : pct >= 0.32 ? "C"
    : pct >= 0.16 ? "D"
    : "F";
  return { letter, color: window.gradeColor ? window.gradeColor(letter) : "var(--mint-soft)", score };
}

function rosterShapeLabel(settingsInput = {}, scoring = "ppr") {
  const settings = readDraftSettings({ ...settingsInput, scoring });
  const slots = settings.slots;
  const starterSlots = ["QB", "RB", "WR", "TE", "FLEX", "SUPERFLEX", "DST", "K"]
    .filter(slot => (slots[slot] || 0) > 0)
    .map(slot => `${slots[slot]}${slot}`);
  return `${settings.teamCount}-team · ${starterSlots.join("/") || "No starters"}`;
}

function replacementSummaryLabel(replacementRanks = {}) {
  return ["QB", "RB", "WR", "TE", "DST", "K"]
    .filter(pos => (replacementRanks[pos] || 0) > 0)
    .map(pos => `${pos}${replacementRanks[pos]}`)
    .join(" · ");
}

Object.assign(window, {
  isDraftBoardEligible,
  readDraftSettings,
  deriveBaselines,
  referenceReplacementPoints,
  dampedExpectedSurplus,
  normalizedSurplus,
  positionalSurplusMultiplier,
  scoringAdjustedRange,
  applyPositionFloors,
  overallBoardGrade,
  overallImpactScoreFromSettings,
  starterDemandGuardrail,
  applyOverallTheoryFromSettings,
  buildOverallDraftBoardFromSettings,
  rosterShapeLabel,
  replacementSummaryLabel,
});
