// Draft Rankings — pre-season player breakdown ordered by numerical rank.
// Position dividers are rank bands; 949 Value is a separate historical,
// position-relative production signal and never controls row placement.

const DR_COLS = "60px minmax(220px, 1.6fr) 110px 70px 100px 96px 160px 130px 80px";

function byeWeekFor(player) {
  const teamRow = (window.NFL_SOS || []).find(row => row.team === player.team);
  if (teamRow?.byeWeek) return teamRow.byeWeek;
  return player.byeWeek || player.bye || null;
}

// Years in league for the player. Real PLAYERS data carries `age`; synthetic
// supplemental rankings don't, so fall back to a stable hash.
function playerTenure(player) {
  if (player.pos === "DST" || player.pos === "K") return 5;
  if (typeof player.experience === "number") return player.experience;
  if (player.age) return Math.max(0, player.age - 22);
  const seed = (player.id || "x").split("").reduce((a, c) => a + c.charCodeAt(0), 0);
  return seed % 9;
}

function seasonProjectedTotal(player, scoring) {
  const total = typeof window.formatSeasonPoints === "function"
    ? window.formatSeasonPoints(player, scoring)
    : (Number(player.ros) || (player.adjusted || player.proj || 0) * 17);
  if (Number.isFinite(total) && total > 0) return total;
  return (player.adjusted || player.proj || 0) * 17;
}

// 4-point YoY trend: [2023, 2024, 2025, 2026 projection] in points/game.
const DRAFT_RANKINGS_ROOKIE_IDS = new Set([
  "judkins", "henderson", "omarion", "mgolden", "tuten", "jhiggins", "thorton",
  "treharris", "pbryant", "cdike", "tfranklin", "jmcmillan", "ogordon", "teslaa",
  "dneal", "elic", "bsmith",
]);

function isDraftRankingsRookieProfile(player) {
  return player.rookie
    || DRAFT_RANKINGS_ROOKIE_IDS.has(player.id)
    || (player.age && player.age <= 22 && !player.nine49Model?.history?.length && !player.traj?.length);
}

function yoyTrendData(player, scoring) {
  const projectedTotal = seasonProjectedTotal(player, scoring);
  const projectedPpg = projectedTotal / 17;
  const recPts = typeof window.receptionPointValue === "function"
    ? window.receptionPointValue(scoring)
    : (scoring === "std" ? 0 : scoring === "half" ? 0.5 : 1);
  const seasons = player.historicalSeasons || player.profile?.historical?.seasons || [];
  const byYear = {};
  seasons.forEach((row) => {
    const year = Number(row.year || row.season);
    if (year < 2023 || year > 2025) return;
    const gp = Number(row.gamesPlayed) || 0;
    if (gp <= 0) return;
    const hasStats = [row.receptions, row.receivingYards, row.rushingYards, row.passingYards].some((v) => v != null);
    const pts = hasStats
      ? (Number(row.passingYards) || 0) / 25
        + (Number(row.passingTds) || 0) * 4
        - (Number(row.interceptions) || 0) * 2
        + (Number(row.rushingYards) || 0) / 10
        + (Number(row.rushingTds) || 0) * 6
        + (Number(row.receivingYards) || 0) / 10
        + (Number(row.receivingTds) || 0) * 6
        + (Number(row.receptions) || 0) * recPts
      : (Number(row.fantasyPoints) - (Number(row.receptions) || 0) * (1 - recPts));
    if (!Number.isFinite(pts) || pts <= 0) return;
    byYear[year] = pts / gp;
  });
  const history = [2023, 2024, 2025].map((year) => byYear[year]).filter((value) => Number.isFinite(value) && value > 0);
  if (history.length && projectedPpg > 0) {
    const values = history.concat(projectedPpg);
    const labels = Object.keys(byYear).sort().map((year) => `${year} ${byYear[year].toFixed(1)} PPG`);
    return {
      values,
      formula: `${labels.join(" · ")} · 2026 ${projectedPpg.toFixed(1)} = ${projectedTotal.toFixed(1)} / 17`,
    };
  }

  const yoyPpg = player.profile?.projection?.yoyPpg || player.profile?.yoy_ppg || player.nine49Model?.historyPpg || [];
  const priorPpg = (Array.isArray(yoyPpg) ? yoyPpg : []).slice(0, 3).filter((value) => Number.isFinite(value) && value > 0);
  if (priorPpg.length && projectedPpg > 0) {
    const values = priorPpg.concat(projectedPpg);
    return {
      values,
      formula: `Season PPG ${priorPpg.map((value) => value.toFixed(1)).join(" · ")} · 2026 ${projectedPpg.toFixed(1)} = ${projectedTotal.toFixed(1)} / 17`,
    };
  }

  if (player.nine49Model?.history?.length && projectedPpg > 0) {
    const games = player.nine49Model.historyGames || [];
    const totals = player.nine49Model.history.slice(-3);
    const gameCounts = games.slice(-3);
    const fromTotals = totals.map((total, index) => {
      const played = gameCounts[index];
      return played ? total / played : null;
    }).filter((value) => Number.isFinite(value) && value > 0);
    if (fromTotals.length) {
      const values = fromTotals.concat(projectedPpg);
      return {
        values,
        formula: `Season totals / games · 2026 ${projectedPpg.toFixed(1)} = ${projectedTotal.toFixed(1)} / 17`,
      };
    }
  }

  if (projectedPpg > 0) return { values: [projectedPpg], formula: `2026 ${projectedPpg.toFixed(1)} PPG projected` };
  return null;
}

function yoyTrendProfile(data) {
  if (!data || data.length < 2) return { label: "No Data", color: "var(--slate-dim)" };
  const history = data.slice(0, -1);
  const projection = data[data.length - 1];
  const y2023 = history[0] || 0;
  const y2024 = history[Math.min(1, history.length - 1)] || 0;
  const y2025 = history[history.length - 1] || 0;
  const avgHistory = (y2023 + y2024 + y2025) / 3;
  const peakHistory = Math.max(y2023, y2024, y2025);
  const improvedVs2023 = y2024 >= y2023 * 1.05 && y2025 >= y2023 * 1.05;
  const twoYearDown = y2024 <= y2023 * 0.97 && y2025 <= y2024 * 0.97;
  const projectedRegression = projection <= y2025 * 0.97;
  const projectedRebound = projection >= y2025 * 1.05;
  const stillStrong = projection >= avgHistory * 1.02 || projection >= peakHistory * 0.92;

  if (twoYearDown && !projectedRebound) {
    return { label: "Two-year downtrend", color: "var(--red)" };
  }
  if (projectedRegression && !stillStrong) {
    return { label: "Projected regression", color: "var(--red)" };
  }
  if (projectedRegression) {
    return { label: "Strong profile, regressive projection", color: "var(--mint)" };
  }
  if (improvedVs2023 && projection >= y2023 * 1.05) {
    return { label: "Two-year improvement", color: "var(--mint)" };
  }
  if (projectedRebound && y2025 <= Math.max(y2023, y2024) * 0.94) {
    return { label: "Projected rebound after down year", color: "var(--gold)" };
  }
  if (Math.abs(projection - y2025) <= y2025 * 0.03) {
    return { label: "Stable", color: "var(--gold)" };
  }
  if (projection > y2025) {
    return { label: "Positive projection", color: "var(--mint)" };
  }
  return { label: "Mixed trend", color: "var(--gold)" };
}

const VALUE_TIER_META = {
  "A+": { color: "var(--mint)",      label: "Tier 1", note: "Top-ranked players at this position." },
  "A":  { color: "var(--mint)",      label: "Tier 2", note: "Upper-end positional ranks." },
  "B":  { color: "var(--mint-soft)", label: "Tier 3", note: "Strong positional starters by rank." },
  "C":  { color: "var(--gold)",      label: "Tier 4", note: "Middle positional rank cohort." },
  "D":  { color: "var(--gold)",      label: "Tier 5", note: "Lower positional rank cohort." },
  "F":  { color: "var(--red)",       label: "Tier 6", note: "Final positional rank cohort." },
};
const TIER_ORDER = ["A+", "A", "B", "C", "D", "F"];

// Column-header definitions surfaced via the HelpTip hover.
  const DR_COL_HELP = {
  Rank:        "Overall player rank for this scoring format.",
  Player:      "Player name, position, team, and weekly matchup.",
  "Proj (Total)": "Projected fantasy points for the full season.",
  Bye:         "The player's NFL bye week when supplied by the data provider.",
  SOS:         "2026 strength of schedule: 1 is hardest (red), 32 is easiest (green). Based on each opponent's 2025 record.",
  Risk:        "Composite of floor vs projection (35%), ceiling/floor band tightness (25%), games played over the last three seasons (25%), and how much of the projected range sits above the median (15%). On this board the lowest 25% of scores are High, the highest 25% Low, and the middle 50% Med.",
  "Boom/Bust": "Each scored 2023-2025 week vs that week's projection. Hit = 70% to 130% of projection (inclusive). Boom = above 130%. Bust = below 70%. Percents are of that player's full game sample.",
  "YoY Pts/Gm": "Formula: season fantasy points / games played for 2023, 2024, and 2025; projected 2026 points / 17.",
  Value:       "Format season points vs a 12-team starter pool (QB16, RB30, WR36, TE14). 1.00 is a B (pool average). F is 0.50x or below. Each position is clamped to 8-10 A+. Value does not control rank.",
};

const BASE_LEVELS = (window.RANKING_LEVELS || [
  { id: "std", label: "Standard", shortLabel: "STD", scoringLabel: "Standard" },
  { id: "half", label: "Half PPR", shortLabel: "HALF", scoringLabel: "Half PPR" },
  { id: "ppr", label: "PPR", shortLabel: "PPR", scoringLabel: "PPR" },
]);

const FORMAT_TABS = BASE_LEVELS.map(level => ({
  id: level.id,
  label: level.label || level.scoringLabel,
})).concat([{ id: "2qb", label: "2QB" }]);

const DRAFT_RANKINGS_DEFAULT_LEAGUE_SETTINGS = {
  teamCount: 12,
  rosterConfig: window.DEFAULT_ROSTER_CONFIG,
  includeFreeAgents: true,
};

const DRAFT_RANKINGS_2QB_SETTINGS = {
  teamCount: 12,
  rosterConfig: window.SUPERFLEX_ROSTER_CONFIG || { ...(window.DEFAULT_ROSTER_CONFIG || {}), FLEX: 1, SUPERFLEX: 1 },
  includeFreeAgents: true,
};

function rankingLevelFor(scoring) {
  return BASE_LEVELS.find(level => level.id === scoring)
    || { label: "PPR", shortLabel: "PPR", scoringLabel: "PPR" };
}

function rankingFormatFor(formatId) {
  if (formatId === "2qb") {
    return {
      id: "2qb",
      label: "2QB",
      shortLabel: "2QB",
      scoring: "ppr",
      scoringLabel: "PPR",
      settings: DRAFT_RANKINGS_2QB_SETTINGS,
      isSuperflex: true,
    };
  }
  const level = rankingLevelFor(formatId);
  return {
    ...level,
    id: formatId,
    scoring: formatId,
    settings: DRAFT_RANKINGS_DEFAULT_LEAGUE_SETTINGS,
    isSuperflex: false,
  };
}

function enrichForScoring(players, format) {
  return window.buildOverallDraftBoardFromSettings(players, format.scoring, {}, 1, null, format.settings).map(player => ({
    ...player,
    tier: window.seasonValueGrade(player).letter,
    overallValueGrade: player.overallValueGrade || player.overallBoardGrade || null,
  }));
}

function sortRankedPlayers(players, scoring, pos) {
  const mode = pos === "ALL" ? "overall" : "position";
  const isRookie = (player) => window.isAdpBoardRookie?.(player);
  const veterans = players.filter((player) => !isRookie(player));
  const rookies = players.filter((player) => isRookie(player));
  const sortable = pos === "ALL" && typeof window.enforcePositionalMonotonicity === "function"
    ? window.enforcePositionalMonotonicity(veterans)
    : veterans.slice();
  const orderedVeterans = sortable.sort((a, b) => {
    const aFreeAgent = a.team === "FA";
    const bFreeAgent = b.team === "FA";
    if (aFreeAgent !== bFreeAgent) return aFreeAgent ? 1 : -1;

    if (pos === "ALL") {
      return (b.boardScore || -999) - (a.boardScore || -999)
        || (b.overallTheoryScore || b.overallImpact || 0) - (a.overallTheoryScore || a.overallImpact || 0)
        || b.overallImpact - a.overallImpact
        || window.projectionRankScore(b, scoring, mode) - window.projectionRankScore(a, scoring, mode)
        || (b.valueIndex || 0) - (a.valueIndex || 0)
        || a.adp - b.adp;
    }
    return window.compareByFormatPoints(a, b, scoring)
      || window.projectionRankScore(b, scoring, mode) - window.projectionRankScore(a, scoring, mode)
      || a.adp - b.adp;
  });
  const ordered = pos === "ALL" && typeof window.placeRookiesByAdp === "function"
    ? window.placeRookiesByAdp(orderedVeterans, rookies)
    : [...veterans, ...rookies].sort((a, b) => {
      const aFreeAgent = a.team === "FA";
      const bFreeAgent = b.team === "FA";
      if (aFreeAgent !== bFreeAgent) return aFreeAgent ? 1 : -1;
      return window.compareByFormatPoints(a, b, scoring);
    });
  return ordered.map((p, index) => ({ ...p, displayRank: index + 1 }));
}

function cfDeltaTotal(player) {
  const ceiling = (player.ceiling || player.proj || 0) * 17;
  const floor = (player.floor || player.proj || 0) * 17;
  return Math.max(0, ceiling - floor);
}

function riskCompositeScore(player) {
  const projection = Math.max(0.1, player.adjusted || player.proj || 0);
  const floor = Math.max(0, player.floor || projection);
  const ceiling = Math.max(projection, player.ceiling || projection);
  const bandWidth = Math.max(0, ceiling - floor);
  const floorPredictability = Math.max(0, Math.min(1, floor / projection));
  const bandTightness = Math.max(0, Math.min(1, 1 - (bandWidth / Math.max(1, projection * 1.6))));

  const games = player.nine49Model?.historyGames || player.historyGames || [];
  const avgGames = games.length
    ? games.reduce((sum, count) => sum + Math.min(17, count || 0), 0) / games.length
    : (window.availabilityBreakdown(player).score || 65) / 100 * 17;
  const gamesPlayedScore = Math.max(0, Math.min(1, avgGames / 17));

  const upside = Math.max(0, ceiling - projection);
  const downside = Math.max(0, projection - floor);
  const ceilingProbability = Math.max(0, Math.min(1, upside / Math.max(0.1, upside + downside)));

  return floorPredictability * 35
    + bandTightness * 25
    + gamesPlayedScore * 25
    + ceilingProbability * 15;
}

function riskTierMapFor(players) {
  const scored = players
    .map(player => ({ id: player.id, score: riskCompositeScore(player) }))
    .sort((a, b) => b.score - a.score);
  const highStart = Math.ceil(scored.length * 0.75);
  const lowEnd = Math.floor(scored.length * 0.25);
  return Object.fromEntries(scored.map((entry, index) => {
    const tier = index < lowEnd
      ? { label: "Low", color: "var(--mint)", percentile: 1 - index / Math.max(1, scored.length - 1), score: entry.score }
      : index >= highStart
        ? { label: "High", color: "var(--red)", percentile: 1 - index / Math.max(1, scored.length - 1), score: entry.score }
        : { label: "Med", color: "var(--gold)", percentile: 1 - index / Math.max(1, scored.length - 1), score: entry.score };
    return [entry.id, tier];
  }));
}

function pctFromCount(count, games) {
  return games ? Math.round((count / games) * 100) : 0;
}

function rateToPct(explicitPct, rate, count, games) {
  if (explicitPct != null) return Math.round(explicitPct);
  if (rate != null) return Math.round(rate * 100);
  return pctFromCount(count || 0, games);
}

function classifyBoomHitBust(actual, projected) {
  if (!(projected > 0) || !Number.isFinite(actual)) return null;
  if (actual > projected * 1.30) return "boom";
  if (actual < projected * 0.70) return "bust";
  return "hit";
}

function logPointsForScoring(log, scoring) {
  if (scoring === "std" || scoring === "standard") {
    return {
      actual: Number(log.std ?? log.fantasyPoints),
      projected: Number(log.expectedStd ?? log.expectedPoints),
    };
  }
  if (scoring === "half") {
    return {
      actual: Number(log.half ?? log.fantasyPoints),
      projected: Number(log.expectedHalf ?? log.expectedPoints),
    };
  }
  return {
    actual: Number(log.ppr ?? log.fantasyPoints),
    projected: Number(log.expectedPpr ?? log.expectedPoints),
  };
}

function weeklyConsistencyProfile(player, scoring = "ppr") {
  const projection = player.adjusted || player.proj || 0;
  const boomLine = projection * 1.30;
  const bustLine = projection * 0.70;
  const logs = player.historicalLogs || player.profile?.historical?.gameLogs || [];
  const counted = { boom: 0, hit: 0, bust: 0 };
  let games = 0;
  logs.forEach((log) => {
    const year = Number(log.season);
    if (year < 2023 || year > 2025) return;
    const { actual, projected: weekProjected } = logPointsForScoring(log, scoring);
    if (!Number.isFinite(actual)) return;
    const projected = weekProjected > 0 ? weekProjected : projection;
    const kind = classifyBoomHitBust(actual, projected);
    if (!kind) return;
    games += 1;
    counted[kind] += 1;
  });

  if (games >= 4) {
    const boomPct = Math.round((counted.boom / games) * 100);
    const bustPct = Math.round((counted.bust / games) * 100);
    const hitPct = Math.max(0, 100 - boomPct - bustPct);
    return {
      available: true,
      sampleOk: true,
      games,
      boomGames: counted.boom,
      hitGames: counted.hit,
      bustGames: counted.bust,
      boomPct,
      hitPct,
      bustPct,
      projection,
      boomLine,
      bustLine,
      source: "2023-2025 weekly actual vs weekly projection",
    };
  }

  const source = player.weeklyConsistency?.[scoring]
    || player.weeklyConsistency?.ppr
    || (player.weeklyConsistency?.games || player.weeklyConsistency?.boomWeeks ? player.weeklyConsistency : null)
    || null;
  const fallbackGames = source
    ? (source.games || ((source.boomWeeks || 0) + (source.hitWeeks || 0) + (source.bustWeeks || 0)))
    : 0;
  const boomPct = Math.round(source?.boomPct ?? player.boom ?? 0);
  const bustPct = Math.round(source?.bustPct ?? player.bust ?? 0);
  if (fallbackGames >= 4 || boomPct + bustPct > 0) {
    return {
      available: true,
      sampleOk: true,
      games: fallbackGames || games,
      boomGames: source?.boomWeeks || 0,
      hitGames: source?.hitWeeks || 0,
      bustGames: source?.bustWeeks || 0,
      boomPct,
      hitPct: Math.round(source?.expectedPct || Math.max(0, 100 - boomPct - bustPct)),
      bustPct,
      projection,
      boomLine,
      bustLine,
      source: source?.source || "weekly-game-log",
    };
  }
  return { available: games > 0, sampleOk: false, games, projection, boomLine, bustLine, reason: "Pending weekly game logs" };
}

function rankTierRangeLabel(tier, total) {
  const bounds = { "A+": [0, 0.04], A: [0.04, 0.14], B: [0.14, 0.34], C: [0.34, 0.58], D: [0.58, 0.82], F: [0.82, 1] };
  const [start, end] = bounds[tier] || [0, 1];
  const first = Math.floor(start * total) + 1;
  const last = Math.max(first, Math.ceil(end * total));
  return `Position ranks ${first}-${last}`;
}

// Inline hover-tooltip used on column headers and the tier badge.
// Uses CSS :hover (not React state) so the popup stays visible while the
// cursor moves from the trigger onto the tooltip itself.
function HelpTip({ label, children, side = "bottom", align = "start" }) {
  const cls = `help-tip${align === "end" ? " align-end" : ""}${side === "top" ? " side-top" : ""}`;
  return (
    <span className={cls}>
      {label}
      <span style={{ fontSize: 10, color: "var(--mint)", opacity: 0.7, lineHeight: 1 }}>ⓘ</span>
      <span className="help-pop">{children}</span>
    </span>
  );
}

function DrHead({ children, align = "flex-start" }) {
  const help = DR_COL_HELP[children];
  return (
    <div style={{ display: "flex", alignItems: "center", justifyContent: align, minWidth: 0, padding: "0 6px" }}>
      {help
        ? <HelpTip label={children} align={align === "flex-end" ? "end" : "start"}>{help}</HelpTip>
        : children}
    </div>
  );
}

function DrCell({ children, align = "flex-start" }) {
  return <div style={{ display: "flex", alignItems: "center", justifyContent: align, minWidth: 0, padding: "0 6px" }}>{children}</div>;
}

function DraftRankingsView({ onSelectPlayer }) {
  const nflStatus = window.useNflDataStatus();
  const [pos, setPos] = React.useState("ALL");
  const [rankingFormat, setRankingFormat] = React.useState("ppr");
  const [search, setSearch] = React.useState("");
  const activeFormat = React.useMemo(() => rankingFormatFor(rankingFormat), [rankingFormat]);
  const scoring = activeFormat.scoring;
  const basePlayers = React.useMemo(
    () => window.buildDraftPool(window.PLAYERS || [], scoring),
    [nflStatus.projections, nflStatus.projectionsLoaded, nflStatus.weeklyLogs, nflStatus.weeklyLogsLoaded, scoring],
  );
  const defaultLeagueShape = window.rosterShapeLabel(activeFormat.settings, scoring);

  React.useEffect(() => {
    if (typeof window.setDraftScoringContext === "function") {
      window.setDraftScoringContext(scoring);
    }
  }, [scoring]);

  const enriched = React.useMemo(() => {
    return enrichForScoring(basePlayers, activeFormat);
  }, [basePlayers, activeFormat]);

  const rankedPool = React.useMemo(
    () => enriched.filter(p => p.pos !== "DST" && !p.unranked),
    [enriched],
  );
  const veteranPool = React.useMemo(
    () => rankedPool.filter(p => !window.isAdpBoardRookie?.(p)),
    [rankedPool],
  );

  const filtered = React.useMemo(() => {
    if (pos === "SOS") return [];
    const source = rankedPool;
    const positionFiltered = source
      .filter(p => pos === "ALL" || p.pos === pos || (pos === "FLEX" && ["RB", "WR", "TE"].includes(p.pos)))
      .filter(p => !search || `${p.name} ${p.team} ${p.pos}`.toLowerCase().includes(search.toLowerCase()));
    return sortRankedPlayers(positionFiltered, scoring, pos);
  }, [rankedPool, veteranPool, pos, search, scoring]);

  const byTier = Object.fromEntries(TIER_ORDER.map(t => [t, []]));
  const displayedPlayers = pos === "ALL"
    ? filtered
    : TIER_ORDER
      .flatMap(tier => filtered
        .map((player, index) => ({ ...player, displayRank: index + 1, tier: tierForOrderedRank(index, filtered.length) }))
        .filter(p => p.tier === tier));
  const riskTierByPlayerId = riskTierMapFor(displayedPlayers);
  displayedPlayers.forEach(p => { if (byTier[p.tier]) byTier[p.tier].push(p); });

  const totalCount = displayedPlayers.length;

  if (window.DRAFT_BOARD_PAUSED) {
    return (
      <div className="view-enter" style={{ maxWidth: 900, margin: "0 auto", padding: "48px 24px" }}>
        <div className="card" style={{ padding: 28 }}>
          <div className="mono" style={{ color: "var(--gold)", fontSize: 11, letterSpacing: "0.12em", textTransform: "uppercase" }}>Draft rankings paused</div>
          <h1 className="display" style={{ margin: "10px 0 8px", color: "var(--mint-soft)" }}>Draft rankings paused</h1>
          <p style={{ margin: 0, color: "var(--slate)", lineHeight: 1.6 }}>
            Draft rankings are intentionally disabled in this environment.
          </p>
        </div>
      </div>
    );
  }

  const projectionsLoading = window.REQUIRE_RAPIDAPI_DATA
    && (!nflStatus.projections || nflStatus.projections === "loading");
  const projectionsFailed = window.REQUIRE_RAPIDAPI_DATA
    && nflStatus.projections === "unavailable";
  const projectionsReady = window.REQUIRE_RAPIDAPI_DATA
    && nflStatus.projections === "live"
    && window.PROJECTIONS_OK;

  if (projectionsLoading) {
    return (
      <div className="view-enter" style={{ maxWidth: 900, margin: "0 auto", padding: "48px 24px" }}>
        <div className="card" style={{ padding: 28 }}>
          <div className="mono" style={{ color: "var(--mint)", fontSize: 11, letterSpacing: "0.12em", textTransform: "uppercase" }}>Loading projections</div>
          <h1 className="display" style={{ margin: "10px 0 8px", color: "var(--mint-soft)" }}>Building draft rankings</h1>
          <p style={{ margin: 0, color: "var(--slate)", lineHeight: 1.6 }}>
            Waiting for <code>/api/nfl/projections</code>. First load may take up to a minute.
          </p>
        </div>
      </div>
    );
  }
  if (projectionsFailed) {
    return (
      <div className="view-enter" style={{ maxWidth: 900, margin: "0 auto", padding: "48px 24px" }}>
        <div className="card" style={{ padding: 28 }}>
          <div className="mono" style={{ color: "var(--red)", fontSize: 11, letterSpacing: "0.12em", textTransform: "uppercase" }}>Projection load failed</div>
          <h1 className="display" style={{ margin: "10px 0 8px", color: "var(--mint-soft)" }}>Could not load projections</h1>
          <p style={{ margin: 0, color: "var(--slate)", lineHeight: 1.6 }}>
            {nflStatus.message || "The live projection request failed. Retry when the API is available."}
          </p>
          <button className="btn" onClick={() => window.hydrateProjectionsFromApi?.()} style={{ marginTop: 18 }}>
            Retry
          </button>
        </div>
      </div>
    );
  }
  if (projectionsReady && !basePlayers.length) {
    return (
      <div className="view-enter" style={{ maxWidth: 900, margin: "0 auto", padding: "48px 24px" }}>
        <div className="card" style={{ padding: 28 }}>
          <div className="mono" style={{ color: "var(--gold)", fontSize: 11, letterSpacing: "0.12em", textTransform: "uppercase" }}>Empty player pool</div>
          <h1 className="display" style={{ margin: "10px 0 8px", color: "var(--mint-soft)" }}>No draft players available</h1>
          <p style={{ margin: 0, color: "var(--slate)", lineHeight: 1.6 }}>
            Projections loaded successfully but returned no draftable players for this scoring format.
          </p>
        </div>
      </div>
    );
  }

  return (
    <div className="view-enter" style={{ maxWidth: 1440, margin: "0 auto", padding: pos === "SOS" ? "28px 32px 48px" : "24px 24px 32px" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", marginBottom: 18, flexWrap: "wrap", gap: 16 }}>
        <div>
          <div className="mono" style={{ fontSize: 10, color: "var(--mint)", textTransform: "uppercase", letterSpacing: "0.18em", marginBottom: 6 }}>
            Draft · 2026 Ranking System
          </div>
          <h1 className="display" style={{ margin: 0, fontSize: 32, color: "var(--mint-soft)", lineHeight: 1 }}>Player Breakdown</h1>
          <p style={{ marginTop: 6, color: "var(--slate)", fontSize: 13 }}>
            {pos === "SOS"
              ? "2026 opponents win % · easier schedules sit higher"
              : pos === "ALL"
                ? `${activeFormat.shortLabel || activeFormat.label} ${activeFormat.scoringLabel} overall rankings · ${defaultLeagueShape}`
                : `${activeFormat.label} ${activeFormat.scoringLabel} ${pos} tiers`} · {pos === "SOS" ? "32 teams" : `${totalCount} players`}
          </p>
        </div>

        <div style={{ display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
          <div style={{
            display: "flex", alignItems: "center", gap: 8,
            padding: "9px 12px", borderRadius: 10,
            border: "1px solid var(--green-600)", background: "var(--green-900)",
            minWidth: 240,
          }}>
            <window.IconSearch size={13} stroke="var(--slate)" />
            <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search player, team, position"
              style={{ background: "transparent", border: "none", outline: "none", color: "var(--mint-soft)", fontFamily: "Sora", fontSize: 13, width: "100%" }} />
          </div>
          <TabBar tabs={FORMAT_TABS} value={rankingFormat} onChange={setRankingFormat} />
        </div>
      </div>

      {activeFormat.isSuperflex && (
        <div className="mono" style={{
          marginBottom: 14, padding: "10px 12px", borderRadius: 8,
          border: "1px solid rgba(149,249,174,0.22)", background: "rgba(149,249,174,0.06)",
          color: "var(--mint-soft)", fontSize: 10, letterSpacing: "0.08em", textTransform: "uppercase"
        }}>
          Superflex — QB replacement is QB{enriched[0]?.boardSettings?.replacementRanks?.QB || 25}. Quarterbacks are ranked accordingly.
        </div>
      )}

      <div style={{ marginBottom: 14 }}>
        <TabBar tabs={[
          { id: "ALL",  label: "Overall", count: rankedPool.length },
          { id: "QB",   label: "QB",      count: veteranPool.filter(p => p.pos === "QB").length },
          { id: "RB",   label: "RB",      count: veteranPool.filter(p => p.pos === "RB").length },
          { id: "WR",   label: "WR",      count: veteranPool.filter(p => p.pos === "WR").length },
          { id: "TE",   label: "TE",      count: veteranPool.filter(p => p.pos === "TE").length },
          { id: "FLEX", label: "FLEX" },
          { id: "K",    label: "K",       count: veteranPool.filter(p => p.pos === "K").length },
          { id: "SOS",  label: "SOS" },
        ]} value={pos} onChange={setPos} />
      </div>

      {pos === "SOS" ? (
        <SosScheduleChart />
      ) : (
      <div className="card" style={{ overflow: "visible" }}>
        <div style={{
          display: "grid", gridTemplateColumns: DR_COLS,
          padding: "12px 18px", background: "var(--green-900)",
          borderBottom: "1px solid var(--green-600)",
          fontSize: 10, color: "var(--slate)", textTransform: "uppercase", letterSpacing: "0.1em",
          fontFamily: "JetBrains Mono", fontWeight: 600, alignItems: "center"
        }}>
          <DrHead align="center">Rank</DrHead>
          <DrHead align="flex-start">Player</DrHead>
          <DrHead align="center">Proj (Total)</DrHead>
          <DrHead align="center">Bye</DrHead>
          <DrHead align="center">SOS</DrHead>
          <DrHead align="center">Risk</DrHead>
          <DrHead align="center">Boom/Bust</DrHead>
          <DrHead align="center">YoY Pts/Gm</DrHead>
          <DrHead align="flex-end">Value</DrHead>
        </div>

        {pos === "ALL" ? displayedPlayers.map((p, index) => (
          <DraftRankRow key={p.id} player={p} rowRank={index + 1} scoring={scoring} riskTier={riskTierByPlayerId[p.id]} onSelectPlayer={onSelectPlayer} />
        )) : TIER_ORDER.map(tier => {
          const tierPlayers = byTier[tier];
          if (!tierPlayers || !tierPlayers.length) return null;
          const meta = VALUE_TIER_META[tier];
          const range = rankTierRangeLabel(tier, totalCount);
          return (
            <React.Fragment key={tier}>
              <div style={{
                display: "flex", alignItems: "center", gap: 14,
                padding: "12px 18px",
                background: "rgba(149,249,174,0.04)",
                borderBottom: "1px solid var(--green-600)",
              }}>
                <HelpTip
                  align="start"
                  label={
                    <span className="num" style={{
                      display: "inline-flex", alignItems: "center", justifyContent: "center",
                      width: 36, height: 28, borderRadius: 6,
                      background: meta.color === "var(--mint)" ? "rgba(149,249,174,0.14)"
                        : meta.color === "var(--mint-soft)" ? "rgba(217,255,228,0.10)"
                        : meta.color === "var(--gold)" ? "rgba(242,201,76,0.12)"
                        : "rgba(255,94,94,0.10)",
                      border: `1px solid ${meta.color}55`,
                      fontSize: 14, color: meta.color, fontWeight: 900
                    }}>{tier}</span>
                  }>
                  <strong style={{ color: meta.color, display: "block", marginBottom: 4 }}>
                    {meta.label} · Rank tier {tier} · {range}
                  </strong>
                  Players grouped by numerical position rank. 949 Value is a separate highlight and does not control placement. {meta.note}
                </HelpTip>
                <span className="mono" style={{ fontSize: 10, color: meta.color, letterSpacing: "0.12em", textTransform: "uppercase", fontWeight: 700 }}>
                  {meta.label} · {range}
                </span>
                <span style={{ fontSize: 12, color: "var(--slate)" }}>{meta.note}</span>
                <span style={{ flex: 1, height: 1, background: "linear-gradient(to right, var(--green-600), transparent)" }} />
                <span className="mono" style={{ fontSize: 10, color: "var(--slate-dim)" }}>{tierPlayers.length} player{tierPlayers.length === 1 ? "" : "s"}</span>
              </div>

              {tierPlayers.map(p => (
                <DraftRankRow key={p.id} player={p} rowRank={p.displayRank} scoring={scoring} riskTier={riskTierByPlayerId[p.id]} onSelectPlayer={onSelectPlayer} />
              ))}
            </React.Fragment>
          );
        })}

        {!totalCount && (
          <div style={{ padding: 60, textAlign: "center", color: "var(--slate)", fontSize: 14 }}>
            No players match — try adjusting filters.
          </div>
        )}
      </div>
      )}
    </div>
  );
}

function SosScheduleChart() {
  const [rows, setRows] = React.useState(() => (window.NFL_SOS || []).slice().sort((a, b) => (a.sosRank || 99) - (b.sosRank || 99)));
  React.useEffect(() => {
    const sync = () => setRows((window.NFL_SOS || []).slice().sort((a, b) => (a.sosRank || 99) - (b.sosRank || 99)));
    window.addEventListener("nfl-sos", sync);
    if (!(window.NFL_SOS || []).length && typeof window.hydrateNflSosFromApi === "function") {
      window.hydrateNflSosFromApi().then(sync);
    }
    return () => window.removeEventListener("nfl-sos", sync);
  }, []);
  const winPcts = rows.map((row) => Number(row.opponentWinPct) || 0);
  const minPct = winPcts.length ? Math.min(...winPcts) : 0.4;
  const maxPct = winPcts.length ? Math.max(...winPcts) : 0.6;
  const mid = (minPct + maxPct) / 2;
  const winSpan = Math.max(0.01, maxPct - minPct);
  const yT = (pct) => ((Number(pct) || mid) - minPct) / winSpan;
  const midIndex = Math.max(0, (rows.length - 1) / 2);
  const leftTeam = rows[Math.floor(midIndex)];
  const rightTeam = rows[Math.ceil(midIndex)] || leftTeam;
  const midLinePct = leftTeam && rightTeam
    ? +(((yT(leftTeam.opponentWinPct) + yT(rightTeam.opponentWinPct)) / 2) * 100).toFixed(2)
    : 50;

  return (
    <div className="card" style={{ padding: 22, position: "relative", overflow: "hidden" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", gap: 16, marginBottom: 14, flexWrap: "wrap" }}>
        <div>
          <div className="mono" style={{ color: "var(--mint)", fontSize: 10, letterSpacing: "0.16em", textTransform: "uppercase" }}>2026 Strength of Schedule</div>
          <h2 className="display" style={{ margin: "6px 0 0", color: "var(--mint-soft)", fontSize: 22 }}>Opponents win %</h2>
        </div>
        <div className="mono" style={{ color: "var(--slate)", fontSize: 10, letterSpacing: "0.08em", textTransform: "uppercase" }}>
          Green = easier · Red = harder
        </div>
      </div>
      {!rows.length ? (
        <div style={{ padding: 48, textAlign: "center", color: "var(--slate)" }}>SOS not loaded yet. Refresh after projections hydrate.</div>
      ) : (
        <div style={{
          position: "relative",
          width: "100%",
          aspectRatio: "2000 / 1200",
          borderRadius: 16,
          overflow: "hidden",
          border: "1px solid var(--green-600)",
          background: "var(--green-900)",
        }}>
          <div className="mono" style={{ position: "absolute", left: 16, top: 14, color: "var(--mint)", fontSize: 9, letterSpacing: "0.1em" }}>EASIER</div>
          <div className="mono" style={{ position: "absolute", left: 16, bottom: 14, color: "var(--red)", fontSize: 9, letterSpacing: "0.1em" }}>HARDER</div>
          <div style={{ position: "absolute", inset: 56 }}>
            <div style={{
              position: "absolute", inset: 0, borderRadius: 8, pointerEvents: "none",
              background: `linear-gradient(to bottom, rgba(149,249,174,0.22) 0%, rgba(149,249,174,0.06) ${Math.max(0, midLinePct - 2)}%, rgba(240,68,42,0.08) ${Math.min(100, midLinePct + 2)}%, rgba(240,68,42,0.26) 100%)`,
            }} />
            <div style={{ position: "absolute", left: 0, right: 0, top: `${midLinePct}%`, height: 1, background: "rgba(5,8,7,0.7)" }} />
            {rows.map((row, index) => {
              const spanX = Math.max(1, rows.length - 1);
              const left = `${(index / spanX) * 100}%`;
              const top = `${yT(row.opponentWinPct) * 100}%`;
              const team = window.TEAMS?.[row.team] || {};
              return (
                <div key={row.team} title={`${row.team} · SOS ${row.sosRank} · opp win ${(Number(row.opponentWinPct) * 100).toFixed(1)}%`} style={{
                  position: "absolute", left, top, transform: "translate(-50%, -50%)",
                  display: "grid", justifyItems: "center", gap: 4,
                }}>
                  <span className="mono" style={{
                    width: 34, height: 34, borderRadius: "50%",
                    display: "flex", alignItems: "center", justifyContent: "center",
                    background: team.color || "var(--green-800)",
                    color: "#F4FFF7", fontSize: 9, fontWeight: 800,
                    border: "1px solid rgba(5,8,7,0.35)",
                    boxShadow: "0 2px 8px rgba(0,0,0,0.35)",
                  }}>{row.team}</span>
                  <span className="mono" style={{ color: "var(--mint-soft)", fontSize: 8 }}>{row.sosRank}</span>
                </div>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}

function tierForOrderedRank(index, total) {
  const pct = (index + 1) / Math.max(1, total);
  if (pct <= 0.04) return "A+";
  if (pct <= 0.14) return "A";
  if (pct <= 0.34) return "B";
  if (pct <= 0.58) return "C";
  if (pct <= 0.82) return "D";
  return "F";
}

function DraftRankRow({ player, rowRank, scoring, riskTier, onSelectPlayer }) {
  const projTotal = seasonProjectedTotal(player, scoring);
  const sos = window.scheduleEaseRank(player);
  const yoy = yoyTrendData(player, scoring);
  const yoyProfile = yoyTrendProfile(yoy?.values);
  const valueGrade = window.seasonValueGrade(player);
  return (
    <button key={player.id} onClick={() => onSelectPlayer && !player.synthetic && onSelectPlayer(player)} style={{
      display: "grid", gridTemplateColumns: DR_COLS, gap: 0, padding: "12px 18px",
      background: "transparent", border: "none", borderBottom: "1px solid var(--green-600)",
      cursor: player.synthetic ? "default" : "pointer", width: "100%", textAlign: "left",
      alignItems: "center", transition: "background 0.12s",
    }}
      onMouseEnter={e => e.currentTarget.style.background = "rgba(22,56,36,0.55)"}
      onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
      <DrCell align="center">
        <span className="num" style={{ fontSize: 17, color: "var(--mint-soft)" }}>{rowRank}</span>
      </DrCell>
      <DrCell align="flex-start">
        <PlayerCell player={player} />
      </DrCell>
      <DrCell align="center">
        <span className="num" style={{ fontSize: 16, color: "var(--mint)" }}>{projTotal.toFixed(0)}</span>
      </DrCell>
      <DrCell align="center">
        <span className="mono" style={{ fontSize: 11, color: "var(--mint)" }}>
          {byeWeekFor(player) ? `W${byeWeekFor(player)}` : "TBD"}
        </span>
      </DrCell>
      <DrCell align="center">
        <SosBadge rank={sos} />
      </DrCell>
      <DrCell align="center">
        <DrRiskProfile player={player} riskTier={riskTier} />
      </DrCell>
      <DrCell align="center">
        <DrBoomBustProfile player={player} scoring={scoring} />
      </DrCell>
      <DrCell align="center">
        {yoy?.values && yoy.values.length >= 2
          ? (
            <span title={`${yoyProfile.label}. ${yoy.formula}`}>
              <Sparkline data={yoy.values} width={96} height={28} color={yoyProfile.color} projectedTail />
            </span>
          )
          : <span className="mono" style={{ fontSize: 9, color: "var(--slate-dim)", letterSpacing: "0.08em", textTransform: "uppercase" }}>No Data</span>}
      </DrCell>
      <DrCell align="flex-end">
        <DrValueProfile player={player} valueGrade={valueGrade} />
      </DrCell>
    </button>
  );
}

function DrRiskProfile({ player, riskTier }) {
  const tier = riskTier || { label: "Med", color: "var(--gold)", score: riskCompositeScore(player), percentile: 0.5 };
  const projection = Math.max(0.1, player.adjusted || player.proj || 0);
  const band = `${(player.floor || projection).toFixed(1)}-${(player.ceiling || projection).toFixed(1)} PPG`;
  const games = player.nine49Model?.historyGames || player.historyGames || [];
  const gamesLabel = games.length
    ? `${games.map(count => `${count}g`).join(" / ")}`
    : "availability prior";
  return (
    <div
      title={`Risk ${tier.label}. Composite score ${Math.round(tier.score || 0)}. Uses floor predictability, ceiling/floor band width, games played/availability, and ceiling probability from the projected range. Band ${band}. Games input: ${gamesLabel}.`}
      style={{ display: "inline-flex", alignItems: "center", gap: 6, fontFamily: "JetBrains Mono", fontSize: 10, cursor: "help", color: tier.color }}
    >
      <span style={{ width: 7, height: 7, borderRadius: "50%", background: tier.color, boxShadow: `0 0 0 2px ${tier.color}22` }} />
      <span style={{ fontWeight: 800 }}>{tier.label}</span>
    </div>
  );
}

function DrBoomBustProfile({ player, scoring }) {
  const profile = weeklyConsistencyProfile(player, scoring);
  if (!profile.available || !profile.sampleOk) {
    const label = profile.games ? "Insufficient logs" : "Pending logs";
    const title = profile.games
      ? `Boom/Bust needs at least 4 scored weeks with a projection. Current sample: n=${profile.games}.`
      : "Boom/Bust will populate after weekly actuals can be compared to that week's projection.";
    return (
      <span
        className="mono"
        title={`${title} Hit is 70-130% of projection. Boom is above 130%. Bust is below 70%.`}
        style={{ color: "var(--slate-dim)", fontSize: 9, letterSpacing: "0.06em", textTransform: "uppercase", cursor: "help" }}
      >
        {label}
      </span>
    );
  }

  return (
    <div
      title={`${profile.games} games is 100% of this sample. Boom ${profile.boomPct}% scored above 130% of projection. Bust ${profile.bustPct}% scored below 70%. The rest is Hit (70-130%). ${profile.source}.`}
      style={{ display: "inline-flex", flexDirection: "column", gap: 3, width: 140, fontFamily: "JetBrains Mono", fontSize: 9, cursor: "help" }}
    >
      <span style={{
        position: "relative", display: "block", width: "100%", height: 9,
        border: "1px solid var(--green-600)", borderRadius: 999, overflow: "hidden",
        background: "rgba(255,255,255,0.04)",
      }}>
        <span style={{ position: "absolute", inset: "0 auto 0 0", width: `${profile.boomPct}%`, background: "var(--mint)" }} />
        <span style={{ position: "absolute", inset: "0 0 0 auto", width: `${profile.bustPct}%`, background: "var(--red)" }} />
      </span>
      <span style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 4, color: "var(--mint-soft)" }}>
        <span style={{ color: "var(--mint)", fontWeight: 800 }}>{profile.boomPct}% boom</span>
        <span style={{ color: "var(--red)", fontWeight: 800 }}>{profile.bustPct}% bust</span>
      </span>
    </div>
  );
}

function DrValueProfile({ player, valueGrade }) {
  const valueIndex = player.valueIndex ? ` Value index ${player.valueIndex.toFixed(2)}x.` : "";
  const valueTitle = `949 player grade: ${valueGrade.letter} ${player.pos}.${valueIndex}`;
  const floorNote = player.positionFloorNote ? ` ${player.positionFloorNote}` : "";
  return (
    <div
      title={`${valueTitle}${floorNote}`}
      style={{ display: "inline-flex", alignItems: "center", justifyContent: "flex-end", gap: 6, fontFamily: "JetBrains Mono", fontSize: 10, cursor: "help" }}
    >
      <span style={{ color: valueGrade.color, fontWeight: 900, fontSize: 18 }}>{valueGrade.letter}</span>
    </div>
  );
}

function SosBadge({ rank }) {
  if (!Number.isFinite(rank)) {
    return <span className="mono" style={{ color: "var(--slate-dim)", fontSize: 10, letterSpacing: "0.08em" }}>TBD</span>;
  }
  // 1 = hardest schedule, 32 = easiest. Color shifts toward mint as it gets easier.
  const t = (rank - 1) / 31;
  const color = t > 0.66 ? "var(--mint)" : t > 0.33 ? "var(--gold)" : "var(--red)";
  return (
    <div title={`SOS ${rank}/32 · ${rank <= 11 ? "Hard" : rank >= 22 ? "Easy" : "Average"} · 1 hardest, 32 easiest`} style={{ display: "inline-flex", flexDirection: "column", alignItems: "center", gap: 4 }}>
      <span className="mono" style={{ color, fontSize: 12, fontWeight: 700 }}>
        {rank}<span style={{ color: "var(--slate-dim)", marginLeft: 2 }}>/32</span>
      </span>
      <span style={{ display: "inline-flex", width: 60, height: 4, background: "var(--green-600)", borderRadius: 2, overflow: "hidden" }}>
        <span style={{ width: `${t * 100}%`, background: color, borderRadius: 2 }} />
      </span>
    </div>
  );
}

Object.assign(window, {
  DraftRankingsView,
  rankingFormatFor,
  enrichForScoring,
  sortRankedPlayers,
  FORMAT_TABS,
  TIER_ORDER,
  VALUE_TIER_META,
  tierForOrderedRank,
  rankTierRangeLabel,
  yoyTrendData,
  yoyTrendProfile,
  riskTierMapFor,
  DraftRankRow,
  DrHead,
  DrCell,
  DrRiskProfile,
  DrBoomBustProfile,
  DrValueProfile,
  HelpTip,
});
