File size: 5,412 Bytes
f7cecf3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// 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,
  };
};