File size: 11,578 Bytes
f7cecf3
 
 
 
 
4bf65e0
f7cecf3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4bf65e0
 
 
 
 
 
 
 
 
 
 
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import React from "react";
import { Zap, ExternalLink } from "lucide-react";
import { CHIP_CONFIG } from "../utils/fplLogic";

export const SolverOutputPanel = ({
  pendingSolutions, setPendingSolutions, isSolving, globalPlayers, applySolution, appliedPlanSummary, setAppliedPlanSummary, baselineEv = 0,comprehensiveSettings
}) => {

  // BULLETPROOF RELATIVE EV
  const getRelativeEv = (sol) => {
    if (baselineEv === undefined || !sol) return "+0.00";

    const base = sol.lockedBaselineEv !== undefined ? sol.lockedBaselineEv : baselineEv;

    if (typeof sol === "number") {
      const diff = sol - base;
      return diff >= 0 ? `+${diff.toFixed(2)}` : diff.toFixed(2);
    }

    if (!sol.plan || !Array.isArray(sol.plan) || sol.plan.length === 0) {
      const fallbackEv = sol.ev !== undefined ? sol.ev : 0;
      const diff = fallbackEv - base;
      return diff >= 0 ? `+${diff.toFixed(2)}` : diff.toFixed(2);
    }
    
    let pathEV = 0;
    let hasValidGw = false;

    sol.plan.forEach(gwPlan => {
      const gw = gwPlan.gw;
      if (gw === undefined) return;
      hasValidGw = true;

      const gwChip = gwPlan.chip;
      const gwCapMult = gwChip === "tc" ? 3 : 2;
      let gwPts = 0;

      const getPlayer = (id) => globalPlayers.find(p => String(p.ID) === String(id));

      (gwPlan.lineup || []).forEach(id => {
        const p = getPlayer(id);
        if (p && !p.isBlank) {
            const pts = Number(p[`${gw}_Pts`]) || 0;
            gwPts += pts * (String(p.ID) === String(gwPlan.captain) ? gwCapMult : 1);
        }
      });

      let ofIdx = 0;
      (gwPlan.bench || []).forEach(id => {
        const p = getPlayer(id);
        if (p && !p.isBlank) {
          const pts = Number(p[`${gw}_Pts`]) || 0;
          if (gwChip === "bb") {
            gwPts += pts;
          } else {
            // THE FIX: Output Panel now perfectly matches Python and the main UI!
            const rawBw = comprehensiveSettings?.bench_weights || { 0: 0.03, 1: 0.21, 2: 0.06, 3: 0.002 };
            const gkWeight = Number(rawBw[0] || 0.03);
            const outWeights = [Number(rawBw[1] || 0.21), Number(rawBw[2] || 0.06), Number(rawBw[3] || 0.002)];

            if (p.Pos === "G") {
              gwPts += pts * gkWeight;
            } else {
              gwPts += pts * (outWeights[ofIdx] || 0.02);
              ofIdx++;
            }
          }
        }
      });
      pathEV += gwPts - (gwPlan.hits || 0) * 4;
    });

    if (!hasValidGw || Number.isNaN(pathEV)) {
        const fallbackEv = sol.ev !== undefined ? sol.ev : 0;
        const diff = fallbackEv - base;
        return diff >= 0 ? `+${diff.toFixed(2)}` : diff.toFixed(2);
    }

    const diff = pathEV - base;
    return diff >= 0 ? `+${diff.toFixed(2)}` : diff.toFixed(2);
  };

  return (
    <div className="w-full bg-slate-950 border border-slate-800 rounded-2xl flex flex-col h-auto shadow-2xl overflow-hidden relative min-h-[320px]">
      <div className="border-b border-slate-800 px-5 py-4 bg-slate-900/50 flex items-center justify-between">
        
        {/* Left Side: Original Title & Description */}
        <div className="flex flex-col">
          <h2 className="text-sm font-black uppercase tracking-widest text-slate-300">Solver output</h2>
          <p className="text-[10px] text-slate-500 mt-1">Nothing changes your squad until you apply a path.</p>
        </div>

        {/* Right Side: Sleek, Minimalist Credit */}
        {/* Right Side: Sleek, Minimalist Credit */}
        <a 
          href="https://github.com/sertalpbilal/FPL-Optimization-Tools" 
          target="_blank" 
          rel="noopener noreferrer"
          className="group flex items-center gap-1 text-[9px] font-bold uppercase tracking-widest transition-colors text-right whitespace-nowrap ml-4 shrink-0"
        >
          <span className="text-slate-600">Credit</span>
          <span className="text-slate-500 group-hover:text-luigi-400 transition-colors">Sertalp-Moose Solver</span>
          <ExternalLink size={10} className="text-slate-600 group-hover:text-luigi-400 transition-colors" />
        </a>

      </div>

      <div className="flex-1 flex flex-col p-5 overflow-y-auto custom-scrollbar">
        {pendingSolutions.length > 0 && !isSolving && (
          <div className="flex flex-col gap-4">
            <div className="flex items-center justify-between mb-2">
              <h3 className="text-slate-200 font-black">Optimal Paths Found</h3>
              <button onClick={() => setPendingSolutions([])} className="text-xs text-slate-500 hover:text-red-400 font-bold uppercase transition-colors">Clear</button>
            </div>

            {pendingSolutions.map((sol, index) => (
              <div key={index} className="bg-slate-900 border border-luigi-500/30 rounded-xl p-4 flex flex-col gap-4">
                <div className="flex justify-between items-center border-b border-slate-800 pb-2">
                  <div className="flex items-center gap-2">
                    <span className="font-black text-slate-300">ITERATION {sol.id || index + 1}</span>
                    {sol.chips_used && Object.entries(sol.chips_used).map(([gw, chip]) => {
                        const cfg = CHIP_CONFIG[chip];
                        return cfg ? <span key={gw} className={`text-[9px] font-black px-1.5 py-0.5 rounded ${cfg.badge}`} title={`${cfg.label} in GW${gw}`}>{cfg.short}{gw}</span> : null;
                      })}
                  </div>
                  <div className="flex flex-col items-end gap-0.5">
                    <span className="text-luigi-400 font-mono font-bold text-sm">{getRelativeEv(sol)} pts</span>
                    {sol.objective_score != null && <span className="text-slate-400 font-mono text-[10px]">eval: {sol.objective_score.toFixed(2)}</span>}
                  </div>
                </div>

                <div className="flex flex-col gap-2">
                  {sol.plan.map((gwPlan) => (
                    (gwPlan.transfers_in.length > 0 || gwPlan.transfers_out.length > 0) && (
                      <div key={gwPlan.gw} className="bg-slate-950 rounded p-2 text-xs">
                        <div className="text-slate-500 font-bold mb-2 flex justify-between items-center">
                          <div className="flex items-center gap-2">
                            <span className="bg-slate-800 px-2 py-1 rounded text-slate-300">GW {gwPlan.gw}</span>
                            {gwPlan.chip && CHIP_CONFIG[gwPlan.chip] && <span className={`text-[9px] font-black px-1.5 py-0.5 rounded ${CHIP_CONFIG[gwPlan.chip].badge}`}>{CHIP_CONFIG[gwPlan.chip].short}{gwPlan.gw}</span>}
                          </div>
                          <div className="flex gap-3">
                            <span className="text-emerald-400 font-mono">ITB: £{Math.abs(gwPlan.itb) < 0.05 ? "0.0" : Number(gwPlan.itb).toFixed(1)}</span>
                            <span className="text-cyan-400 font-mono">FT Spend: {gwPlan.chip === "wc" || gwPlan.chip === "fh" ? `${gwPlan.transfers_out?.length || 0}/∞` : `${gwPlan.transfers_out?.length || 0}/${gwPlan.ft_at_start ?? 1}${gwPlan.hits > 0 ? ` (-${gwPlan.hits * 4})` : ""}`}</span>
                          </div>
                        </div>
                        {gwPlan.chip === "wc" || gwPlan.chip === "fh" ? <p className="text-[10px] text-slate-500 italic mb-1">{gwPlan.chip === "wc" ? "Wildcard active — unlimited free transfers" : "Free Hit active — squad reverts after the FH"}</p> : null}
                        {gwPlan.transfers_out.map((id, i) => (
                          <div key={i} className="flex justify-between items-center text-slate-300 font-mono py-0.5">
                            <span className="text-red-400 truncate w-[40%]">{globalPlayers.find((p) => String(p.ID) === String(id))?.Name || id}</span>
                            <span className="text-slate-600 font-bold">»</span>
                            <span className="text-emerald-400 truncate w-[40%] text-right">{globalPlayers.find((p) => String(p.ID) === String(gwPlan.transfers_in[i]))?.Name || gwPlan.transfers_in[i]}</span>
                          </div>
                        ))}
                      </div>
                    )
                  ))}
                </div>
                <button onClick={() => applySolution(sol)} className="w-full bg-slate-800 hover:bg-luigi-500 hover:text-slate-950 text-luigi-400 font-bold py-2 rounded-lg transition-colors text-sm">Apply Path</button>
              </div>
            ))}
          </div>
        )}

        {!isSolving && pendingSolutions.length === 0 && (
          appliedPlanSummary ? (
            <div className="flex flex-col gap-3 p-1">
              <div className="flex items-center justify-between mb-1">
                <h4 className="text-slate-300 font-bold text-xs uppercase tracking-wider">Last Applied · {appliedPlanSummary.horizon}</h4>
                <button onClick={() => setAppliedPlanSummary(null)} className="text-slate-600 hover:text-red-400 text-xs font-bold">✕</button>
              </div>
              <div className="text-[10px] text-slate-500 font-mono">{getRelativeEv(appliedPlanSummary)} pts {appliedPlanSummary.objectiveScore != null && ` · eval ${appliedPlanSummary.objectiveScore.toFixed(2)}`}</div>
              {appliedPlanSummary.transfers.map((t, i) => (
                <div key={i} className="bg-slate-900 rounded-lg p-2.5 text-xs">
                  <div className="flex items-center justify-between gap-2 mb-1.5">
                    <div className="flex items-center gap-2">
                      <span className="text-slate-400 font-bold">GW {t.gw}</span>
                      {t.chip && CHIP_CONFIG[t.chip] && <span className={`text-[9px] px-1 py-0.5 rounded font-black ${CHIP_CONFIG[t.chip].badge}`}>{CHIP_CONFIG[t.chip].short}{t.gw}</span>}
                    </div>
                    <div className="flex gap-2 text-[9px] font-mono">
                      <span className="text-cyan-400">FT Spend: {t.chip === "wc" || t.chip === "fh" ? `${t.outs?.length || 0}/∞` : `${t.outs?.length || 0}/${t.ft_at_start ?? 1}${t.hits > 0 ? ` (-${t.hits * 4})` : ""}`}</span>
                      <span className="text-emerald-400">£{Math.abs(t.itb) < 0.05 ? "0.0" : Number(t.itb).toFixed(1)}m</span>
                    </div>
                  </div>
                  {t.outs.length === 0 && t.ins.length === 0 ? (
                    <span className="text-slate-600 italic text-[10px]">{t.chip ? `${CHIP_CONFIG[t.chip]?.label || t.chip} active` : 'Hold — no transfers'}</span>
                  ) : (
                    t.outs.map((name, j) => (
                      <div key={j} className="flex items-center gap-1 py-0.5 font-mono">
                        <span className="text-red-400 truncate flex-1">{name}</span>
                        <span className="text-slate-600 font-bold shrink-0">»</span>
                        <span className="text-emerald-400 truncate flex-1 text-right">{t.ins[j] || "?"}</span>
                      </div>
                    ))
                  )}
                </div>
              ))}
            </div>
          ) : (
            <div className="flex flex-col items-center justify-center gap-3 min-h-[200px] text-slate-500 text-sm text-center px-4">
              <Zap size={28} className="text-slate-700" />
              Configure settings and hit <span className="text-luigi-400 font-bold">Solve</span> in the left panel.
            </div>
          )
        )}
      </div>
    </div>
  );
};