// src/utils/fplLogic.js export const CHIP_CONFIG = { wc: { label: "Wildcard", short: "WC", desc: "Unlimited free transfers for 1 GW. FTs reset to max after.", dot: "bg-yellow-400", text: "text-yellow-400", activeBg: "bg-yellow-500 text-slate-950", inactiveBg: "bg-slate-800 text-slate-500 hover:bg-slate-700", border: "border-yellow-700/40", badge: "bg-yellow-500/20 text-yellow-300", }, fh: { label: "Free Hit", short: "FH", desc: "Unlimited transfers for 1 GW. Squad reverts to original after.", dot: "bg-orange-400", text: "text-orange-400", activeBg: "bg-orange-500 text-slate-950", inactiveBg: "bg-slate-800 text-slate-500 hover:bg-slate-700", border: "border-orange-700/40", badge: "bg-orange-500/20 text-orange-300", }, bb: { label: "Bench Boost", short: "BB", desc: "All 15 squad players score points this GW.", dot: "bg-emerald-400", text: "text-emerald-400", activeBg: "bg-emerald-500 text-slate-950", inactiveBg: "bg-slate-800 text-slate-500 hover:bg-slate-700", border: "border-emerald-700/40", badge: "bg-emerald-500/20 text-emerald-300", }, tc: { label: "Triple Captain", short: "TC", desc: "Captain earns 3× points instead of 2× this GW.", dot: "bg-purple-400", text: "text-purple-400", activeBg: "bg-purple-500 text-white", inactiveBg: "bg-slate-800 text-slate-500 hover:bg-slate-700", border: "border-purple-700/40", badge: "bg-purple-500/20 text-purple-300", }, }; export const getPlayerPrice = (p) => { // 1. Exact FPL Calculation if we have both purchase_price and now_cost let pp = p.purchase_price !== undefined ? Number(p.purchase_price) : undefined; let nc = p.now_cost !== undefined ? Number(p.now_cost) : undefined; // FPL API sometimes sends prices multiplied by 10 (e.g., 43 for 4.3m) if (pp > 20) pp = pp / 10; if (nc > 20) nc = nc / 10; if (pp !== undefined && nc !== undefined) { if (nc > pp) { // Gain is 0.1m for every 0.2m increase. // To avoid floating point errors, multiply by 10, floor the difference/2, and divide by 10. const diff = Math.round((nc - pp) * 10); const gain = Math.floor(diff / 2); return (Math.round(pp * 10) + gain) / 10; } else { // FPL Rule: You take full losses on price drops. return nc; } } // 2. Fallbacks if purchase_price isn't explicitly available if (p.selling_price !== undefined && p.selling_price !== null) { const sp = Number(p.selling_price); return sp > 20 ? sp / 10 : sp; } if (p.sell_price !== undefined && p.sell_price !== null) { const sp = Number(p.sell_price); return sp > 20 ? sp / 10 : sp; } if (p.Price !== undefined) return Number(p.Price); if (nc !== undefined) return nc; return 0; }; export function normalizeBenchGkFirst(teamData, gw) { if (!teamData.length || teamData.length < 15 || !gw) return teamData; const starters = teamData.slice(0, 11); const bench = teamData.slice(11, 15); const getEV = (p) => Number(p[`${gw}_Pts`]) || 0; const nonBlank = bench.filter((p) => !p.isBlank); const blanks = bench.filter((p) => p.isBlank); const gk = nonBlank.find((p) => p.Pos === "G"); const outfield = nonBlank.filter((p) => p.Pos !== "G"); outfield.sort((a, b) => getEV(b) - getEV(a)); const ordered = gk ? [gk, ...outfield] : [...outfield]; const newBench = [...ordered, ...blanks]; while (newBench.length < 4) { newBench.push({ ID: `blank_pad_${Date.now()}_${newBench.length}`, isBlank: true, Pos: "M", Name: "", Team: "", Price: 0, }); } return [...starters, ...newBench.slice(0, 4)]; } export function countSquadByPos(players) { const c = { G: 0, D: 0, M: 0, F: 0 }; players.filter((p) => !p.isBlank && p.Pos).forEach((p) => { if (c[p.Pos] !== undefined) c[p.Pos] += 1; }); return c; } export function isValidFplSquad(players) { const c = countSquadByPos(players); return c.G === 2 && c.D === 5 && c.M === 5 && c.F === 3; } export const getOptimalLayout = (players, gw) => { if (!players.length || !gw || players.some((p) => p.isBlank)) return null; const getEV = (p) => Number(p[`${gw}_Pts`]) || 0; let gks = players.filter((p) => p.Pos === "G").sort((a, b) => getEV(b) - getEV(a)); let defs = players.filter((p) => p.Pos === "D").sort((a, b) => getEV(b) - getEV(a)); let mids = players.filter((p) => p.Pos === "M").sort((a, b) => getEV(b) - getEV(a)); let fwds = players.filter((p) => p.Pos === "F").sort((a, b) => getEV(b) - getEV(a)); const starters = []; if (gks.length) starters.push(gks.shift()); starters.push(...defs.splice(0, 3), ...mids.splice(0, 2), ...fwds.splice(0, 1)); const remaining = [...defs, ...mids, ...fwds].sort((a, b) => getEV(b) - getEV(a)); starters.push(...remaining.splice(0, 11 - starters.length)); const finalStarters = [ ...starters.filter((p) => p.Pos === "G"), ...starters.filter((p) => p.Pos === "D"), ...starters.filter((p) => p.Pos === "M"), ...starters.filter((p) => p.Pos === "F"), ]; const benchGk = gks.length ? gks[0] : null; const benchRest = remaining.sort((a, b) => getEV(b) - getEV(a)); const bench = benchGk ? [benchGk, ...benchRest] : benchRest; const topStarters = [...finalStarters].sort((a, b) => getEV(b) - getEV(a)); return { optimalArray: [...finalStarters, ...bench], cap: topStarters[0]?.ID, vice: topStarters[1]?.ID, }; };