import { useState } from "react"; import { getPlayerPrice } from "../utils/fplLogic"; export const useFplSolverApi = (abortControllerRef) => { const [isSolving, setIsSolving] = useState(false); const [isChipSolving, setIsChipSolving] = useState(false); const [isRunningSens, setIsRunningSens] = useState(false); const [pendingSolutions, setPendingSolutions] = useState([]); const [chipSolveSolutions, setChipSolveSolutions] = useState([]); const [sensResults, setSensResults] = useState(null); const [sensViewGw, setSensViewGw] = useState(null); // Reusable helper to format players for the backend const formatMarketPlayers = (globalPlayers, teamData, horizonGWs) => { return globalPlayers.map((p) => { const squadPlayer = teamData.find((sp) => sp.ID === p.ID); const sellPrice = squadPlayer ? squadPlayer.Price : getPlayerPrice(p); const evs = {}; horizonGWs.forEach((gw) => { evs[String(gw)] = Number(p[`${gw}_Pts`]) || 0; }); return { id: p.ID, name: p.Name, pos: p.Pos, team: p.Team, now_cost: getPlayerPrice(p), sell_price: sellPrice, evs, }; }); }; // Extracts the basic frontend settings (bans, locks, chips) into a clean dictionary const buildBaseSettings = (quickSettings, chipsByGw, forceIterations = null) => { return { decay_base: Number(quickSettings?.decay || 0.85), ft_value_base: Number(quickSettings?.ft_value || 1.5), iterations: forceIterations !== null ? forceIterations : Number(quickSettings?.iterations || 1), banned_ids: quickSettings?.banned?.map((p) => p.ID) || [], locked_ids: quickSettings?.locked?.map((p) => p.ID) || [], ban_this_gw: quickSettings?.ban_this_gw?.map((p) => p.ID) || [], lock_this_gw: quickSettings?.lock_this_gw?.map((p) => p.ID) || [], use_wc: Object.entries(chipsByGw || {}).filter(([, c]) => c === "wc").map(([g]) => Number(g)), use_fh: Object.entries(chipsByGw || {}).filter(([, c]) => c === "fh").map(([g]) => Number(g)), use_bb: Object.entries(chipsByGw || {}).filter(([, c]) => c === "bb").map(([g]) => Number(g)), use_tc: Object.entries(chipsByGw || {}).filter(([, c]) => c === "tc").map(([g]) => Number(g)), }; }; const handleSolve = async ({ teamId, solveGWs, horizonGWs, teamData, globalPlayers, itb, availableFts, quickSettings, chipsByGw, comprehensiveSettings, lockedBaselineEv, pastBaselineEv // <-- ADDED HERE }) => { setIsSolving(true); abortControllerRef.current = new AbortController(); try { const payload = { team_id: parseInt(teamId, 10) || 0, horizon_gws: solveGWs, current_squad_ids: teamData.filter((p) => !p.isBlank && typeof p.ID === "number").map((p) => p.ID), market_players: formatMarketPlayers(globalPlayers, teamData, horizonGWs), in_the_bank: itb, free_transfers: availableFts, settings: buildBaseSettings(quickSettings, chipsByGw), comprehensive_settings: comprehensiveSettings, }; const res = await fetch("https://anayshukla-fpl-solver.hf.space/api/solve", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), signal: abortControllerRef.current.signal, }); const data = await res.json(); if (!res.ok) throw new Error(data.detail || data.message || "Solver failed"); if (data.status === "success" && Array.isArray(data.solutions)) { // THE FIX: Inject the specific baselines and calculate the padded visual total const enhancedSolutions = data.solutions.map(sol => ({ ...sol, lockedBaselineEv, paddedTotalEv: (sol.ev || 0) + (pastBaselineEv || 0) })); const sorted = [...enhancedSolutions].sort((a, b) => { const oa = a.objective_score != null ? a.objective_score : a.ev; const ob = b.objective_score != null ? b.objective_score : b.ev; if (ob !== oa) return ob - oa; return (b.ev || 0) - (a.ev || 0); }); setPendingSolutions(sorted); } else { alert("Solver failed: " + (data.message || data.detail || "Unknown")); } } catch (err) { if (err.name === 'AbortError') return; alert(err.message || "Failed to run the solver."); } finally { setIsSolving(false); } }; const handleChipSolve = async ({ teamId, horizonGWs, teamData, globalPlayers, itb, availableFts, quickSettings, comprehensiveSettings, chipSolveOptions, lockedBaselineEv, pastBaselineEv // <-- ADDED HERE }) => { const hasOptions = Object.values(chipSolveOptions).some((v) => v.length > 0); if (!hasOptions) { alert("Select at least one GW for a chip before running the chip solve."); return; } setIsChipSolving(true); setChipSolveSolutions([]); abortControllerRef.current = new AbortController(); try { const payload = { team_id: parseInt(teamId, 10) || 0, horizon_gws: horizonGWs, current_squad_ids: teamData.filter((p) => !p.isBlank && typeof p.ID === "number").map((p) => p.ID), market_players: formatMarketPlayers(globalPlayers, teamData, horizonGWs), in_the_bank: itb, free_transfers: availableFts, settings: buildBaseSettings(quickSettings, {}), comprehensive_settings: comprehensiveSettings, chip_gw_options: { wc: chipSolveOptions.wc, fh: chipSolveOptions.fh, bb: chipSolveOptions.bb, tc: chipSolveOptions.tc, }, }; const res = await fetch("https://anayshukla-fpl-solver.hf.space/api/chip-solve", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), signal: abortControllerRef.current.signal, }); const data = await res.json(); if (!res.ok) { const d = data.detail; const msg = typeof d === "string" ? d : Array.isArray(d) ? d.map((x) => x.msg || x).join(", ") : JSON.stringify(d); throw new Error(msg || "Chip solve failed"); } if (data.status === "success" && Array.isArray(data.solutions)) { // THE FIX: Inject the specific baselines const enhancedSolutions = data.solutions.map(sol => ({ ...sol, lockedBaselineEv, paddedTotalEv: (sol.ev || 0) + (pastBaselineEv || 0) })); setChipSolveSolutions(enhancedSolutions); } else { alert("Chip solve failed: " + (data.message || "Unknown error")); } } catch (err) { if (err.name === 'AbortError') return; alert(err.message || "Failed to run chip solve."); } finally { setIsChipSolving(false); } }; const handleSensAnalysis = async ({ teamId, solveGWs, horizonGWs, teamData, globalPlayers, itb, availableFts, quickSettings, chipsByGw, comprehensiveSettings, numSims, lockedBaselineEv, pastBaselineEv }) => { setIsRunningSens(true); setSensResults(null); abortControllerRef.current = new AbortController(); try { const payload = { team_id: parseInt(teamId, 10) || 0, horizon_gws: solveGWs, current_squad_ids: teamData.filter((p) => !p.isBlank && typeof p.ID === "number").map((p) => p.ID), market_players: formatMarketPlayers(globalPlayers, teamData, horizonGWs), in_the_bank: itb, free_transfers: availableFts, settings: buildBaseSettings(quickSettings, chipsByGw, 1), // Force 1 iteration for sens analysis comprehensive_settings: comprehensiveSettings, num_sims: numSims, analysis_gw: solveGWs[0] || null, }; const res = await fetch("https://anayshukla-fpl-solver.hf.space/api/sensitivity", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), signal: abortControllerRef.current.signal, }); const data = await res.json(); if (!res.ok) { const d = data.detail; const msg = typeof d === "string" ? d : Array.isArray(d) ? d.map((x) => x.msg || x).join(", ") : JSON.stringify(d); throw new Error(msg || "Sensitivity analysis failed"); } if (data.status === "success") { setSensResults(data); setSensViewGw(data.horizon_gws?.[0] ?? null); } else { alert("Sensitivity failed: " + (data.message || "Unknown error")); } } catch (err) { if (err.name === 'AbortError') return; alert(err.message || "Failed to run sensitivity analysis."); } finally { setIsRunningSens(false); } }; const loadSettingsFromCloud = async (teamId) => { try { const res = await fetch(`https://anayshukla-fpl-solver.hf.space/api/settings/${teamId}`); const data = await res.json(); if (data.status === "success") { console.log("📥 LOADED FROM CLOUD:", data); return { quick: data.quick_settings, advanced: data.advanced_settings }; } } catch (err) { console.warn("Failed to load cloud settings:", err); } return null; }; const saveSettingsToCloud = async (teamId, quickSettings, compSettings) => { console.log("📤 SAVING TO CLOUD. Advanced Settings payload:", compSettings); try { await fetch("https://anayshukla-fpl-solver.hf.space/api/settings/save", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ team_id: parseInt(teamId, 10), quick_settings: quickSettings, advanced_settings: compSettings // <-- Safely maps React state to Python expectation }) }); } catch (err) { console.warn("Failed to save settings to cloud:", err); } }; return { isSolving, isChipSolving, isRunningSens, pendingSolutions, setPendingSolutions, chipSolveSolutions, setChipSolveSolutions, sensResults, setSensResults, sensViewGw, setSensViewGw, handleSolve, handleChipSolve, handleSensAnalysis, loadSettingsFromCloud, saveSettingsToCloud, }; };