File size: 15,263 Bytes
f7cecf3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
052f3f2
f7cecf3
052f3f2
f7cecf3
052f3f2
f7cecf3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
052f3f2
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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
import React, { useState } from "react";
import { X, Power, Info, RotateCcw } from "lucide-react";

// The absolute baseline FPL defaults as defined in your comprehensive_settings.json
export const DEFAULT_SETTINGS = {
  enabled: false,
  secs: 600,
  hit_limit: 0,
  no_transfer_last_gws: 0,
  keep_top_ev_percent: 7,
  ft_use_penalty: 0.1,
  itb_loss_per_transfer: 0.0,
  vcap_weight: 0.1,
  opposing_play_penalty: 0.5,
  use_ft_value_list: false, // Sub-toggle for FT behavior
  ft_value: 1.5,
  xmin_lb: 45,
  ft_value_list: { "2": 2, "3": 1.6, "4": 1.3, "5": 1.1 },
  bench_weights: { "0": 0.03, "1": 0.21, "2": 0.06, "3": 0.002 },
  randomization_strength: 1.0,
  iteration_criteria: "this_gw_transfer_in_out",
  iteration_diff: 2,
};

export function AdvancedSettingsModal({
  setShowAdvancedSettings,
  comprehensiveSettings,
  setComprehensiveSettings,
}) {
  const [isEnabled, setIsEnabled] = useState(
    comprehensiveSettings.enabled ?? false
  );

  const handleToggle = () => {
    const newState = !isEnabled;
    setIsEnabled(newState);
    setComprehensiveSettings((prev) => ({ ...prev, enabled: newState }));
  };

  const handleResetToDefaults = () => {
    if (window.confirm("Are you sure you want to restore all advanced settings to their recommended defaults?")) {
      // Restore all defaults, but preserve whatever the current toggle state is!
      setComprehensiveSettings({ ...DEFAULT_SETTINGS, enabled: isEnabled });
    }
  };

  const handleChange = (key, value, nestedKey = null) => {
    setComprehensiveSettings((prev) => {
      if (nestedKey !== null) {
        return {
          ...prev,
          [key]: {
            ...(prev[key] || {}),
            [nestedKey]: value,
          },
        };
      }
      return { ...prev, [key]: value };
    });
  };

  // Check if we are using the dynamic list or the flat value
  const isUsingFtList = comprehensiveSettings.use_ft_value_list ?? DEFAULT_SETTINGS.use_ft_value_list;

  const SETTINGS_GROUPS = [
    {
      title: "Solver Constraints",
      items: [
        { key: "secs", label: "Solve Time Limit (secs)", type: "number", step: "1", default: DEFAULT_SETTINGS.secs, desc: "Maximum time in seconds allowed for the solver per iteration." },
        { key: "hit_limit", label: "Max Horizon Hits", type: "number", step: "1", default: DEFAULT_SETTINGS.hit_limit, desc: "Maximum total hits allowed over the entire horizon. Leave blank for infinite." },
        { key: "no_transfer_last_gws", label: "No Transfers Last X GWs", type: "number", step: "1", default: DEFAULT_SETTINGS.no_transfer_last_gws, desc: "Prevent transfers in the final X gameweeks of the horizon." },
        { key: "xmin_lb", label: "Min xMins (Per GW)", type: "number", step: "1", default: DEFAULT_SETTINGS.xmin_lb, desc: "Minimum expected minutes per GW. Multiplied by the horizon length to filter out non-playing players before solving." },
        { key: "keep_top_ev_percent", label: "Keep Top EV (%)", type: "number", step: "1", default: DEFAULT_SETTINGS.keep_top_ev_percent, desc: "Percentage of top EV players to keep for the solve." },
      ]
    },
    {
      title: "Penalties & Weights",
      items: [
        { key: "ft_use_penalty", label: "FT Use Penalty", type: "number", step: "0.01", default: DEFAULT_SETTINGS.ft_use_penalty, desc: "Penalty applied for using a free transfer (encourages rolling)." },
        { key: "itb_loss_per_transfer", label: "ITB Loss per Transfer", type: "number", step: "0.01", default: DEFAULT_SETTINGS.itb_loss_per_transfer, desc: "Artificial cost deducted from ITB per transfer to prefer cheaper identical-EV moves." },
        { key: "vcap_weight", label: "Vice-Captain Weight", type: "number", step: "0.01", default: DEFAULT_SETTINGS.vcap_weight, desc: "Fractional EV added to the Vice Captain in case the main captain does not play." },
        { key: "opposing_play_penalty", label: "Opposing Play Penalty", type: "number", step: "0.01", default: DEFAULT_SETTINGS.opposing_play_penalty, desc: "Penalty applied when attacking players face defensive players in your lineup." },
      ]
    },
    {
      title: "Bench Weights",
      desc: "Fractional EV added to bench players based on their bench order.",
      isNested: true,
      parentKey: "bench_weights",
      items: [
        { nestedKey: "0", label: "Goalkeeper", type: "number", step: "0.01", default: DEFAULT_SETTINGS.bench_weights["0"] },
        { nestedKey: "1", label: "Outfield 1st", type: "number", step: "0.01", default: DEFAULT_SETTINGS.bench_weights["1"] },
        { nestedKey: "2", label: "Outfield 2nd", type: "number", step: "0.01", default: DEFAULT_SETTINGS.bench_weights["2"] },
        { nestedKey: "3", label: "Outfield 3rd", type: "number", step: "0.01", default: DEFAULT_SETTINGS.bench_weights["3"] },
      ]
    },
    {
      title: "Iterations & Simulations",
      items: [
        { key: "randomization_strength", label: "Randomization Strength", type: "number", step: "0.01", default: DEFAULT_SETTINGS.randomization_strength, desc: "Multiplier/Strength for adding noise to EVs during Sensitivity Analysis." },
        { key: "iteration_criteria", label: "Iteration Criteria", type: "select", default: DEFAULT_SETTINGS.iteration_criteria, desc: "Rule to generate alternative solutions in subsequent iterations.",
          options: [
            { value: "this_gw_transfer_in_out", label: "Transfers In & Out (Current GW)" },
            { value: "this_gw_transfer_in", label: "Transfers In (Current GW)" },
            { value: "this_gw_transfer_out", label: "Transfers Out (Current GW)" },
          ]
        },
        { key: "iteration_diff", label: "Iteration Difference", type: "number", step: "1", default: DEFAULT_SETTINGS.iteration_diff, desc: "Minimum number of transfers that must change to find an alternate optimal solution." }
      ]
    }
  ];

  return (
    <div className="fixed inset-0 z-modal flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
      <div className="bg-slate-950 border border-slate-800 w-full max-w-3xl max-h-[90vh] overflow-y-auto rounded-2xl flex flex-col shadow-2xl">

        {/* Header */}
        <div className="sticky top-0 z-sticky bg-slate-950/90 backdrop-blur-md border-b border-slate-800 px-6 py-4 flex items-center justify-between">
          <div>
            <h3 className="text-xl font-black text-slate-100 flex items-center gap-2">
              Advanced Algorithm Settings
            </h3>
            <p className="text-xs text-slate-400 mt-1">Configure comprehensive internal MILP parameters and weights.</p>
          </div>
          <div className="flex items-center gap-3">
            <button onClick={handleResetToDefaults} className="flex items-center gap-1.5 text-xs font-bold text-slate-400 hover:text-red-400 transition-colors bg-slate-900 px-3 py-2 rounded-lg border border-slate-800 hover:border-red-500/30">
              <RotateCcw size={14} /> Defaults
            </button>
            <button onClick={() => setShowAdvancedSettings(false)} className="text-slate-500 hover:text-white transition-colors bg-slate-900 p-2 rounded-lg border border-slate-800">
              <X size={20} />
            </button>
          </div>
        </div>

        {/* Body */}
        <div className="p-6 flex flex-col gap-8">
          
          {/* Master Toggle */}
          <div className={`p-4 rounded-xl border flex items-center justify-between transition-colors shadow-lg ${isEnabled ? 'bg-luigi-500/10 border-luigi-500/30' : 'bg-slate-900 border-slate-700'}`}>
            <div className="flex items-center gap-4">
              <div className={`p-2.5 rounded-lg ${isEnabled ? 'bg-luigi-500/20 text-luigi-400' : 'bg-slate-800 text-slate-500'}`}>
                <Power size={22} />
              </div>
              <div>
                <h4 className={`font-bold text-lg ${isEnabled ? 'text-luigi-400' : 'text-slate-400'}`}>Enable Advanced Overrides</h4>
                <p className="text-xs text-slate-500">When enabled, these parameters will be injected into the solver payload.</p>
              </div>
            </div>
            <button 
              onClick={handleToggle}
              className={`relative inline-flex h-7 w-12 items-center rounded-full transition-colors focus:outline-none ${isEnabled ? 'bg-luigi-500' : 'bg-slate-700'}`}
            >
              <span className={`inline-block h-5 w-5 transform rounded-full bg-white transition-transform ${isEnabled ? 'translate-x-6' : 'translate-x-1'}`} />
            </button>
          </div>

          <div className={`flex flex-col gap-8 transition-opacity duration-300 ${!isEnabled ? 'opacity-30 pointer-events-none grayscale' : 'opacity-100'}`}>
            
            {/* SPECIAL SECTION: Free Transfer Valuation */}
            <div className="bg-slate-900/40 rounded-2xl p-5 border border-slate-800/60">
                <div className="mb-5 border-b border-slate-800 pb-3 flex items-center justify-between">
                  <div>
                    <h4 className="text-sm font-black text-slate-400 uppercase tracking-widest">FT Val</h4>
                    <p className="text-[11px] text-slate-500 mt-1">Intrinsic EV value assigned for holding/rolling free transfers.</p>
                  </div>
                  <div className="flex items-center gap-2">
                     <span className="text-xs font-bold text-slate-400">Dynamic List</span>
                     <button onClick={() => handleChange("use_ft_value_list", !isUsingFtList)} className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none ${isUsingFtList ? 'bg-luigi-500' : 'bg-slate-700'}`}>
                        <span className={`inline-block h-3 w-3 transform rounded-full bg-white transition-transform ${isUsingFtList ? 'translate-x-5' : 'translate-x-1'}`} />
                     </button>
                  </div>
                </div>

                {isUsingFtList ? (
                    <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                        {["2", "3", "4", "5"].map((num) => (
                           <div key={`ft_${num}`} className="bg-slate-900 border border-slate-700 p-4 rounded-xl">
                             <label className="text-xs font-bold text-slate-300 block mb-2">Value of {num}{num==='2'?'nd':num==='3'?'rd':'th'} FT</label>
                             <input type="number" step="0.1" value={comprehensiveSettings.ft_value_list?.[num] ?? DEFAULT_SETTINGS.ft_value_list[num]} onChange={(e) => handleChange("ft_value_list", parseFloat(e.target.value) || 0, num)} className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-100 focus:outline-none focus:border-luigi-500 font-mono" />
                           </div>
                        ))}
                    </div>
                ) : (
                    <div className="bg-slate-900/50 border border-slate-700 p-4 rounded-xl flex items-center justify-center text-center h-[88px]">
                        <p className="text-xs text-slate-400 font-bold">Using standard flat FT Value from normal settings.</p>
                    </div>
                )}
            </div>

            {/* Render Remaining Standard Settings Groups */}
            {SETTINGS_GROUPS.map((group, idx) => (
              <div key={idx} className="bg-slate-900/40 rounded-2xl p-5 border border-slate-800/60">
                <div className="mb-5 border-b border-slate-800 pb-3">
                  <h4 className="text-sm font-black text-slate-400 uppercase tracking-widest">{group.title}</h4>
                  {group.desc && <p className="text-[11px] text-slate-500 mt-1">{group.desc}</p>}
                </div>
                
                <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                  {group.items.map((item) => {
                    let val = group.isNested 
                      ? (comprehensiveSettings[group.parentKey]?.[item.nestedKey] ?? item.default)
                      : (comprehensiveSettings[item.key] ?? item.default);

                    return (
                      <div key={item.key || item.nestedKey} className="bg-slate-900 border border-slate-700 p-4 rounded-xl relative group hover:border-slate-500 transition-colors">
                        <div className="flex justify-between items-center mb-2">
                          <label className="text-xs font-bold text-slate-300">{item.label}</label>
                          <Info size={14} className="text-slate-600 group-hover:text-luigi-400 transition-colors" />
                        </div>
                        
                        {item.type === "select" ? (
                          <select value={val} onChange={(e) => handleChange(item.key, e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-100 focus:outline-none focus:border-luigi-500 cursor-pointer">
                            {item.options.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
                          </select>
                        ) : (
                          <input 
                            type={item.type} 
                            step={item.step} 
                            min={item.min} 
                            // THE FIX 1: If it's a checkbox, use 'checked'. Otherwise use 'value'.
                            checked={item.type === "checkbox" ? Boolean(val) : undefined}
                            value={item.type !== "checkbox" ? (val === "" ? "" : val) : undefined} 
                            placeholder={item.default === "" ? "None" : item.default} 
                            onChange={(e) => { 
                              // THE FIX 2: Safely extract boolean for checkboxes, numbers for everything else
                              let newVal;
                              if (item.type === "checkbox") {
                                newVal = e.target.checked;
                              } else {
                                newVal = e.target.value === "" ? "" : (parseFloat(e.target.value) || 0);
                              }
                              
                              group.isNested ? handleChange(group.parentKey, newVal, item.nestedKey) : handleChange(item.key, newVal); 
                            }} 
                            className={`bg-slate-950 border border-slate-700 rounded-lg text-sm text-slate-100 focus:outline-none focus:border-luigi-500 font-mono ${item.type === 'checkbox' ? 'w-5 h-5 accent-luigi-500 cursor-pointer' : 'w-full px-3 py-2'}`} 
                          />
                        )}

                        <div className="absolute left-0 -bottom-2 translate-y-full opacity-0 group-hover:opacity-100 transition-opacity z-tooltip w-[110%] bg-slate-800 text-slate-300 text-[11px] p-2.5 rounded-lg shadow-xl pointer-events-none border border-slate-700">
                          {item.desc || group.desc}
                        </div>
                      </div>
                    );
                  })}
                </div>
              </div>
            ))}
          </div>

        </div>
      </div>
    </div>
  );
}