/* SINAIS FIFA — Análise H2H modal (idle → loading → analysis) */
const { useState: useS_M, useEffect: useE_M, useRef: useR_M } = React;
const LOADING_STEPS = [
'Conectando ao motor estatístico…',
'Lendo padrões ELO + λ Poisson dos jogadores…',
'Decifrando histórico de confronto direto…',
'Calculando edge e odds justas…',
'Validando análise para entrega…',
];
const SAMPLE_SIZES = [5, 10, 15, 20];
const FormChip = ({ result }) => (
{result}
);
const SignalModal = ({ match, onClose }) => {
const [phase, setPhase] = useS_M('idle'); // idle | loading | analysis | error
const [step, setStep] = useS_M(0);
const [sample, setSample] = useS_M(10);
const [analysis, setAnalysis] = useS_M(null);
const [errMsg, setErrMsg] = useS_M('');
const stepRef = useR_M(null);
useE_M(() => () => { if (stepRef.current) clearInterval(stepRef.current); }, []);
const generate = async () => {
setPhase('loading');
setStep(0);
setErrMsg('');
setAnalysis(null);
// animação dos passos roda enquanto a API responde
stepRef.current = setInterval(() => {
setStep(s => Math.min(s + 1, LOADING_STEPS.length - 1));
}, 380);
try {
const res = await fetch(`/api/analysis/${match.id}?sample=${sample}`);
if (!res.ok) {
const detail = await res.json().catch(() => ({}));
throw new Error(detail.detail || `HTTP ${res.status}`);
}
const data = await res.json();
// garante o tempo mínimo da animação (≈1.5s) pra não piscar
const elapsed = (LOADING_STEPS.length - 1) * 380;
const wait = Math.max(0, 1500 - elapsed);
setTimeout(() => {
clearInterval(stepRef.current);
setStep(LOADING_STEPS.length - 1);
setAnalysis(data);
setPhase('analysis');
}, wait);
} catch (e) {
clearInterval(stepRef.current);
setErrMsg(e.message || 'falha ao gerar análise');
setPhase('error');
}
};
// URL real do jogo (do banco) > fallback do AFFIL
const gameUrl = (analysis && analysis.url) || match.url || `${AFFIL.gameUrlBase}${match.id}`;
// streak: a API retorna side='home'|'away'; resolvemos pro objeto match
const streakObj = analysis && analysis.streak
? (analysis.streak.side === 'home' ? match.home : match.away)
: null;
return (
e.stopPropagation()}>
Análise H2H · {match.home.handle} vs {match.away.handle}
{match.league} · kickoff {match.t} BRT
{phase === 'idle' && (
{match.home.handle.slice(0,2).toUpperCase()}
{match.home.handle}
{match.home.team}
VS
{match.away.handle.slice(0,2).toUpperCase()}
{match.away.handle}
{match.away.team}
Base de análise · últimos N jogos
{SAMPLE_SIZES.map(n => (
))}
EV estimado +{(match.ev || 0).toFixed(1)}% · Conf {match.conf}%
)}
{phase === 'loading' && (
{LOADING_STEPS.map((s, i) => (
{s}
))}
)}
{phase === 'error' && (
⚠️
Falha ao gerar análise
{errMsg}
)}
{phase === 'analysis' && analysis && (
<>
{/* Sample size pill */}
base · últimos {sample} jogos · conf {analysis.confidence}%
{/* Recent form — both players */}
{match.home.handle} · forma recente
{analysis.recentHome.length === 0 && sem histórico}
{analysis.recentHome.map((r, i) => )}
{match.away.handle} · forma recente
{analysis.recentAway.length === 0 && sem histórico}
{analysis.recentAway.map((r, i) => )}
{/* H2H direct + ELO */}
H2H direto · {analysis.h2h.n} confrontos
{analysis.h2h.n === 0 ? (
sem confronto direto registrado
) : (
<>
{analysis.h2h.w}V
{analysis.h2h.d}E
{analysis.h2h.l}D
>
)}
ELO · momentum
{analysis.eloHome}
{match.home.handle}
= 0 ? 'up' : 'down'}`}>
{analysis.momentum >= 0 ? '↑' : '↓'} {Math.abs(analysis.momentum)}
{analysis.eloAway}
{match.away.handle}
{/* Stats grid */}
Gols esperados (λ)
{analysis.avgGoals}
BTTS prob
{analysis.btts}%
Over 2.5 hits
{analysis.overHits}/{analysis.overTotal}
WR casa · fora
{analysis.homeWR}% · {analysis.awayWR}%
Streak ativa
{analysis.streak.n > 0 ? (
<>
{analysis.streak.n}{analysis.streak.type}
{streakObj?.handle}
>
) : —}
{/* Recommended picks (ranked) */}
Picks recomendados
ranqueados por edge — motor estatístico real
{analysis.picks.length === 0 && (
nenhum pick com edge positivo
)}
{analysis.picks.map((p, i) => (
#{i+1}
{p.type}
odd mín. {p.minOdd} · justa {p.fairOdd}
{p.marketOdd && <> · GoldeBet {p.marketOdd}>}
{' '}· prob {p.prob}%
= 6 ? 'hot' : ''}`}>
+{p.edge}%
))}
Ir para o jogo →
>
)}
);
};
window.SignalModal = SignalModal;