// fp-agents.jsx v3 — 9 Agents incl. WEATHER, Parallel Multi-Match Analysis
const { useState, useEffect, useRef, useCallback } = React;

// ─── Animated Glow Border ────────────────────────────────────
function GlowBorder({ color, active, children, style = {} }) {
  return (
    <div style={{
      position:'relative', borderRadius:16,
      background:active?`${color}0c`:'rgba(255,255,255,0.025)',
      border:`1px solid ${active?color+'44':'rgba(255,255,255,0.07)'}`,
      boxShadow:active?`0 0 20px ${color}20, inset 0 1px 0 ${color}15`:'inset 0 1px 0 rgba(255,255,255,0.04)',
      transition:'all 0.4s ease', ...style
    }}>
      {active && <div style={{
        position:'absolute',inset:-1,borderRadius:17,pointerEvents:'none',
        background:`conic-gradient(from 0deg, transparent 0%, ${color}66 25%, transparent 50%, ${color}44 75%, transparent 100%)`,
        animation:'spin 3s linear infinite',opacity:0.5,
        WebkitMask:'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
        WebkitMaskComposite:'xor',maskComposite:'exclude',padding:1,
      }} />}
      {children}
    </div>
  );
}

// ─── Scan Line ────────────────────────────────────────────────
function ScanLine({ color, active }) {
  if (!active) return null;
  return (
    <div style={{
      position:'absolute',left:0,right:0,height:2,borderRadius:99,
      background:`linear-gradient(90deg, transparent, ${color}, transparent)`,
      animation:'scanLine 1.8s ease-in-out infinite',
      boxShadow:`0 0 8px ${color}`,
    }} />
  );
}

// ─── Agent Card ───────────────────────────────────────────────
function AgentCard({ agent, status, result, index }) {
  const thinking = status === 'thinking';
  const done = status === 'done';
  const c = agent.color;
  return (
    <GlowBorder color={c} active={done||thinking} style={{
      padding:'10px 12px',overflow:'hidden',
      animation:`fadeUp 0.4s ${index*0.06}s both ease`,
      backdropFilter:'blur(12px)',
    }}>
      {thinking && <ScanLine color={c} active />}
      <div style={{display:'flex',alignItems:'center',gap:10}}>
        <div style={{
          width:38,height:38,borderRadius:'50%',flexShrink:0,
          background:`radial-gradient(circle at 35% 30%, ${c}30, #0b0f1599)`,
          border:`1.5px solid ${done?c+'88':thinking?c+'55':'rgba(255,255,255,0.1)'}`,
          display:'flex',alignItems:'center',justifyContent:'center',fontSize:17,
          boxShadow:done?agent.glow:'none',transition:'all 0.4s ease',position:'relative',
        }}>
          {agent.icon}
          {thinking && <div style={{position:'absolute',inset:-3,borderRadius:'50%',border:`2px solid ${c}`,borderTopColor:'transparent',animation:'spin 0.9s linear infinite'}} />}
          {done && <div style={{position:'absolute',bottom:-2,right:-2,width:13,height:13,borderRadius:'50%',background:'#22ff88',border:'1.5px solid #0b0f15',fontSize:7,display:'flex',alignItems:'center',justifyContent:'center',color:'#0b0f15',fontWeight:800}}>✓</div>}
        </div>
        <div style={{flex:1,minWidth:0}}>
          <div style={{display:'flex',alignItems:'center',gap:6,marginBottom:2}}>
            <span style={{fontSize:10,fontFamily:'JetBrains Mono',fontWeight:700,color:c,letterSpacing:'0.1em'}}>{agent.name}</span>
            <span style={{fontSize:9,color:'rgba(255,255,255,0.3)',fontFamily:'Inter'}}>{agent.specialty}</span>
          </div>
          {thinking && (
            <div style={{display:'flex',gap:3,alignItems:'center'}}>
              {[0,1,2].map(i=><div key={i} style={{width:4,height:4,borderRadius:'50%',background:c,animation:`dotBounce 1s ${i*0.15}s ease-in-out infinite`}} />)}
              <span style={{fontSize:9,color:'rgba(255,255,255,0.35)',marginLeft:4,fontFamily:'Inter'}}>{t('analysing')}</span>
            </div>
          )}
          {done && result && (
            <div style={{display:'flex',alignItems:'center',gap:5,flexWrap:'wrap'}}>
              <span style={{fontSize:10,fontFamily:'Inter',color:'rgba(255,255,255,0.7)',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',maxWidth:140}}>{result.bet_label}</span>
              <span style={{fontSize:9,color:getRiskColor(result.risk),background:getRiskColor(result.risk)+'18',padding:'1px 5px',borderRadius:4,fontFamily:'JetBrains Mono',flexShrink:0}}>{result.risk}</span>
              {result.weather_impact && result.weather_impact!=='NEUTRAL' && (
                <span style={{fontSize:8,color:'#a8edff',background:'rgba(168,237,255,0.1)',padding:'1px 5px',borderRadius:4,fontFamily:'JetBrains Mono',flexShrink:0}}>🌦️{result.weather_impact}</span>
              )}
            </div>
          )}
          {status==='pending' && <span style={{fontSize:9,color:'rgba(255,255,255,0.2)',fontFamily:'Inter'}}>{t('pendingWave')} {agent.wave} {t('waitingSuffix')}</span>}
          {status==='error' && <span style={{fontSize:9,color:'#ff5470',fontFamily:'Inter'}}>{t('error_conn')} Fallback aktiv</span>}
        </div>
        {done && result && (
          <div style={{flexShrink:0,position:'relative',width:34,height:34}}>
            <svg width="34" height="34" viewBox="0 0 34 34">
              <circle cx="17" cy="17" r="14" fill="none" stroke="rgba(255,255,255,0.06)" strokeWidth="2.5" />
              <circle cx="17" cy="17" r="14" fill="none" stroke={c} strokeWidth="2.5"
                strokeDasharray={`${result.confidence*0.88} 88`}
                strokeLinecap="round" transform="rotate(-90 17 17)"
                style={{transition:'stroke-dasharray 0.8s ease'}} />
            </svg>
            <div style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',fontSize:8,fontFamily:'JetBrains Mono',fontWeight:700,color:c}}>{result.confidence}</div>
          </div>
        )}
      </div>
      {done && result?.reasoning && (
        <div style={{marginTop:8,paddingTop:8,borderTop:`1px solid ${c}18`}}>
          <p style={{fontSize:10,color:'rgba(255,255,255,0.55)',lineHeight:1.55,margin:0,fontFamily:'Inter'}}>{result.reasoning}</p>
          {result.key_factor && <div style={{marginTop:4,fontSize:9,color:c,fontFamily:'Inter',fontStyle:'italic'}}>→ {result.key_factor}</div>}
          {/* Weather specific */}
          {agent.id==='weather' && result.weather_impact && (
            <div style={{marginTop:6,display:'flex',alignItems:'center',gap:6,padding:'5px 8px',background:'rgba(168,237,255,0.06)',borderRadius:8,border:'1px solid rgba(168,237,255,0.15)'}}>
              <span style={{fontSize:11}}>🌦️</span>
              <span style={{fontSize:9,color:'#a8edff',fontFamily:'JetBrains Mono'}}>{result.weather_impact}</span>
            </div>
          )}
        </div>
      )}
    </GlowBorder>
  );
}

// ─── Master Verdict ───────────────────────────────────────────
function MasterVerdict({ result, agent }) {
  const [visible, setVisible] = useState(false);
  useEffect(()=>{setTimeout(()=>setVisible(true),100);},[]);
  if (!result) return null;
  const c = agent.color;
  const consColor = getConsensusColor(result.consensus);
  return (
    <div style={{
      opacity:visible?1:0,transform:visible?'translateY(0) scale(1)':'translateY(16px) scale(0.97)',
      transition:'all 0.5s cubic-bezier(0.22,1,0.36,1)',
      background:`linear-gradient(135deg, ${c}12 0%, rgba(255,255,255,0.02) 100%)`,
      border:`1px solid ${c}44`,borderRadius:18,padding:'16px',
      boxShadow:agent.glow+', inset 0 1px 0 rgba(255,255,255,0.06)',
      backdropFilter:'blur(20px)',
    }}>
      <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:12}}>
        <span style={{fontSize:20}}>{agent.icon}</span>
        <div style={{flex:1}}>
          <div style={{fontSize:9,color:c,fontFamily:'JetBrains Mono',letterSpacing:'0.14em',marginBottom:2}}>{t('masterDecision')}</div>
          <div style={{fontSize:16,fontWeight:700,color:'rgba(255,255,255,0.95)',fontFamily:'Space Grotesk'}}>{result.bet_label||result.final_pick}</div>
        </div>
        <div style={{textAlign:'right',flexShrink:0}}>
          <div style={{fontSize:22,fontWeight:700,fontFamily:'JetBrains Mono',color:'#ffb648',textShadow:'0 0 12px #ffb648cc, 0 0 28px #ffb64866'}}>{formatOdds(result.odds)}</div>
          <div style={{fontSize:9,color:'rgba(255,255,255,0.35)',fontFamily:'JetBrains Mono'}}>{t('odds')}</div>
        </div>
      </div>
      <div style={{display:'flex',gap:6,marginBottom:10,flexWrap:'wrap'}}>
        <span style={{fontSize:10,background:getRiskColor(result.risk)+'22',color:getRiskColor(result.risk),padding:'3px 10px',borderRadius:99,fontFamily:'JetBrains Mono',fontWeight:700,textShadow:`0 0 8px ${getRiskColor(result.risk)}99`,boxShadow:`0 0 12px ${getRiskColor(result.risk)}22`}}>{getRiskIcon(result.risk)} {result.risk}</span>
        <span style={{fontSize:10,background:consColor+'18',color:consColor,padding:'3px 10px',borderRadius:99,fontFamily:'JetBrains Mono',textShadow:`0 0 7px ${consColor}99`}}>{result.consensus_count||'?'}/9 {t('of')}</span>
        <span style={{fontSize:10,background:`${c}18`,color:c,padding:'3px 10px',borderRadius:99,fontFamily:'JetBrains Mono',textShadow:`0 0 8px ${c}99`,boxShadow:`0 0 12px ${c}22`}}>{result.confidence}% {t('confidence')}</span>
      </div>
      <p style={{fontSize:12,color:'rgba(255,255,255,0.72)',lineHeight:1.65,margin:'0 0 8px',fontFamily:'Inter'}}>{result.reasoning}</p>
      {result.key_factor && <div style={{display:'flex',gap:6,alignItems:'flex-start',marginBottom:8}}><span style={{fontSize:10,color:'rgba(255,255,255,0.3)',fontFamily:'Inter'}}>Entscheidend:</span><span style={{fontSize:10,color:c,fontFamily:'Inter',fontStyle:'italic'}}>{result.key_factor}</span></div>}
      {(() => {
        const alt = (result.alt_pick || '').toLowerCase();
        const isEmpty = !alt || alt.includes('keine') || alt.includes('none') || alt.includes('n/a') || alt.includes('-');
        if (isEmpty) return null;
        return <div style={{padding:'8px 10px',background:'rgba(255,255,255,0.04)',borderRadius:10,border:'1px solid rgba(255,255,255,0.08)'}}><span style={{fontSize:9,color:'rgba(255,255,255,0.35)',fontFamily:'JetBrains Mono',letterSpacing:'0.08em'}}>ALT: </span><span style={{fontSize:10,color:'rgba(255,255,255,0.55)',fontFamily:'Inter'}}>{result.alt_pick}</span></div>;
      })()}
      {(() => {
        const d = (result.dissent || '').toLowerCase();
        // Hide "no dissent" messages: hide if it says "kein dissens", "einheitlich", "all agree", "no dissent", etc.
        const noDissent = !d || d.startsWith('kein') || d.includes('einheitlich') || d.includes('all agree') || d.includes('no dissent') || d.includes('einig') || d.includes('konsens')
          || (typeof result.consensus_count === 'number' && result.consensus_count >= 8);
        if (noDissent) {
          // Positive consensus indicator instead
          if (typeof result.consensus_count === 'number' && result.consensus_count >= 7) {
            return <div style={{marginTop:8,padding:'7px 10px',background:'rgba(34,255,136,0.06)',borderRadius:10,border:'1px solid rgba(34,255,136,0.18)'}}><span style={{fontSize:10,color:'#22ff88',fontFamily:'Inter'}}>✓ {t('strongConsensus').replace('{n}', result.consensus_count)}</span></div>;
          }
          return null;
        }
        return <div style={{marginTop:8,padding:'7px 10px',background:'rgba(255,84,112,0.08)',borderRadius:10,border:'1px solid rgba(255,84,112,0.2)'}}><span style={{fontSize:10,color:'#ff5470',fontFamily:'Inter'}}>⚠ {result.dissent}</span></div>;
      })()}
      {result.quant && <QuantBreakdown q={result.quant}/>}
      {result.ensemble && <EnsembleBreakdown e={result.ensemble} accent={c}/>}
    </div>
  );
}

function EnsembleBreakdown({ e, accent }) {
  if (!e) return null;
  const consColor = e.agreeCount === e.total ? '#22ff88' : e.agreeCount >= 2 ? '#5ee7ff' : e.agreeCount === 1 ? '#ffb648' : '#ff5470';
  const consLabel = e.agreeCount === e.total ? (currentLang==='tr'?'TAM KONSENSUS':currentLang==='en'?'FULL CONSENSUS':'VOLLER KONSENS')
                  : e.agreeCount >= 2 ? (currentLang==='tr'?'ÇOĞUNLUK':currentLang==='en'?'MAJORITY':'MEHRHEIT')
                  : (currentLang==='tr'?'BÖLÜNMÜŞ':currentLang==='en'?'SPLIT':'GETEILT');
  const voices = [
    e.master && { name:'DeepSeek V4', icon:'🧠', color:'#a855f7', ...e.master },
    e.groq   && { name:'Groq Llama-3.3', icon:'⚡', color:'#ffb648', ...e.groq },
    e.gemini && { name:'OpenAI GPT-OSS 120B', icon:'✨', color:'#5ee7ff', ...e.gemini },
  ].filter(Boolean);
  return (
    <div style={{marginTop:10,padding:'10px 12px',background:`${consColor}06`,border:`1px solid ${consColor}33`,borderRadius:12}}>
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:8}}>
        <span style={{fontSize:8,color:consColor,fontFamily:'JetBrains Mono',letterSpacing:'.14em'}}>🎼 ENSEMBLE · 3 STIMMEN · {consLabel}</span>
        <span style={{fontSize:11,fontWeight:800,fontFamily:'JetBrains Mono',color:consColor}}>{e.agreeCount}/{e.total}</span>
      </div>
      {voices.map((v,i) => (
        <div key={i} style={{display:'flex',alignItems:'flex-start',gap:8,padding:'6px 0',borderTop:i>0?'1px solid rgba(255,255,255,0.05)':'none'}}>
          <span style={{fontSize:14,flexShrink:0}}>{v.icon}</span>
          <div style={{flex:1,minWidth:0}}>
            <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',gap:6}}>
              <span style={{fontSize:9,fontWeight:700,color:v.color,fontFamily:'JetBrains Mono',letterSpacing:'.06em'}}>{v.name}</span>
              <span style={{fontSize:8,padding:'1px 6px',borderRadius:99,background:v.agrees?'rgba(34,255,136,0.12)':'rgba(255,182,72,0.12)',color:v.agrees?'#22ff88':'#ffb648',fontFamily:'JetBrains Mono'}}>{v.agrees?'✓':'≠'}{v.confidence!=null?` ${v.confidence}%`:''}</span>
            </div>
            <div style={{fontSize:10,color:'rgba(255,255,255,0.78)',fontFamily:'Space Grotesk',fontWeight:600,marginTop:1}}>{v.pick}</div>
            {v.reasoning && !v.agrees && <div style={{fontSize:9,color:'rgba(255,255,255,0.45)',fontFamily:'Inter',marginTop:2,lineHeight:1.4}}>{v.reasoning}</div>}
          </div>
        </div>
      ))}
    </div>
  );
}

function QuantBreakdown({ q }) {
  if (!q) return null;
  const pct = (n) => `${(n*100).toFixed(0)}%`;
  const edgeColor = q.edge > 0.05 ? '#22ff88' : q.edge > 0 ? '#ffb648' : '#ff5470';
  return (
    <div style={{marginTop:10,padding:'10px 12px',background:'rgba(94,231,255,0.04)',border:'1px solid rgba(94,231,255,0.18)',borderRadius:12}}>
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:8}}>
        <span style={{fontSize:8,color:'#5ee7ff',fontFamily:'JetBrains Mono',letterSpacing:'.12em'}}>⚙ {t('quantBlend')} · {q.method}</span>
        <span style={{fontSize:10,color:edgeColor,fontFamily:'JetBrains Mono',fontWeight:700}}>EDGE {q.edge>0?'+':''}{(q.edge*100).toFixed(1)}%</span>
      </div>
      <table style={{width:'100%',borderCollapse:'collapse',fontFamily:'JetBrains Mono',fontSize:9}}>
        <thead>
          <tr style={{color:'rgba(255,255,255,0.4)'}}>
            <td></td>
            <td style={{textAlign:'center',padding:'2px 0'}}>1</td>
            <td style={{textAlign:'center',padding:'2px 0'}}>X</td>
            <td style={{textAlign:'center',padding:'2px 0'}}>2</td>
          </tr>
        </thead>
        <tbody>
          <tr><td style={{color:'rgba(255,255,255,0.5)'}}>Poisson</td><td style={{textAlign:'center',color:'rgba(255,255,255,0.85)'}}>{pct(q.poisson1X2.home)}</td><td style={{textAlign:'center',color:'rgba(255,255,255,0.85)'}}>{pct(q.poisson1X2.draw)}</td><td style={{textAlign:'center',color:'rgba(255,255,255,0.85)'}}>{pct(q.poisson1X2.away)}</td></tr>
          {q.elo1X2 && <tr><td style={{color:'rgba(255,255,255,0.5)'}}>Elo {q.elo1X2.ratings.home}/{q.elo1X2.ratings.away}</td><td style={{textAlign:'center',color:'rgba(255,255,255,0.85)'}}>{pct(q.elo1X2.home)}</td><td style={{textAlign:'center',color:'rgba(255,255,255,0.85)'}}>{pct(q.elo1X2.draw)}</td><td style={{textAlign:'center',color:'rgba(255,255,255,0.85)'}}>{pct(q.elo1X2.away)}</td></tr>}
          <tr><td style={{color:'rgba(255,255,255,0.5)'}}>LLM (9)</td><td style={{textAlign:'center',color:'rgba(255,255,255,0.85)'}}>{pct(q.llm1X2.home)}</td><td style={{textAlign:'center',color:'rgba(255,255,255,0.85)'}}>{pct(q.llm1X2.draw)}</td><td style={{textAlign:'center',color:'rgba(255,255,255,0.85)'}}>{pct(q.llm1X2.away)}</td></tr>
          <tr style={{borderTop:'1px solid rgba(255,255,255,0.1)'}}><td style={{color:'#5ee7ff',fontWeight:700,paddingTop:3}}>BLEND</td><td style={{textAlign:'center',color:'#fff',fontWeight:700,paddingTop:3}}>{pct(q.blended1X2.home)}</td><td style={{textAlign:'center',color:'#fff',fontWeight:700,paddingTop:3}}>{pct(q.blended1X2.draw)}</td><td style={{textAlign:'center',color:'#fff',fontWeight:700,paddingTop:3}}>{pct(q.blended1X2.away)}</td></tr>
        </tbody>
      </table>
    </div>
  );
}

// ─── Wave Label ───────────────────────────────────────────────
function WaveLabel({ wave, label, active, done }) {
  const c = active?'#22ff88':done?'rgba(255,255,255,0.4)':'rgba(255,255,255,0.15)';
  return (
    <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:8,marginTop:12}}>
      <div style={{width:18,height:18,borderRadius:'50%',background:active?'rgba(34,255,136,0.2)':'rgba(255,255,255,0.05)',border:`1px solid ${c}`,display:'flex',alignItems:'center',justifyContent:'center',fontSize:8,fontFamily:'JetBrains Mono',fontWeight:700,color:c}}>{wave}</div>
      <span style={{fontSize:9,fontFamily:'JetBrains Mono',letterSpacing:'0.12em',color:c}}>{label}</span>
      {active && <div style={{flex:1,height:1,background:`linear-gradient(90deg, rgba(34,255,136,0.4), transparent)`}} />}
    </div>
  );
}

// ─── Match Selector (Parallel Mode) ──────────────────────────
function MatchSelector({ matches, selected, onToggle, onSelectAll, onClear }) {
  return (
    <div style={{padding:'12px 16px',borderBottom:'1px solid rgba(255,255,255,0.05)'}}>
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:8}}>
        <span style={{fontSize:9,color:'rgba(255,255,255,0.4)',fontFamily:'JetBrains Mono',letterSpacing:'.12em'}}>SPIELE AUSWÄHLEN ({selected.length}/{matches.length})</span>
        <div style={{display:'flex',gap:6}}>
          <button onClick={onSelectAll} style={{fontSize:9,padding:'3px 8px',borderRadius:6,background:'rgba(34,255,136,0.1)',border:'1px solid rgba(34,255,136,0.25)',color:'#22ff88',cursor:'pointer',fontFamily:'JetBrains Mono'}}>Alle</button>
          <button onClick={onClear} style={{fontSize:9,padding:'3px 8px',borderRadius:6,background:'rgba(255,84,112,0.1)',border:'1px solid rgba(255,84,112,0.2)',color:'#ff5470',cursor:'pointer',fontFamily:'JetBrains Mono'}}>Keine</button>
        </div>
      </div>
      <div style={{display:'flex',flexDirection:'column',gap:4,maxHeight:180,overflowY:'auto'}}>
        {matches.map(m=>{
          const sel = selected.includes(m.id);
          return (
            <div key={m.id} onClick={()=>onToggle(m.id)} style={{
              display:'flex',alignItems:'center',gap:8,padding:'7px 10px',
              borderRadius:10,cursor:'pointer',transition:'all .2s',
              background:sel?'rgba(36,218,255,0.08)':'rgba(255,255,255,0.025)',
              border:`1px solid ${sel?'rgba(36,218,255,0.3)':'rgba(255,255,255,0.06)'}`,
            }}>
              <div style={{width:14,height:14,borderRadius:4,border:`1.5px solid ${sel?'#24daff':'rgba(255,255,255,0.2)'}`,background:sel?'rgba(36,218,255,0.3)':'transparent',display:'flex',alignItems:'center',justifyContent:'center',fontSize:9,color:'#24daff',flexShrink:0}}>{sel?'✓':''}</div>
              <span style={{fontSize:9,color:'rgba(255,255,255,0.25)',fontFamily:'JetBrains Mono',width:28,flexShrink:0}}>{m.leagueFlag}</span>
              <span style={{fontSize:11,fontFamily:'Space Grotesk',color:'rgba(255,255,255,0.8)',flex:1,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{m.home} vs {m.away}</span>
              <span style={{fontSize:9,fontFamily:'JetBrains Mono',color:'rgba(255,255,255,0.3)',flexShrink:0}}>{m.time}</span>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ─── Parallel Results Card ────────────────────────────────────
function ParallelResultCard({ match, result, onAddToTicket }) {
  const [exp, setExp] = useState(false);
  if (!result) return (
    <div style={{padding:'10px 12px',background:'rgba(255,255,255,0.02)',borderRadius:12,border:'1px solid rgba(255,255,255,0.06)',marginBottom:6,display:'flex',alignItems:'center',gap:8}}>
      <div style={{width:16,height:16,borderRadius:'50%',border:'2px solid #22ff88',borderTopColor:'transparent',animation:'spin 0.8s linear infinite',flexShrink:0}} />
      <span style={{fontSize:11,color:'rgba(255,255,255,0.4)',fontFamily:'Space Grotesk'}}>{match.home} vs {match.away}</span>
    </div>
  );
  const c = getRiskColor(result.risk);
  return (
    <div style={{marginBottom:8,borderRadius:14,overflow:'hidden',background:`${c}08`,border:`1px solid ${c}22`,boxShadow:`0 0 16px ${c}08`}}>
      <div onClick={()=>setExp(!exp)} style={{padding:'10px 12px',cursor:'pointer',display:'flex',alignItems:'center',gap:8}}>
        <div style={{flex:1,minWidth:0}}>
          <div style={{fontSize:9,color:'rgba(255,255,255,0.35)',fontFamily:'JetBrains Mono',marginBottom:2}}>{match.leagueFlag} {match.leagueShort} · {match.time}</div>
          <div style={{fontSize:12,fontWeight:700,fontFamily:'Space Grotesk',color:'rgba(255,255,255,0.9)',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{match.home} vs {match.away}</div>
          <div style={{fontSize:11,color:c,fontFamily:'Inter',marginTop:1}}>{result.bet_label||result.final_pick}</div>
        </div>
        <div style={{textAlign:'right',flexShrink:0}}>
          <div style={{fontSize:18,fontWeight:700,fontFamily:'JetBrains Mono',color:'#ffb648'}}>{formatOdds(result.odds)}</div>
          <div style={{fontSize:9,color:c,fontFamily:'JetBrains Mono',background:c+'18',padding:'1px 6px',borderRadius:4}}>{result.confidence}%</div>
        </div>
        <span style={{fontSize:9,color:'rgba(255,255,255,0.2)',transform:exp?'rotate(180deg)':'none',transition:'transform .3s',flexShrink:0}}>▼</span>
      </div>
      {exp && (
        <div style={{padding:'0 12px 10px',borderTop:`1px solid ${c}15`}}>
          <p style={{fontSize:10,color:'rgba(255,255,255,0.55)',lineHeight:1.5,margin:'8px 0',fontFamily:'Inter'}}>{result.reasoning}</p>
          <button onClick={()=>onAddToTicket(match,result)} style={{width:'100%',padding:'8px',borderRadius:10,background:`${c}18`,border:`1px solid ${c}33`,color:c,cursor:'pointer',fontFamily:'Space Grotesk',fontWeight:700,fontSize:11}}>+ {t('addTicket')}</button>
        </div>
      )}
    </div>
  );
}

// ─── Agent Pipeline (Single + Parallel) ─────────────────────
function AgentPipeline({ match, matches, onResultReady, onAddToTicket }) {
  const [mode, setMode] = useState('single'); // 'single' | 'parallel'
  const [selectedIds, setSelectedIds] = useState([]);
  const [parallelResults, setParallelResults] = useState({});
  const [parallelRunning, setParallelRunning] = useState(false);
  const [parallelProgress, setParallelProgress] = useState(0);

  // Single mode state
  const wave1Agents = AGENTS.filter(a=>a.wave===1);
  const wave2Agents = AGENTS.filter(a=>a.wave===2);
  const riskAgent   = AGENTS.find(a=>a.wave===3);
  const masterAgent = AGENTS.find(a=>a.wave===4);

  const [statuses, setStatuses] = useState(()=>Object.fromEntries(AGENTS.map(a=>[a.id,'pending'])));
  const [results,  setResults]  = useState({});
  const [running,  setRunning]  = useState(false);
  const [waveActive, setWaveActive] = useState(0);
  const [enrichTick, setEnrichTick] = useState(0);
  const resultsRef = useRef({});
  const scrollRef  = useRef(null);

  // Enrich match with FATIGUE + LINEUP when selected
  useEffect(() => {
    if (!match || !window.enrichMatch) return;
    window.enrichMatch(match).then(() => setEnrichTick(t => t + 1));
  }, [match?.id]);

  const setStatus = (id,s) => setStatuses(p=>({...p,[id]:s}));
  const setResult = (id,r) => { resultsRef.current[id]=r; setResults(p=>({...p,[id]:r})); };
  const parseJSON = t => { try{const m=t.match(/\{[\s\S]*\}/);return m?JSON.parse(m[0]):null;}catch{return null;} };

  const runAgent = async (agent,prompt) => {
    setStatus(agent.id,'thinking');
    try {
      const text = await callAI(prompt);
      const parsed = parseJSON(text);
      const r = parsed||{recommendation:'HOME_WIN',bet_label:'Analyse fehlgeschlagen',confidence:60,reasoning:'KI-Verbindung unterbrochen.',key_factor:'-',risk:'GOOD'};
      setResult(agent.id,r); setStatus(agent.id,'done'); return r;
    } catch(e) {
      const r={recommendation:'HOME_WIN',bet_label:'Fehler',confidence:55,reasoning:'Verbindungsfehler.',key_factor:'-',risk:'GOOD'};
      setResult(agent.id,r); setStatus(agent.id,'error'); return r;
    }
  };

  const startAnalysis = useCallback(async () => {
    if (running||!match) return;
    setRunning(true); setWaveActive(0);
    setResults({}); resultsRef.current={};
    setStatuses(Object.fromEntries(AGENTS.map(a=>[a.id,'pending'])));
    const weather = match.weather;

    setWaveActive(1);
    const [s,st,inj,wthr] = await Promise.all([
      runAgent(AGENTS[0],AGENTS[0].prompt(match)),
      runAgent(AGENTS[1],AGENTS[1].prompt(match)),
      runAgent(AGENTS[2],AGENTS[2].prompt(match)),
      runAgent(AGENTS[3],AGENTS[3].prompt(match,null,weather)),
    ]);

    const w1Summary=`Scout:${s?.bet_label}(${s?.confidence}%) Stats:${st?.bet_label}(${st?.confidence}%) Injury:${inj?.recommendation}(${inj?.rotation_risk||'MED'}) Weather:${wthr?.weather_impact||'NEUTRAL'}`;

    setWaveActive(2);
    const [tac,men,val,line] = await Promise.all([
      runAgent(AGENTS[4],AGENTS[4].prompt(match,w1Summary)),
      runAgent(AGENTS[5],AGENTS[5].prompt(match,w1Summary)),
      runAgent(AGENTS[6],AGENTS[6].prompt(match,w1Summary)),
      runAgent(AGENTS[7],AGENTS[7].prompt(match,w1Summary)),
    ]);

    setWaveActive(3);
    const allRecs=[s,st,inj,wthr,tac,men,val,line].map((r,i)=>r?{agent:AGENTS[i]?.name,rec:r.bet_label,conf:r.confidence,risk:r.risk}:null);
    const risk = await runAgent(AGENTS[8],AGENTS[8].prompt(match,allRecs));

    setWaveActive(4);
    // Wave 4: 3-Voice-Ensemble — DeepSeek MASTER + Groq Llama + Google Gemini parallel
    const allLLMResults = {scout:s,stats:st,injury:inj,weather:wthr,tactical:tac,mental:men,value:val,line,risk};
    const ensemblePrompt = (engineName) => `Du bist ${engineName}, eine unabhängige KI als Teil eines 3-Stimmen-Ensembles. 9 Spezialisten haben analysiert:
${JSON.stringify(allLLMResults,null,1)}
Spiel: ${match.home} vs ${match.away} (${match.league||match.leagueName})
Quoten: H${match.odds.home} X${match.odds.draw} A${match.odds.away} | BTTS:${match.btts?.yes} | O2.5:${match.overUnder?.o25}
Gib eine UNABHÄNGIGE Einschätzung — nicht von den Spezialisten dominieren lassen.
Antworte NUR als JSON: {"final_pick":"HOME_WIN|DRAW|AWAY_WIN|OVER_25|UNDER_25|BTTS_YES","bet_label":"...","odds":1.85,"confidence":78,"reasoning":"2 Sätze","key_factor":"...","risk":"SAFE|GOOD|RISK"}`;

    const [masterRes, groqText, gptOssText] = await Promise.all([
      runAgent(AGENTS[9], AGENTS[9].prompt(match, allLLMResults)),
      callGroq(ensemblePrompt('GROQ Llama-3.3'), 800),
      callOpenAIOss(ensemblePrompt('OpenAI GPT-OSS 120B'), 800),
    ]);
    const groqRes = groqText ? parseJSON(groqText) : null;
    const geminiRes = gptOssText ? parseJSON(gptOssText) : null;

    // Quant-Override: blende Poisson + Elo + LLM-Konsens, ersetze final_pick
    const blended = computeQuantVerdict(match, masterRes, { ...allLLMResults, groq: groqRes, gemini: geminiRes });
    let finalRes = blended ? { ...masterRes, ...blended } : masterRes;

    // 3-Voice-Vergleich: zähle Stimmen die mit Final-Pick übereinstimmen
    if (finalRes) {
      const norm = s => (s||'').toString().toUpperCase().replace(/[^A-Z]/g,'');
      const finalKey = norm(finalRes.final_pick || finalRes.bet_label).slice(0,4);
      const matches3 = (r) => {
        if (!r) return false;
        const k1 = norm(r.final_pick).slice(0,4);
        const k2 = norm(r.bet_label).slice(0,4);
        return k1 === finalKey || k2 === finalKey;
      };
      const masterAgrees = matches3(masterRes);
      const groqAgrees = matches3(groqRes);
      const geminiAgrees = matches3(geminiRes);
      const agreeCount = (masterAgrees?1:0) + (groqAgrees?1:0) + (geminiAgrees?1:0);
      const validVoices = (masterRes?1:0) + (groqRes?1:0) + (geminiRes?1:0);

      finalRes.ensemble = {
        agreeCount, total: validVoices,
        master: masterRes ? { agrees: masterAgrees, pick: masterRes.bet_label || masterRes.final_pick, confidence: masterRes.confidence } : null,
        groq: groqRes ? { agrees: groqAgrees, pick: groqRes.bet_label || groqRes.final_pick, confidence: groqRes.confidence, reasoning: groqRes.reasoning } : null,
        gemini: geminiRes ? { agrees: geminiAgrees, pick: geminiRes.bet_label || geminiRes.final_pick, confidence: geminiRes.confidence, reasoning: geminiRes.reasoning } : null,
      };
      // Confidence-Adjustment je nach Konsens
      if (validVoices >= 2 && finalRes.confidence) {
        if (agreeCount === validVoices) finalRes.confidence = Math.min(99, finalRes.confidence + 8); // Volle Einigkeit
        else if (agreeCount >= 2) finalRes.confidence = Math.min(99, finalRes.confidence + 3); // Mehrheit
        else if (agreeCount === 0) finalRes.confidence = Math.max(30, finalRes.confidence - 8); // Niemand stimmt zu (Quant alleine)
      }
    }

    setResult(AGENTS[9].id, finalRes);
    setRunning(false); setWaveActive(5);
    if (onResultReady&&finalRes) onResultReady(match.id,finalRes);
    if (scrollRef.current) scrollRef.current.scrollTo({top:scrollRef.current.scrollHeight,behavior:'smooth'});
  },[match,running]);

  // ── Parallel Analysis ─────────────────────────────────────
  const runParallel = async () => {
    if (parallelRunning||selectedIds.length===0) return;
    setParallelRunning(true); setParallelProgress(0); setParallelResults({});
    const toAnalyse = (matches||[]).filter(m=>selectedIds.includes(m.id));

    const analyseOne = async (m) => {
      const weather = m.weather;
      try {
        const prompt = AGENTS[9].prompt(m, {
          scout:{bet_label:`${m.home} Sieg`,confidence:72,risk:'GOOD'},
          stats:{bet_label:`O2.5 Tore`,confidence:68,risk:'GOOD'},
          weather:{bet_label:`Wetter: ${weather?.label}`,confidence:65,risk:'GOOD',weather_impact:weather?.impact||'NEUTRAL'},
          // Use fast single prompt for parallel mode
        });
        // Faster: single master prompt with all context
        const fastPrompt = `Du bist MASTER Fußball-KI. Analysiere schnell:
Spiel: ${m.home} vs ${m.away} (${m.league}, ${m.time})
Quoten: H${m.odds.home} X${m.odds.draw} A${m.odds.away}
xG: H${m.xG.home} A${m.xG.away} | Form: H${m.form.home.join('')} A${m.form.away.join('')}
Sharp Money: ${m.lineMovement?.sharpMoney||'NEUTRAL'} | Wetter: ${weather?.label} (${weather?.impact})
H2H: ${m.h2h} | AvgGoals: ${m.avgGoals}
Antworte NUR als JSON: {"final_pick":"...","bet_label":"...","odds":1.85,"confidence":76,"reasoning":"2 Sätze","key_factor":"...","risk":"SAFE|GOOD|RISK","consensus":"MODERATE","consensus_count":6,"ticket_worthy":true,"alt_pick":"..."}`;
        const text = await callAI(fastPrompt);
        const parsed = text.match(/\{[\s\S]*\}/);
        return parsed ? JSON.parse(parsed[0]) : null;
      } catch(e) { return null; }
    };

    // Run 3 at a time to avoid rate limits
    for (let i=0; i<toAnalyse.length; i+=3) {
      const batch = toAnalyse.slice(i,i+3);
      const batchResults = await Promise.all(batch.map(analyseOne));
      batchResults.forEach((r,j)=>{
        const m = batch[j];
        setParallelResults(p=>({...p,[m.id]:r}));
        if (r&&onResultReady) onResultReady(m.id,r);
      });
      setParallelProgress(Math.round((i+batch.length)/toAnalyse.length*100));
    }
    setParallelRunning(false);
  };

  const allDone = waveActive===5;
  const masterResult = results['master'];

  return (
    <div style={{display:'flex',flexDirection:'column',height:'100%'}}>
      {/* Mode Toggle */}
      <div style={{padding:'10px 16px 0',flexShrink:0}}>
        <div style={{display:'flex',background:'rgba(255,255,255,0.04)',borderRadius:12,padding:3,gap:2,marginBottom:10}}>
          {[{id:'single',label:'🎯 Einzel'},{id:'parallel',label:'⚡ Multi'},{id:'liga',label:'🏆 Liga'}].map(m=>(
            <button key={m.id} onClick={()=>setMode(m.id)} style={{
              flex:1,padding:'7px 4px',borderRadius:10,border:'none',cursor:'pointer',
              fontFamily:'Space Grotesk',fontWeight:700,fontSize:10,letterSpacing:'.03em',
              background:mode===m.id?'rgba(36,218,255,.15)':'transparent',
              color:mode===m.id?'#24daff':'rgba(255,255,255,.35)',transition:'all .2s',
              boxShadow:mode===m.id?'0 0 12px rgba(36,218,255,.1)':'none',
            }}>{m.label}</button>
          ))}
        </div>
      </div>

      {mode==='single' ? (        <>
          {/* Match Header */}
          <div style={{padding:'8px 16px 10px',flexShrink:0,background:'rgba(255,255,255,0.02)',borderBottom:'1px solid rgba(255,255,255,0.05)'}}>
            {match ? (
              <>
                <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:4}}>
                  <div style={{fontSize:9,color:'rgba(255,255,255,0.35)',fontFamily:'JetBrains Mono',letterSpacing:'0.14em'}}>
                    {match.leagueFlag} {match.leagueShort} · {match.time}{match.importance?' · '+match.importance:''}
                  </div>
                  {/* Weather badge */}
                  {match.weather && (
                    <div style={{fontSize:9,padding:'2px 8px',background:getWeatherImpactColor(match.weather.impact)+'15',border:`1px solid ${getWeatherImpactColor(match.weather.impact)}30`,borderRadius:99,color:getWeatherImpactColor(match.weather.impact),fontFamily:'JetBrains Mono'}}>
                      {match.weather.label} {match.weather.wind}km/h
                    </div>
                  )}
                </div>
                <div style={{fontSize:15,fontWeight:700,fontFamily:'Space Grotesk',color:'rgba(255,255,255,0.95)'}}>
                  {match.home} <span style={{color:'rgba(255,255,255,0.25)',fontWeight:400}}>vs</span> {match.away}
                </div>
                <div style={{display:'flex',gap:8,marginTop:6}}>
                  {[{l:'H',v:match.odds.home},{l:'X',v:match.odds.draw},{l:'A',v:match.odds.away}].map(o=>(
                    <div key={o.l} style={{background:'rgba(255,255,255,0.06)',borderRadius:8,padding:'4px 10px',textAlign:'center'}}>
                      <div style={{fontSize:8,color:'rgba(255,255,255,0.3)',fontFamily:'JetBrains Mono'}}>{o.l}</div>
                      <div style={{fontSize:13,fontWeight:700,fontFamily:'JetBrains Mono',color:'#ffb648',textShadow:'0 0 8px #ffb648,0 0 16px #ffb64880'}}>{o.v.toFixed(2)}</div>
                    </div>
                  ))}
                </div>
                <DataAgentBadges match={match}/>
              </>
            ) : (
              <div style={{fontSize:13,color:'rgba(255,255,255,0.3)',fontFamily:'Inter'}}>Kein Spiel ausgewählt — gehe zu "Heute"</div>
            )}
          </div>

          {/* Scrollable content: radar (top) + master decision */}
          <div ref={scrollRef} style={{flex:1,overflowY:'auto'}}>
            <RadarPanel mode="single" match={match} results={results} statuses={statuses} running={running} masterDone={!!masterResult}/>

            {/* Quant Model (Poisson) — always visible if match available */}
            {match && <PoissonCard match={match}/>}

            {/* Only Master Decision */}
            <div style={{padding:'4px 16px 16px'}}>
              {masterResult ? (
                <MasterVerdict result={masterResult} agent={masterAgent}/>
              ) : running ? (
                <div style={{textAlign:'center',padding:'30px 16px',color:'rgba(255,255,255,0.4)',fontFamily:'Inter',fontSize:13}}>
                  <div style={{fontSize:9,color:'#22ff88',fontFamily:'JetBrains Mono',letterSpacing:'.14em',marginBottom:6}}>{t('waveRunning').replace('{w}', waveActive).toUpperCase()}</div>
                  {t('agentsAnalysing')}
                </div>
              ) : (
                <div style={{textAlign:'center',padding:'30px 16px',color:'rgba(255,255,255,0.3)',fontFamily:'Inter',fontSize:12}}>
                  {t('tapToStart')}
                </div>
              )}
            </div>
          </div>

          {/* CTA */}
          <div style={{padding:'10px 16px 14px',flexShrink:0,borderTop:'1px solid rgba(255,255,255,0.05)'}}>
            {!running&&!allDone&&match&&(
              <button onClick={startAnalysis} style={{width:'100%',padding:'14px',borderRadius:14,background:'linear-gradient(135deg,#22ff88 0%,#0eaf5b 100%)',border:'none',cursor:'pointer',color:'#05070a',fontFamily:'Space Grotesk',fontWeight:700,fontSize:15,boxShadow:'0 0 28px rgba(34,255,136,0.35)',display:'flex',alignItems:'center',justifyContent:'center',gap:8}}>🤖 {t('startAgents')}</button>
            )}
            {running&&(
              <div style={{textAlign:'center',padding:'10px',display:'flex',alignItems:'center',justifyContent:'center',gap:10}}>
                <div style={{width:16,height:16,borderRadius:'50%',border:'2px solid #22ff88',borderTopColor:'transparent',animation:'spin 0.8s linear infinite'}} />
                <span style={{fontSize:12,color:'#22ff88',fontFamily:'Inter'}}>{t('running')} {waveActive}/4 {t('of')}...</span>
              </div>
            )}
            {allDone&&(
              <button onClick={startAnalysis} style={{width:'100%',padding:'12px',borderRadius:12,background:'rgba(255,255,255,0.05)',border:'1px solid rgba(255,255,255,0.1)',cursor:'pointer',color:'rgba(255,255,255,0.5)',fontFamily:'Space Grotesk',fontWeight:600,fontSize:13,display:'flex',alignItems:'center',justifyContent:'center',gap:8}}>🔄 {t('reanalyse')}</button>
            )}
          </div>
        </>
      ) : mode==='parallel' ? (
        /* PARALLEL MODE */
        <div style={{display:'flex',flexDirection:'column',flex:1,overflow:'hidden'}}>
          <MatchSelector
            matches={matches||[]}
            selected={selectedIds}
            onToggle={id=>setSelectedIds(p=>p.includes(id)?p.filter(x=>x!==id):[...p,id])}
            onSelectAll={()=>setSelectedIds((matches||[]).map(m=>m.id))}
            onClear={()=>setSelectedIds([])}
          />
          <div style={{flex:1,overflowY:'auto',padding:'12px 16px'}}>
            <RadarPanel mode="parallel" matchResults={parallelResults} running={parallelRunning} totalCount={selectedIds.length} size={220}/>
            {selectedIds.length===0&&(
              <div style={{textAlign:'center',padding:'32px 20px',color:'rgba(255,255,255,0.25)',fontFamily:'Inter',fontSize:12}}>
                <div style={{fontSize:32,marginBottom:10}}>⚡</div>
                Wähle Spiele für Parallel-Analyse
              </div>
            )}
            {(matches||[]).filter(m=>selectedIds.includes(m.id)).map(m=>(
              <ParallelResultCard key={m.id} match={m} result={parallelResults[m.id]} onAddToTicket={onAddToTicket||((match,res)=>{})} />
            ))}
            {Object.keys(parallelResults).length>0&&!parallelRunning&&(
              <div style={{marginTop:8,padding:'10px',background:'rgba(34,255,136,0.06)',border:'1px solid rgba(34,255,136,0.2)',borderRadius:12,textAlign:'center'}}>
                <span style={{fontSize:11,color:'#22ff88',fontFamily:'Space Grotesk',fontWeight:600}}>✓ {Object.keys(parallelResults).length} Spiele analysiert · Tipps jetzt unter "Heute" sichtbar</span>
              </div>
            )}
            <div style={{height:16}} />
          </div>
          <div style={{padding:'10px 16px 14px',flexShrink:0,borderTop:'1px solid rgba(255,255,255,0.05)'}}>
            {parallelRunning ? (
              <div>
                <div style={{display:'flex',justifyContent:'space-between',marginBottom:6}}>
                  <span style={{fontSize:11,color:'#22ff88',fontFamily:'Inter'}}>⚡ Parallel-Analyse läuft...</span>
                  <span style={{fontSize:11,fontFamily:'JetBrains Mono',color:'#22ff88'}}>{parallelProgress}%</span>
                </div>
                <div style={{height:4,background:'rgba(255,255,255,0.06)',borderRadius:99,overflow:'hidden'}}>
                  <div style={{height:'100%',width:`${parallelProgress}%`,background:'linear-gradient(90deg,#22ff88,#00e5ff)',borderRadius:99,transition:'width .4s ease',boxShadow:'0 0 8px rgba(34,255,136,0.5)'}} />
                </div>
              </div>
            ) : (
              <button onClick={runParallel} disabled={selectedIds.length===0} style={{
                width:'100%',padding:'14px',borderRadius:14,
                background:selectedIds.length>0?'linear-gradient(135deg,#00e5ff,#24daff88)':'rgba(255,255,255,0.04)',
                border:selectedIds.length>0?'none':'1px solid rgba(255,255,255,0.08)',
                cursor:selectedIds.length>0?'pointer':'default',
                color:selectedIds.length>0?'#05070a':'rgba(255,255,255,0.25)',
                fontFamily:'Space Grotesk',fontWeight:700,fontSize:14,
                boxShadow:selectedIds.length>0?'0 0 24px rgba(0,229,255,0.3)':'none',
                display:'flex',alignItems:'center',justifyContent:'center',gap:8,
              }}>⚡ {selectedIds.length} Spiele analysieren</button>
            )}
          </div>
        </div>
      ) : mode==='liga' ? (
        <LigaAnalyseScreen matches={matches||[]} onAddToTicket={onAddToTicket||((m,r)=>{})} />
      ) : null}
    </div>
  );
}

// ─── Liga Analyse Result Row ──────────────────────────────────
function LigaResultRow({ match, result, rank, onAddToTicket, isRunning }) {
  const [exp, setExp] = useState(false);
  // Pending — only show spinner if analysis is actively running
  if (!result) return isRunning ? (
    <div style={{ display:'flex', alignItems:'center', gap:8, padding:'7px 12px', background:'rgba(255,255,255,0.02)', borderRadius:10, marginBottom:4, border:'1px solid rgba(255,255,255,0.05)' }}>
      <div style={{ width:10, height:10, borderRadius:'50%', border:'1.5px solid #22ff88', borderTopColor:'transparent', animation:'spin 0.8s linear infinite', flexShrink:0 }} />
      <span style={{ fontSize:11, color:'rgba(255,255,255,0.3)', fontFamily:'Space Grotesk' }}>{match.home} vs {match.away}</span>
      <span style={{ fontSize:8, color:'rgba(255,255,255,0.18)', fontFamily:'JetBrains Mono', marginLeft:'auto' }}>{match.leagueFlag} {match.time}</span>
    </div>
  ) : null;
  const rc = getRiskColor(result.risk);
  const conf = result.confidence || 75;
  return (
    <div style={{ marginBottom:5, borderRadius:12, overflow:'hidden', border:`1px solid ${rc}22`, background:`${rc}06`, animation:'fadeUp .3s ease' }}>
      <div onClick={() => setExp(!exp)} style={{ display:'flex', alignItems:'center', gap:8, padding:'8px 12px', cursor:'pointer' }}>
        {/* Rank */}
        <div style={{ width:22, height:22, borderRadius:'50%', background:`${rc}20`, border:`1.5px solid ${rc}50`, display:'flex', alignItems:'center', justifyContent:'center', fontSize:9, fontFamily:'JetBrains Mono', fontWeight:800, color:rc, flexShrink:0 }}>#{rank}</div>
        {/* Match info */}
        <div style={{ flex:1, minWidth:0 }}>
          <div style={{ fontSize:9, color:'rgba(255,255,255,0.28)', fontFamily:'JetBrains Mono', marginBottom:1 }}>{match.leagueFlag} {match.leagueShort} · {match.time}</div>
          <div style={{ fontSize:11, fontWeight:700, fontFamily:'Space Grotesk', color:'rgba(255,255,255,0.88)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{match.home} vs {match.away}</div>
          <div style={{ fontSize:10, color:rc, fontFamily:'Inter' }}>{result.bet_label || result.final_pick}</div>
        </div>
        {/* Odds + Confidence */}
        <div style={{ textAlign:'right', flexShrink:0 }}>
          <div style={{ fontSize:17, fontWeight:800, fontFamily:'JetBrains Mono', color:'#ffa500', textShadow:'0 0 10px rgba(255,165,0,0.4)' }}>{(result.odds||2.0).toFixed(2)}</div>
          <div style={{ fontSize:8, color:rc, fontFamily:'JetBrains Mono' }}>{conf}%</div>
        </div>
        <span style={{ fontSize:8, color:'rgba(255,255,255,0.15)', flexShrink:0 }}>{exp?'▲':'▼'}</span>
      </div>
      {/* Expanded */}
      {exp && (
        <div style={{ padding:'0 12px 10px', borderTop:`1px solid ${rc}15` }}>
          {/* Confidence bar */}
          <div style={{ marginBottom:8, marginTop:8 }}>
            <div style={{ display:'flex', justifyContent:'space-between', marginBottom:3 }}>
              <span style={{ fontSize:8, color:'rgba(255,255,255,0.3)', fontFamily:'JetBrains Mono' }}>KONFIDENZ</span>
              <span style={{ fontSize:8, color:rc, fontFamily:'JetBrains Mono', fontWeight:700 }}>{conf}%</span>
            </div>
            <div style={{ height:4, background:'rgba(255,255,255,0.06)', borderRadius:99, overflow:'hidden' }}>
              <div style={{ height:'100%', width:`${conf}%`, background:`linear-gradient(90deg,${rc},${rc}88)`, borderRadius:99, boxShadow:`0 0 8px ${rc}`, transition:'width 0.8s ease' }} />
            </div>
          </div>
          {result.reasoning && <p style={{ fontSize:10, color:'rgba(255,255,255,0.5)', lineHeight:1.5, margin:'0 0 8px', fontFamily:'Inter' }}>{result.reasoning}</p>}
          {result.key_factor && <div style={{ fontSize:9, color:rc, fontFamily:'Inter', fontStyle:'italic', marginBottom:8 }}>→ {result.key_factor}</div>}
          <div style={{ display:'flex', gap:6 }}>
            <button onClick={() => onAddToTicket(match, result)} style={{ flex:1, padding:'8px', borderRadius:10, background:`${rc}18`, border:`1px solid ${rc}35`, color:rc, cursor:'pointer', fontFamily:'Space Grotesk', fontWeight:700, fontSize:11, display:'flex', alignItems:'center', justifyContent:'center', gap:4 }}>+ Zum Schein</button>
          </div>
        </div>
      )}
    </div>
  );
}

// ─── Top 3 Summary Card ───────────────────────────────────────
function Top3Card({ results, matches, onAddToTicket, onAddAll }) {
  const top3 = results.slice(0, 3);
  if (top3.length === 0) return null;
  const totalOdds = top3.reduce((a, r) => a * (r.result?.odds || 1.85), 1);
  return (
    <div style={{ margin:'12px 0', padding:'12px 14px', background:'linear-gradient(135deg,rgba(0,245,255,0.08),rgba(0,255,136,0.04))', border:'1px solid rgba(0,245,255,0.2)', borderRadius:16, backdropFilter:'blur(12px)', boxShadow:'0 0 24px rgba(0,245,255,0.08)' }}>
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:10 }}>
        <div>
          <div style={{ fontSize:8, color:'#00f5ff', fontFamily:'JetBrains Mono', letterSpacing:'.14em', marginBottom:2 }}>🏆 TOP 3 HEUTE</div>
          <div style={{ fontSize:11, color:'rgba(255,255,255,0.6)', fontFamily:'Inter' }}>Kombi-Quote: <strong style={{ color:'#ffa500', fontFamily:'JetBrains Mono' }}>{totalOdds.toFixed(2)}</strong></div>
        </div>
        <button onClick={onAddAll} style={{ padding:'6px 12px', borderRadius:10, background:'rgba(0,245,255,0.15)', border:'1px solid rgba(0,245,255,0.3)', color:'#00f5ff', cursor:'pointer', fontFamily:'Space Grotesk', fontWeight:700, fontSize:10 }}>+ Alle zum Schein</button>
      </div>
      {top3.map((r, i) => {
        const rc = getRiskColor(r.result?.risk || 'GOOD');
        return (
          <div key={i} style={{ display:'flex', alignItems:'center', gap:8, padding:'6px 0', borderBottom: i < 2 ? '1px solid rgba(255,255,255,0.05)' : 'none' }}>
            <div style={{ width:18, height:18, borderRadius:'50%', background:`${rc}20`, border:`1px solid ${rc}40`, display:'flex', alignItems:'center', justifyContent:'center', fontSize:8, fontFamily:'JetBrains Mono', fontWeight:800, color:rc, flexShrink:0 }}>#{i+1}</div>
            <div style={{ flex:1, minWidth:0 }}>
              <div style={{ fontSize:10, fontWeight:600, fontFamily:'Space Grotesk', color:'rgba(255,255,255,0.82)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{r.match.home} vs {r.match.away}</div>
              <div style={{ fontSize:9, color:rc }}>{r.result?.bet_label}</div>
            </div>
            <div style={{ textAlign:'right', flexShrink:0 }}>
              <div style={{ fontSize:14, fontWeight:800, fontFamily:'JetBrains Mono', color:'#ffa500' }}>{(r.result?.odds||1.85).toFixed(2)}</div>
              <div style={{ fontSize:8, color:rc, fontFamily:'JetBrains Mono' }}>{r.result?.confidence}%</div>
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ─── Liga Analyse Screen ──────────────────────────────────────
function LigaAnalyseScreen({ matches, onAddToTicket }) {
  const [selectedLeague, setSelectedLeague] = useState('ALL');
  const [results, setResults] = useState({}); // matchId → result
  const [running, setRunning] = useState(false);
  const [progress, setProgress] = useState(0);
  const [sortBy, setSortBy] = useState('confidence'); // 'confidence' | 'odds' | 'risk'

  // Get unique leagues
  const leagues = ['ALL', ...new Set(matches.map(m => m.leagueShort))];
  const leagueNames = { ALL:'🌍 Alle Ligen', ...Object.fromEntries(matches.map(m => [m.leagueShort, `${m.leagueFlag} ${m.leagueShort}`])) };

  // Filter matches by league
  const filtered = selectedLeague === 'ALL' ? matches : matches.filter(m => m.leagueShort === selectedLeague);

  // Sorted results
  const sortedResults = Object.entries(results)
    .map(([id, result]) => ({ match: matches.find(m => m.id === id), result }))
    .filter(r => r.match && r.result)
    .sort((a, b) => {
      if (sortBy === 'confidence') return (b.result.confidence||0) - (a.result.confidence||0);
      if (sortBy === 'odds') return (b.result.odds||0) - (a.result.odds||0);
      if (sortBy === 'risk') {
        const riskOrder = { SAFE:0, GOOD:1, RISK:2 };
        return (riskOrder[a.result.risk]||1) - (riskOrder[b.result.risk]||1);
      }
      return 0;
    });

  const analyseLeague = async () => {
    if (running) return;
    // Only analyse matches in currently selected league that haven't been done yet
    const toRun = filtered.filter(m => !results[m.id]);
    if (toRun.length === 0) { setResults({}); return; } // reset if all done
    setRunning(true); setProgress(0);

    for (let i = 0; i < toRun.length; i += 3) {
      const batch = toRun.slice(i, i + 3);
      await Promise.all(batch.map(async m => {
        const weather = m.weather;
        const prompt = `Du bist Elite-Fußball-KI. Analysiere dieses Spiel präzise:
Spiel: ${m.home} vs ${m.away} (${m.league}, ${m.time})
Quoten: H${m.odds.home} X${m.odds.draw} A${m.odds.away} | BTTS:${m.btts.yes} | O2.5:${m.overUnder.o25}
xG: H${m.xG.home} A${m.xG.away} | Form: H${m.form.home.join('')} A${m.form.away.join('')}
Sharp Money: ${m.lineMovement?.sharpMoney||'NEUTRAL'} | Wetter: ${weather?.label||'?'} (${weather?.impact||'NEUTRAL'})
H2H: ${m.h2h} | AvgGoals: ${m.avgGoals} | Heimpos: ${m.homePos}. | Auswärtspos: ${m.awayPos}.
Antworte NUR als JSON: {"final_pick":"...","bet_label":"...","odds":1.85,"confidence":76,"reasoning":"2 präzise Sätze","key_factor":"...","risk":"SAFE|GOOD|RISK","ticket_worthy":true,"alt_pick":"..."}`;
        try {
          const text = await callAI(prompt);
          const parsed = text.match(/\{[\s\S]*\}/);
          if (parsed) {
            const result = JSON.parse(parsed[0]);
            setResults(p => ({ ...p, [m.id]: result }));
          }
        } catch(e) { console.warn('Liga analyse:', m.home, e.message); }
      }));
      setProgress(Math.round((i + Math.min(3, toRun.length - i)) / toRun.length * 100));
    }
    setRunning(false); setProgress(100);
  };

  const clearResults = () => { setResults({}); setProgress(0); };

  const handleAddAll = () => {
    sortedResults.slice(0, 3).forEach(({ match, result }) => {
      if (result?.ticket_worthy !== false) onAddToTicket(match, result);
    });
  };

  const doneCount = Object.keys(results).length;
  const ticketWorthy = sortedResults.filter(r => r.result?.ticket_worthy !== false && (r.result?.confidence||0) >= 72);

  return (
    <div style={{ display:'flex', flexDirection:'column', height:'100%' }}>
      {/* League Selector */}
      <div style={{ padding:'10px 16px 0', flexShrink:0 }}>
        <div style={{ fontSize:8, color:'rgba(255,255,255,0.3)', fontFamily:'JetBrains Mono', letterSpacing:'.14em', marginBottom:8 }}>LIGA WÄHLEN</div>
        <div style={{ display:'flex', gap:5, overflowX:'auto', scrollbarWidth:'none', paddingBottom:8 }}>
          {leagues.map(lk => {
            const a = selectedLeague === lk;
            const matchCount = lk === 'ALL' ? matches.length : matches.filter(m => m.leagueShort === lk).length;
            const doneInLeague = lk === 'ALL' ? doneCount : matches.filter(m => m.leagueShort === lk && results[m.id]).length;
            return (
              <button key={lk} onClick={() => setSelectedLeague(lk)} style={{
                display:'flex', alignItems:'center', gap:3, padding:'5px 10px', borderRadius:99, flexShrink:0,
                background: a ? 'rgba(0,245,255,0.15)' : 'rgba(255,255,255,0.04)',
                border: `1px solid ${a ? 'rgba(0,245,255,0.5)' : 'rgba(255,255,255,0.08)'}`,
                color: a ? '#00f5ff' : 'rgba(255,255,255,0.45)', cursor:'pointer',
                fontFamily:'JetBrains Mono', fontWeight:700, fontSize:9, letterSpacing:'.04em',
                boxShadow: a ? '0 0 12px rgba(0,245,255,0.2)' : 'none',
              }}>
                {leagueNames[lk]}
                <span style={{ fontSize:8, background: doneInLeague > 0 ? 'rgba(0,255,136,0.2)' : 'rgba(255,255,255,0.1)', color: doneInLeague > 0 ? '#00ff88' : 'rgba(255,255,255,0.4)', padding:'0 4px', borderRadius:4, fontFamily:'JetBrains Mono' }}>
                  {doneInLeague}/{matchCount}
                </span>
              </button>
            );
          })}
        </div>
      </div>

      {/* Sort + Stats bar */}
      {doneCount > 0 && (
        <div style={{ padding:'0 16px 8px', flexShrink:0, display:'flex', alignItems:'center', gap:8 }}>
          <span style={{ fontSize:8, color:'rgba(255,255,255,0.3)', fontFamily:'JetBrains Mono' }}>SORTIEREN:</span>
          {[{k:'confidence',l:'KONFIDENZ'},{k:'odds',l:'QUOTE'},{k:'risk',l:'RISIKO'}].map(s => (
            <button key={s.k} onClick={() => setSortBy(s.k)} style={{ padding:'3px 8px', borderRadius:6, background: sortBy===s.k ? 'rgba(0,245,255,0.15)' : 'rgba(255,255,255,0.04)', border:`1px solid ${sortBy===s.k ? 'rgba(0,245,255,0.4)' : 'rgba(255,255,255,0.07)'}`, color: sortBy===s.k ? '#00f5ff' : 'rgba(255,255,255,0.35)', cursor:'pointer', fontFamily:'JetBrains Mono', fontSize:8, fontWeight:700 }}>{s.l}</button>
          ))}
          <button onClick={clearResults} style={{ marginLeft:'auto', padding:'3px 8px', borderRadius:6, background:'rgba(255,84,112,0.1)', border:'1px solid rgba(255,84,112,0.2)', color:'#ff5470', cursor:'pointer', fontFamily:'JetBrains Mono', fontSize:8 }}>Reset</button>
        </div>
      )}

      {/* Scrollable results */}
      <div style={{ flex:1, overflowY:'auto', padding:'0 16px' }}>

        <RadarPanel mode="liga" matchResults={results} running={running} totalCount={filtered.length} size={220}/>

        {/* Empty state — before first analysis */}
        {Object.keys(results).length === 0 && !running && (
          <div style={{ textAlign:'center', padding:'32px 16px' }}>
            <div style={{ fontSize:40, marginBottom:12, opacity:0.5 }}>🏆</div>
            <div style={{ fontSize:14, fontWeight:700, color:'rgba(255,255,255,0.5)', fontFamily:'Space Grotesk', marginBottom:8 }}>
              {selectedLeague === 'ALL' ? 'Alle 18 Spiele' : `${filtered.length} Spiele in ${selectedLeague}`} bereit
            </div>
            <div style={{ fontSize:11, color:'rgba(255,255,255,0.25)', fontFamily:'Inter', lineHeight:1.6, marginBottom:16 }}>
              Wähle eine Liga und klicke auf Analysieren.<br/>Jede Analyse kostet API-Calls — gezielt einsetzen!
            </div>
            <div style={{ display:'flex', flexWrap:'wrap', gap:6, justifyContent:'center' }}>
              {filtered.map(m=>(
                <div key={m.id} style={{ fontSize:9, color:'rgba(255,255,255,0.3)', fontFamily:'JetBrains Mono', padding:'3px 8px', background:'rgba(255,255,255,0.04)', borderRadius:99, border:'1px solid rgba(255,255,255,0.07)' }}>
                  {m.leagueFlag} {m.home.split(' ')[0]} vs {m.away.split(' ')[0]}
                </div>
              ))}
            </div>
          </div>
        )}

        {/* Top 3 summary */}
        {sortedResults.length >= 3 && (
          <Top3Card results={sortedResults} matches={matches} onAddToTicket={onAddToTicket} onAddAll={handleAddAll} />
        )}

        {/* Ticket-worthy picks highlight */}
        {ticketWorthy.length > 0 && (
          <div style={{ marginBottom:10, padding:'8px 12px', background:'rgba(0,255,136,0.05)', border:'1px solid rgba(0,255,136,0.15)', borderRadius:12 }}>
            <div style={{ fontSize:8, color:'#00ff88', fontFamily:'JetBrains Mono', letterSpacing:'.12em', marginBottom:4 }}>✅ SCHEIN-WÜRDIG ({ticketWorthy.length} PICKS)</div>
            <div style={{ fontSize:10, color:'rgba(255,255,255,0.5)', fontFamily:'Inter' }}>
              {ticketWorthy.length} Tipps mit ≥72% Konfidenz · Top-5 Kombi: <strong style={{ color:'#ffa500', fontFamily:'JetBrains Mono' }}>{ticketWorthy.slice(0,5).reduce((a,r)=>a*(r.result?.odds||1.85),1).toFixed(2)}</strong>
            </div>
          </div>
        )}

        {/* Pending (spinner only while running) */}
        {running && filtered.filter(m => !results[m.id]).map(m => (
          <LigaResultRow key={m.id} match={m} result={null} rank={0} onAddToTicket={onAddToTicket} isRunning={true} />
        ))}

        {/* Sorted results */}
        {sortedResults
          .filter(r => filtered.find(m => m.id === r.match.id))
          .map((r, i) => (
            <LigaResultRow key={r.match.id} match={r.match} result={r.result} rank={i+1} onAddToTicket={onAddToTicket} isRunning={false} />
          ))
        }

        <div style={{ height:16 }} />
      </div>

      {/* CTA */}
      <div style={{ padding:'10px 16px 14px', flexShrink:0, borderTop:'1px solid rgba(255,255,255,0.05)' }}>
        {/* API cost warning */}
        {!running && Object.keys(results).length === 0 && (
          <div style={{ fontSize:9, color:'rgba(255,165,0,0.6)', fontFamily:'JetBrains Mono', textAlign:'center', marginBottom:8, padding:'5px 10px', background:'rgba(255,165,0,0.06)', borderRadius:8, border:'1px solid rgba(255,165,0,0.15)' }}>
            ⚡ {filtered.length} API-Call{filtered.length>1?'s':''} · {selectedLeague === 'ALL' ? 'Alle Ligen' : `nur ${selectedLeague}`} · erst nach Klick
          </div>
        )}
        {running ? (
          <div>
            <div style={{ display:'flex', justifyContent:'space-between', marginBottom:6 }}>
              <span style={{ fontSize:11, color:'#22ff88', fontFamily:'Space Grotesk', fontWeight:600 }}>⚡ Analysiere {filtered.length} Spiele...</span>
              <span style={{ fontSize:11, fontFamily:'JetBrains Mono', color:'#22ff88', fontWeight:700 }}>{progress}%</span>
            </div>
            <div style={{ height:5, background:'rgba(255,255,255,0.06)', borderRadius:99, overflow:'hidden' }}>
              <div style={{ height:'100%', width:`${progress}%`, background:'linear-gradient(90deg,#22ff88,#00f5ff)', borderRadius:99, transition:'width .4s ease', boxShadow:'0 0 12px rgba(34,255,136,0.5)' }} />
            </div>
            <div style={{ fontSize:9, color:'rgba(255,255,255,0.25)', fontFamily:'JetBrains Mono', textAlign:'center', marginTop:6 }}>
              {Object.keys(results).length}/{filtered.length} abgeschlossen
            </div>
          </div>
        ) : (
          <div style={{ display:'flex', gap:8 }}>
            <button onClick={analyseLeague} style={{
              flex:1, padding:'13px', borderRadius:14,
              background: filtered.every(m => results[m.id])
                ? 'rgba(255,255,255,0.05)'
                : 'linear-gradient(135deg,#22ff88,#00f5ff88)',
              border: filtered.every(m => results[m.id]) ? '1px solid rgba(255,255,255,0.1)' : 'none',
              cursor:'pointer', fontFamily:'Space Grotesk', fontWeight:700, fontSize:13,
              color: filtered.every(m => results[m.id]) ? 'rgba(255,255,255,0.4)' : '#05070a',
              boxShadow: filtered.every(m => results[m.id]) ? 'none' : '0 0 28px rgba(34,255,136,0.35)',
              display:'flex', alignItems:'center', justifyContent:'center', gap:8,
            }}>
              {filtered.every(m => results[m.id])
                ? '🔄 Neu analysieren'
                : `⚡ ${selectedLeague === 'ALL' ? 'Alle 18' : filtered.length + ' ' + selectedLeague} analysieren`}
            </button>
            {ticketWorthy.length >= 2 && (
              <button onClick={handleAddAll} style={{ padding:'13px 16px', borderRadius:14, background:'rgba(255,165,0,0.15)', border:'1px solid rgba(255,165,0,0.35)', color:'#ffa500', cursor:'pointer', fontFamily:'Space Grotesk', fontWeight:700, fontSize:12, display:'flex', alignItems:'center', justifyContent:'center', gap:6 }}>
                🎟️ Top {Math.min(3,ticketWorthy.length)}
              </button>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

// ─── Erweiterte Radar-Knoten (Data + Quant + Ensemble) ────────
const RADAR_DATA_QUANT = [
  // Wave 0 — Daten-Enricher (laufen vor dem KI-Komitee)
  { id:'fatigue',    name:'FAT', color:'#ffb648', wave:0, type:'data',     fullName:'Fatigue' },
  { id:'lineup',     name:'LIN', color:'#00ff88', wave:0, type:'data',     fullName:'Lineup'  },
  { id:'motivation', name:'MOT', color:'#ffd700', wave:0, type:'data',     fullName:'Motivation' },
  { id:'referee',    name:'REF', color:'#ff5470', wave:0, type:'data',     fullName:'Referee' },
  // Wave 4.5 — Ensemble Co-Master (parallel zu MASTER, andere Labs)
  { id:'groq',       name:'GRQ', color:'#ffb648', wave:4, type:'ensemble', fullName:'Groq Llama-3.3 70B' },
  { id:'gptoss',     name:'GPT', color:'#5ee7ff', wave:4, type:'ensemble', fullName:'OpenAI GPT-OSS 120B' },
  // Wave 5 — Quant-Anker (deterministisch, immer verfügbar)
  { id:'poisson',    name:'POI', color:'#5ee7ff', wave:5, type:'quant',    fullName:'Poisson 10k' },
  { id:'elo',        name:'ELO', color:'#a855f7', wave:5, type:'quant',    fullName:'Elo Rating' },
];

// Liefert die volle Knoten-Liste in Reihenfolge: data → wave1 → wave2 → wave3 → wave4 → ensemble → quant
function getRadarAgents() {
  const dataNodes     = RADAR_DATA_QUANT.filter(n => n.type === 'data');
  const ensembleNodes = RADAR_DATA_QUANT.filter(n => n.type === 'ensemble');
  const quantNodes    = RADAR_DATA_QUANT.filter(n => n.type === 'quant');
  return [
    ...dataNodes,
    ...AGENTS.filter(a => a.wave === 1),
    ...AGENTS.filter(a => a.wave === 2),
    ...AGENTS.filter(a => a.wave === 3),
    ...AGENTS.filter(a => a.wave === 4),
    ...ensembleNodes,
    ...quantNodes,
  ];
}

// Aggregat-Status für Multi/Liga-Modus: keine per-agent Daten verfügbar (nur ein
// kumuliertes LLM-Result pro Match), daher zeigen alle 18 Knoten den Gesamtstatus —
// running während Analyse, done mit avg-confidence wenn fertig, idle sonst.
function getRadarAggregateResults(matchResults, running) {
  const agents = getRadarAgents();
  const results = {};
  const statuses = {};
  const matchIds = Object.keys(matchResults || {});

  if (matchIds.length === 0) {
    // Idle / pre-run — leeres Radar
    agents.forEach(a => { statuses[a.id] = 'idle'; });
    return { results, statuses };
  }

  const confidences = matchIds
    .map(id => matchResults[id]?.confidence)
    .filter(c => typeof c === 'number' && Number.isFinite(c));
  const avg = confidences.length
    ? confidences.reduce((a, b) => a + b, 0) / confidences.length
    : 50;

  agents.forEach(a => {
    statuses[a.id] = running ? 'running' : 'done';
    results[a.id] = { confidence: avg, dataQuality: 'aggregate' };
  });
  return { results, statuses };
}

// Mappt Datenagent-Status + Quant-Probas auf Confidence-Werte für den Radar
// Garantie: jeder Knoten kriegt IMMER einen sichtbaren Wert (Loading / no-data / partial / full)
function getRadarResults(match, llmResults, llmStatuses) {
  const results  = { ...llmResults };
  const statuses = { ...llmStatuses };
  const set = (id, status, conf, dataQuality) => {
    statuses[id] = status;
    if (conf != null) results[id] = { confidence: conf, dataQuality };
  };

  // FATIGUE
  if (!match?.fatigue) {
    set('fatigue', 'running');
  } else if (match.fatigue.status === 'ok') {
    const both = match.fatigue.home && match.fatigue.away;
    set('fatigue', 'done', both ? 92 : 55, both ? 'full' : 'partial');
  } else {
    set('fatigue', 'done', 18, 'unavailable');
  }
  // LINEUP
  if (!match?.lineup) {
    set('lineup', 'running');
  } else if (match.lineup.confirmed) {
    set('lineup', 'done', 95, 'full');
  } else if (match.lineup.status === 'no_espn_id' || match.lineup.status === 'unavailable') {
    set('lineup', 'done', 18, 'unavailable');
  } else {
    set('lineup', 'done', 35, 'pending'); // Roster bekannt aber nicht final
  }
  // MOTIVATION
  if (!match?.motivation) {
    set('motivation', 'running');
  } else if (match.motivation.status === 'ok') {
    const avg = Math.round((match.motivation.home.score + match.motivation.away.score) / 2);
    set('motivation', 'done', avg, 'full');
  } else {
    set('motivation', 'done', 18, 'unavailable');
  }
  // REFEREE
  if (!match?.referee) {
    set('referee', 'running');
  } else if (match.referee.status === 'ok') {
    set('referee', 'done', 95, 'full');
  } else if (match.referee.status === 'not_found' || match.referee.status === 'no_data') {
    set('referee', 'done', 38, 'partial');
  } else {
    set('referee', 'done', 18, 'unavailable');
  }
  // GROQ + GPT-OSS Ensemble (aus llmResults.master.ensemble nach Wave 4)
  const ens = llmResults.master?.ensemble || (llmResults.groq || llmResults.gemini ? { groq: llmResults.groq?{confidence:llmResults.groq.confidence||60,agrees:true}:null, gemini: llmResults.gemini?{confidence:llmResults.gemini.confidence||60,agrees:true}:null } : null);
  if (ens?.groq) {
    set('groq', 'done', ens.groq.confidence || 60, ens.groq.agrees ? 'full' : 'partial');
  } else if (llmStatuses.master === 'running') {
    set('groq', 'running');
  }
  if (ens?.gemini) {
    set('gptoss', 'done', ens.gemini.confidence || 60, ens.gemini.agrees ? 'full' : 'partial');
  } else if (llmStatuses.master === 'running') {
    set('gptoss', 'running');
  }
  // POISSON — immer verfügbar bei Match mit Quoten
  if (match?.odds && window.poisson) {
    try {
      const sim = window.poisson.simulate(match, 2000);
      const top = Math.max(sim.home, sim.draw, sim.away);
      set('poisson', 'done', Math.round(top * 100), 'full');
    } catch { set('poisson', 'done', 25, 'unavailable'); }
  } else {
    set('poisson', 'done', 25, 'unavailable');
  }
  // ELO — geht nur mit Team-IDs
  if (match?.homeTeamId && match?.awayTeamId && window.elo) {
    try {
      const pred = window.elo.predict(match.homeTeamId, match.awayTeamId);
      const top = Math.max(pred.home, pred.draw, pred.away);
      const ratingsKnown = window.elo.get(match.homeTeamId) !== window.elo.DEFAULT && window.elo.get(match.awayTeamId) !== window.elo.DEFAULT;
      set('elo', 'done', Math.round(top * 100), ratingsKnown ? 'full' : 'bootstrapped');
    } catch { set('elo', 'done', 25, 'unavailable'); }
  } else {
    set('elo', 'done', 25, 'unavailable');
  }
  return { results, statuses };
}

// ─── Quant-Verdict (Poisson + Elo + LLM-Konsens) ──────────────
// Gewichte aus tracker.sourceWeights() — initial 60P/25E/15L,
// werden nach 20+ aufgelösten Picks per inverse-Brier gelernt.
function computeQuantVerdict(match, masterLLM, llmResults) {
  if (!match || !window.poisson) return null;
  const sim = window.poisson.simulate(match, 10000);
  const elo = window.elo && match.homeTeamId && match.awayTeamId
    ? window.elo.predict(match.homeTeamId, match.awayTeamId)
    : null;

  // Dynamische Gewichte aus History (Brier-basiert)
  const W = window.tracker?.sourceWeights?.() || { poisson: 0.60, elo: 0.25, llm: 0.15, learned: false };

  // LLM-Konsens für 1X2: zähle Stimmen pro Outcome
  const recs = Object.values(llmResults || {}).filter(r => r && r.recommendation);
  const llmVotes = { '1':0, 'X':0, '2':0, 'O':0, 'U':0, 'B':0 };
  recs.forEach(r => {
    const v = (r.recommendation || '').toUpperCase();
    if (v.includes('HOME')) llmVotes['1']++;
    else if (v === 'DRAW') llmVotes['X']++;
    else if (v.includes('AWAY')) llmVotes['2']++;
    else if (v.includes('OVER')) llmVotes['O']++;
    else if (v.includes('UNDER')) llmVotes['U']++;
    else if (v.includes('BTTS')) llmVotes['B']++;
  });
  const llmTotal = Object.values(llmVotes).reduce((a,b)=>a+b,0) || 1;
  const llm1X2 = {
    home: llmVotes['1'] / llmTotal,
    draw: llmVotes['X'] / llmTotal,
    away: llmVotes['2'] / llmTotal,
  };

  // 1X2 blended mit dynamischen Gewichten
  const blended1X2 = {
    home: W.poisson * sim.home + W.elo * (elo?.home ?? sim.home) + W.llm * llm1X2.home,
    draw: W.poisson * sim.draw + W.elo * (elo?.draw ?? sim.draw) + W.llm * llm1X2.draw,
    away: W.poisson * sim.away + W.elo * (elo?.away ?? sim.away) + W.llm * llm1X2.away,
  };
  // Normalisieren
  const z = blended1X2.home + blended1X2.draw + blended1X2.away;
  if (z > 0) { blended1X2.home /= z; blended1X2.draw /= z; blended1X2.away /= z; }

  // O2.5 / BTTS: Poisson dominiert, LLM-Beimischung skaliert mit gelerntem llm-Gewicht
  const llmOver = (llmVotes['O'] - llmVotes['U']) / llmTotal + 0.5;
  const wLlmGoals = Math.min(0.4, Math.max(0.1, W.llm * 2)); // 0.15→0.30, 0.05→0.10
  const blendedO25 = (1 - wLlmGoals) * sim.over[2.5] + wLlmGoals * Math.max(0, Math.min(1, llmOver));
  const llmBtts = llmVotes['B'] / llmTotal > 0 ? 0.65 : sim.btts;
  const blendedBtts = (1 - wLlmGoals) * sim.btts + wLlmGoals * llmBtts;

  // Kandidaten mit Edge gegen Markt
  const candidates = [
    { market:'1X2', pick:'1', label:`${match.home} ${currentLang==='de'?'Sieg':currentLang==='tr'?'Galip':'Win'}`, prob: blended1X2.home, odd: match.odds?.home },
    { market:'1X2', pick:'X', label: t('draw'), prob: blended1X2.draw, odd: match.odds?.draw },
    { market:'1X2', pick:'2', label:`${match.away} ${currentLang==='de'?'Sieg':currentLang==='tr'?'Galip':'Win'}`,  prob: blended1X2.away, odd: match.odds?.away },
    { market:'O2.5', pick:'over', label: t('overGoals'),   prob: blendedO25,      odd: match.overUnder?.o25 },
    { market:'O2.5', pick:'under', label: t('underGoals'), prob: 1 - blendedO25,  odd: match.overUnder?.u25 },
    { market:'BTTS', pick:'yes', label: t('bttsYes'), prob: blendedBtts,   odd: match.btts?.yes },
    { market:'BTTS', pick:'no', label: t('bttsNo'),prob: 1-blendedBtts, odd: match.btts?.no },
  ].filter(c => c.odd && c.prob > 0).map(c => ({...c, edge: c.odd * c.prob - 1}));
  candidates.sort((a,b) => b.edge - a.edge);
  const best = candidates[0];
  if (!best) return null;

  // Per-Source-Probability für genau diese Auswahl (best.market/pick)
  // → Brier pro Source kann nach Resolution berechnet werden
  const sourceProbForBest = (() => {
    const m = best.market, p = best.pick;
    const pickProb = (probs1X2, probOver, probBtts) => {
      if (m === '1X2') return p === '1' ? probs1X2.home : p === '2' ? probs1X2.away : probs1X2.draw;
      if (m === 'O2.5') return p === 'over' ? probOver : 1 - probOver;
      if (m === 'BTTS') return p === 'yes' ? probBtts : 1 - probBtts;
      return null;
    };
    return {
      poisson: pickProb({ home: sim.home, draw: sim.draw, away: sim.away }, sim.over[2.5], sim.btts),
      elo: elo ? pickProb({ home: elo.home, draw: elo.draw, away: elo.away }, sim.over[2.5], sim.btts) : null,
      llm: pickProb(llm1X2, Math.max(0, Math.min(1, llmOver)), llmBtts),
    };
  })();

  // Isotonic-Kalibrierung auf die Final-Confidence (wenn genug Historie)
  const calib = window.tracker?.isotonicFit?.();
  const calibratedProb = calib?.ready ? calib.calibrate(best.prob) : best.prob;
  const conf = Math.round(calibratedProb * 100);

  // Risk-Klassifizierung anhand Edge + Confidence
  const risk = best.edge > 0.10 || conf > 70 ? (best.odd < 1.8 ? 'SAFE' : 'GOOD')
             : best.edge > 0 ? 'GOOD' : 'RISK';
  const ticketWorthy = best.edge >= 0.03 && conf >= 50;

  // Auto-Log in Tracker
  if (window.tracker) {
    window.tracker.logPick({
      matchId: match.id,
      source: 'master_quant',
      market: best.market,
      pick: best.pick,
      confidence: conf,
      oddsAtPick: best.odd,
      kickoff: match.espnData?.date || null,
      label: best.label,
      meta: {
        home: match.home, away: match.away,
        edge: +best.edge.toFixed(3),
        rawProb: +best.prob.toFixed(4),
        calibratedProb: +calibratedProb.toFixed(4),
        weights: { poisson: +W.poisson.toFixed(3), elo: +W.elo.toFixed(3), llm: +W.llm.toFixed(3), learned: !!W.learned },
        sources: sourceProbForBest,
      },
    });
  }

  return {
    final_pick: best.pick,
    bet_label: best.label,
    odds: best.odd,
    confidence: conf,
    risk,
    ticket_worthy: ticketWorthy,
    quant: {
      poisson1X2: { home: +sim.home.toFixed(3), draw: +sim.draw.toFixed(3), away: +sim.away.toFixed(3) },
      elo1X2: elo ? { home: elo.home, draw: elo.draw, away: elo.away, ratings: elo.ratings } : null,
      llm1X2: { home: +llm1X2.home.toFixed(3), draw: +llm1X2.draw.toFixed(3), away: +llm1X2.away.toFixed(3) },
      blended1X2: { home: +blended1X2.home.toFixed(3), draw: +blended1X2.draw.toFixed(3), away: +blended1X2.away.toFixed(3) },
      edge: +best.edge.toFixed(3),
      method: `${Math.round(W.poisson*100)}P+${Math.round(W.elo*100)}E+${Math.round(W.llm*100)}L${W.learned?'•learned':''}`,
      weights: W,
      calibrated: calib?.ready ? { rawProb: +best.prob.toFixed(3), calibratedProb: +calibratedProb.toFixed(3) } : null,
    },
  };
}

// ─── FATIGUE + LINEUP Badges ──────────────────────────────────
function DataAgentBadges({ match }) {
  const f = match?.fatigue, l = match?.lineup, mo = match?.motivation;
  if (!f && !l && !mo) return null;
  const fatigueLevel = (s) => {
    if (!s) return null;
    if (s.midweekEuro && s.daysRest <= 3) return { txt: t('tired'), c:'#ff5470' };
    if (s.daysRest <= 2) return { txt: t('tired'), c:'#ff5470' };
    if (s.daysRest >= 6) return { txt: t('fresh'), c:'#22ff88' };
    return { txt: t('normalRest'), c:'#ffb648' };
  };
  const fH = fatigueLevel(f?.home), fA = fatigueLevel(f?.away);
  const luOk = l?.confirmed === true;
  return (
    <div style={{display:'flex',gap:5,marginTop:8,flexWrap:'wrap'}}>
      {fH && <span style={{fontSize:8,padding:'2px 7px',borderRadius:99,background:`${fH.c}12`,border:`1px solid ${fH.c}33`,color:fH.c,fontFamily:'JetBrains Mono',letterSpacing:'.04em'}}>⚡ {t('home_')} {f.home.daysRest}d · {fH.txt}{f.home.midweekEuro?' · UEL':''}</span>}
      {fA && <span style={{fontSize:8,padding:'2px 7px',borderRadius:99,background:`${fA.c}12`,border:`1px solid ${fA.c}33`,color:fA.c,fontFamily:'JetBrains Mono',letterSpacing:'.04em'}}>⚡ {t('away_')} {f.away.daysRest}d · {fA.txt}{f.away.midweekEuro?' · UEL':''}</span>}
      {l && (luOk
        ? <span style={{fontSize:8,padding:'2px 7px',borderRadius:99,background:'rgba(34,255,136,0.1)',border:'1px solid rgba(34,255,136,0.35)',color:'#22ff88',fontFamily:'JetBrains Mono',letterSpacing:'.04em'}}>✓ {t('lineupConfirmed')}</span>
        : l.status==='unavailable' || l.status==='no_espn_id'
        ? null
        : <span style={{fontSize:8,padding:'2px 7px',borderRadius:99,background:'rgba(255,255,255,0.04)',border:'1px solid rgba(255,255,255,0.1)',color:'rgba(255,255,255,0.4)',fontFamily:'JetBrains Mono',letterSpacing:'.04em'}}>⏳ {t('lineupPending')}</span>)}
      {mo?.status === 'ok' && (() => {
        const catColor = (cat) => cat?.includes('title_race') ? '#ffd700' : cat?.includes('relegation') ? '#ff5470' : cat?.includes('european') ? '#5ee7ff' : cat?.includes('dead_rubber') || cat?.includes('champion_secure') ? '#888' : cat?.includes('derby') ? '#ff0080' : '#ffb648';
        const catIcon = (cat) => cat?.includes('title_race') ? '🏆' : cat?.includes('relegation_fight') ? '🆘' : cat?.includes('relegation') ? '⚠️' : cat?.includes('european') ? '🇪🇺' : cat?.includes('dead_rubber') ? '💤' : cat?.includes('champion_secure') ? '👑' : cat?.includes('derby') ? '🔥' : '⚽';
        const cH = catColor(mo.home.category), cA = catColor(mo.away.category);
        return (
          <>
            <span style={{fontSize:8,padding:'2px 7px',borderRadius:99,background:`${cH}15`,border:`1px solid ${cH}40`,color:cH,fontFamily:'JetBrains Mono',letterSpacing:'.04em'}}>{catIcon(mo.home.category)} {t('home_')[0]} {mo.home.score}</span>
            <span style={{fontSize:8,padding:'2px 7px',borderRadius:99,background:`${cA}15`,border:`1px solid ${cA}40`,color:cA,fontFamily:'JetBrains Mono',letterSpacing:'.04em'}}>{catIcon(mo.away.category)} {t('away_')[0]} {mo.away.score}</span>
          </>
        );
      })()}
    </div>
  );
}

// ─── Poisson Quant-Modell Card ────────────────────────────────
function PoissonCard({ match }) {
  const sim = React.useMemo(() => {
    try { return poisson.simulate(match, 10000); } catch { return null; }
  }, [match?.id, match?.odds?.home, match?.odds?.away, match?.xG?.home, match?.xG?.away]);
  if (!sim) return null;
  const pct = (p) => `${(p*100).toFixed(0)}%`;
  const bestEdge = sim.bestBets?.[0];
  const edgeColor = (e) => e == null ? 'rgba(255,255,255,0.3)' : e > 0.05 ? '#22ff88' : e > 0 ? '#ffb648' : '#ff5470';
  return (
    <div style={{margin:'4px 16px 12px',padding:'12px 14px',background:'rgba(255,255,255,0.03)',border:'1px solid rgba(94,231,255,0.18)',borderRadius:14,position:'relative',overflow:'hidden'}}>
      <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:8}}>
        <div style={{fontSize:8,color:'#5ee7ff',fontFamily:'JetBrains Mono',letterSpacing:'.14em'}}>⚙ {t('poissonQuant')}</div>
        <div style={{fontSize:8,color:'rgba(255,255,255,0.4)',fontFamily:'JetBrains Mono'}}>λH={sim.lambdas.home} · λA={sim.lambdas.away} · Ø{sim.avgGoals}</div>
      </div>
      {/* 1X2 + Edge */}
      <div style={{display:'grid',gridTemplateColumns:'1fr 1fr 1fr',gap:6,marginBottom:8}}>
        {[
          {l:'1', p:sim.home, e:sim.edge?.home, fair:sim.fair.home, market:match.odds?.home},
          {l:'X', p:sim.draw, e:sim.edge?.draw, fair:sim.fair.draw, market:match.odds?.draw},
          {l:'2', p:sim.away, e:sim.edge?.away, fair:sim.fair.away, market:match.odds?.away},
        ].map(c=>(
          <div key={c.l} style={{textAlign:'center',padding:'7px 4px',borderRadius:10,background:'rgba(255,255,255,0.04)',border:`1px solid ${edgeColor(c.e)}30`}}>
            <div style={{fontSize:8,color:'rgba(255,255,255,0.4)',fontFamily:'JetBrains Mono'}}>{c.l}</div>
            <div style={{fontSize:14,fontWeight:800,fontFamily:'JetBrains Mono',color:'#fff',textShadow:`0 0 10px ${edgeColor(c.e)}88, 0 0 20px ${edgeColor(c.e)}44`}}>{pct(c.p)}</div>
            <div style={{fontSize:8,color:'rgba(255,255,255,0.4)',fontFamily:'JetBrains Mono',marginTop:1}}>fair {c.fair} · markt {c.market?.toFixed(2)}</div>
            <div style={{fontSize:9,fontWeight:700,fontFamily:'JetBrains Mono',color:edgeColor(c.e),marginTop:2,textShadow:`0 0 8px ${edgeColor(c.e)}cc`}}>
              {c.e == null ? '—' : (c.e > 0 ? '+' : '') + (c.e*100).toFixed(1) + '%'}
            </div>
          </div>
        ))}
      </div>
      {/* O2.5 + BTTS */}
      <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:6,marginBottom:bestEdge?8:0}}>
        <div style={{padding:'6px 8px',borderRadius:10,background:'rgba(168,85,247,0.06)',border:'1px solid rgba(168,85,247,0.2)'}}>
          <div style={{fontSize:8,color:'#a855f7',fontFamily:'JetBrains Mono',letterSpacing:'.1em'}}>OVER 2.5</div>
          <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline'}}>
            <span style={{fontSize:13,fontWeight:700,fontFamily:'JetBrains Mono',color:'#fff',textShadow:'0 0 8px currentColor'}}>{pct(sim.over[2.5])}</span>
            <span style={{fontSize:9,fontFamily:'JetBrains Mono',color:edgeColor(sim.edge?.o25)}}>{sim.edge?.o25 == null ? '' : (sim.edge.o25>0?'+':'') + (sim.edge.o25*100).toFixed(1)+'%'}</span>
          </div>
        </div>
        <div style={{padding:'6px 8px',borderRadius:10,background:'rgba(255,0,128,0.06)',border:'1px solid rgba(255,0,128,0.2)'}}>
          <div style={{fontSize:8,color:'#ff0080',fontFamily:'JetBrains Mono',letterSpacing:'.1em'}}>BTTS</div>
          <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline'}}>
            <span style={{fontSize:13,fontWeight:700,fontFamily:'JetBrains Mono',color:'#fff',textShadow:'0 0 8px currentColor'}}>{pct(sim.btts)}</span>
            <span style={{fontSize:9,fontFamily:'JetBrains Mono',color:edgeColor(sim.edge?.btts_y)}}>{sim.edge?.btts_y == null ? '' : (sim.edge.btts_y>0?'+':'') + (sim.edge.btts_y*100).toFixed(1)+'%'}</span>
          </div>
        </div>
      </div>
      {/* Best Bet */}
      {bestEdge && bestEdge.edge > 0 && (
        <div style={{padding:'8px 10px',borderRadius:10,background:`linear-gradient(135deg, ${edgeColor(bestEdge.edge)}15 0%, transparent 100%)`,border:`1px solid ${edgeColor(bestEdge.edge)}40`}}>
          <div style={{fontSize:8,color:edgeColor(bestEdge.edge),fontFamily:'JetBrains Mono',letterSpacing:'.12em',marginBottom:2}}>{t('valuePick')}</div>
          <div style={{display:'flex',justifyContent:'space-between',alignItems:'center'}}>
            <span style={{fontSize:11,fontWeight:600,fontFamily:'Space Grotesk',color:'rgba(255,255,255,0.9)'}}>{bestEdge.label}</span>
            <span style={{fontSize:11,fontWeight:800,fontFamily:'JetBrains Mono',color:edgeColor(bestEdge.edge)}}>{(bestEdge.edge*100).toFixed(1)}% Edge · @{bestEdge.odd?.toFixed(2)}</span>
          </div>
        </div>
      )}
    </div>
  );
}

// ─── Konfidenz Radar Chart ────────────────────────────────────
// Wrapper-Komponente: KONFIDENZ RADAR mit Header, Legende + optionalem Glow-Backdrop.
// Funktioniert in 3 Modi:
//   single   → results/statuses kommen von einem einzelnen Match (per-agent)
//   parallel → matchResults ist ein {matchId: {confidence,...}} Objekt aus Multi-Analyse
//   liga     → matchResults ist ein {matchId: {confidence,...}} Objekt aus Liga-Analyse
function RadarPanel({ mode, match, results, statuses, running, masterDone,
                     matchResults, totalCount, size = 260 }) {
  const isAggregate = mode === 'parallel' || mode === 'liga';
  const radarAgents = getRadarAgents();
  const { results: radarResults, statuses: radarStatuses } = isAggregate
    ? getRadarAggregateResults(matchResults, running)
    : getRadarResults(match, results, statuses);

  const doneCount = isAggregate ? Object.keys(matchResults || {}).length : 0;
  const headerLabel = isAggregate
    ? `⬡ KONFIDENZ RADAR · 18 KNOTEN · ${doneCount}/${totalCount || 0} MATCHES`
    : '⬡ KONFIDENZ RADAR · 18 KNOTEN';

  const glowColor = masterDone ? '#22ff88' : '#5ee7ff';

  return (
    <div style={{padding:'10px 14px 14px',position:'relative',overflow:'hidden'}}>
      {running && (
        <div style={{position:'absolute',inset:0,background:`radial-gradient(circle at 50% 50%, ${glowColor}10 0%, transparent 60%)`,animation:'rimGlow 2s ease-in-out infinite',pointerEvents:'none'}}/>
      )}
      <div style={{position:'relative',zIndex:1}}>
        <div style={{display:'flex',alignItems:'center',justifyContent:'center',gap:8,marginBottom:6,flexWrap:'wrap'}}>
          <div style={{fontSize:8,color:'rgba(255,255,255,0.35)',fontFamily:'JetBrains Mono',letterSpacing:'.14em'}}>{headerLabel}</div>
          {running && <div style={{fontSize:8,color:'#22ff88',fontFamily:'JetBrains Mono',letterSpacing:'.12em',padding:'1px 6px',borderRadius:99,background:'rgba(34,255,136,0.1)',border:'1px solid rgba(34,255,136,0.3)',animation:'rimGlow 1.4s ease-in-out infinite'}}>● LIVE</div>}
        </div>
        <KonfidenzRadar agents={radarAgents} results={radarResults} statuses={radarStatuses} running={running} size={size}/>
        <div style={{display:'flex',justifyContent:'center',gap:14,marginTop:6,fontSize:8,fontFamily:'JetBrains Mono',color:'rgba(255,255,255,0.4)',letterSpacing:'.08em'}}>
          <span style={{display:'flex',alignItems:'center',gap:3}}><svg width="8" height="8"><polygon points="4,0 8,4 4,8 0,4" fill="#ffd700"/></svg>DATA·4</span>
          <span style={{display:'flex',alignItems:'center',gap:3}}><svg width="8" height="8"><circle cx="4" cy="4" r="3.5" fill="#5ee7ff"/></svg>LLM·10</span>
          <span style={{display:'flex',alignItems:'center',gap:3}}><svg width="9" height="8"><polygon points="0,4 2.25,0.5 6.75,0.5 9,4 6.75,7.5 2.25,7.5" fill="#ffb648"/></svg>ENS·2</span>
          <span style={{display:'flex',alignItems:'center',gap:3}}><svg width="8" height="8"><rect x="0.5" y="0.5" width="7" height="7" fill="#a855f7"/></svg>QUANT·2</span>
        </div>
      </div>
    </div>
  );
}

function KonfidenzRadar({ agents, results, statuses = {}, running = false, size = 260 }) {
  // Wave-arrival ripple effect
  const [waveRipple, setWaveRipple] = React.useState(0);
  const prevDoneRef = React.useRef(0);
  React.useEffect(() => {
    const llmAgents = agents.filter(a => a.type !== 'data' && a.type !== 'quant');
    const done = llmAgents.filter(a => statuses[a.id] === 'done').length;
    if (done > prevDoneRef.current) setWaveRipple(t => t + 1);
    prevDoneRef.current = done;
  }, [statuses]);
  const cx = size / 2, cy = size / 2;
  const maxR = size / 2 - 28;
  const n = agents.length;
  const rings = [25, 50, 75, 100];

  const angleOf = i => (i / n) * Math.PI * 2 - Math.PI / 2;
  const pointOf = (i, r) => {
    const a = angleOf(i);
    return { x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r };
  };

  // Build polygon path for agent confidences
  const confPoints = agents.map((ag, i) => {
    const r = results[ag.id];
    const conf = r ? (r.confidence || 60) : 0;
    const radius = (conf / 100) * maxR;
    return pointOf(i, radius);
  });

  const polyPath = confPoints.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x.toFixed(1)} ${p.y.toFixed(1)}`).join(' ') + ' Z';

  // Gradient fill stops based on average confidence
  const avgConf = agents.reduce((a, ag) => a + ((results[ag.id]?.confidence || 0)), 0) / agents.length;
  const fillColor = avgConf >= 75 ? '#00ff88' : avgConf >= 60 ? '#ffa500' : '#ff5470';

  return (
    <div style={{ position:'relative', width:size, height:size, margin:'0 auto' }}>
      <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{ overflow:'visible' }}>
        <defs>
          <radialGradient id="radarFill" cx="50%" cy="50%" r="50%">
            <stop offset="0%"   stopColor={fillColor} stopOpacity="0.10"/>
            <stop offset="70%"  stopColor={fillColor} stopOpacity="0.04"/>
            <stop offset="100%" stopColor={fillColor} stopOpacity="0"/>
          </radialGradient>
          <filter id="radarGlow">
            <feGaussianBlur stdDeviation="2" result="blur"/>
            <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
          </filter>
        </defs>

        {/* Concentric rings */}
        {rings.map(pct => {
          const r = (pct / 100) * maxR;
          const pts = agents.map((_, i) => pointOf(i, r));
          const path = pts.map((p, i) => `${i===0?'M':'L'} ${p.x.toFixed(1)} ${p.y.toFixed(1)}`).join(' ') + ' Z';
          return (
            <g key={pct}>
              <path d={path} fill="none" stroke="rgba(255,255,255,0.07)" strokeWidth="1"/>
              <text x={cx} y={cy - r - 3} textAnchor="middle" fontSize="8" fill="rgba(255,255,255,0.2)" fontFamily="JetBrains Mono">{pct}%</text>
            </g>
          );
        })}

        {/* Axis lines */}
        {agents.map((ag, i) => {
          const outer = pointOf(i, maxR);
          return <line key={ag.id} x1={cx} y1={cy} x2={outer.x} y2={outer.y} stroke="rgba(255,255,255,0.08)" strokeWidth="1"/>;
        })}

        {/* Confidence polygon — sequentially drawn as LLM agents complete */}
        {avgConf > 0 && (() => {
          const llmAgents = agents.filter(a => a.type !== 'data' && a.type !== 'quant');
          const llmDone = llmAgents.filter(a => statuses[a.id] === 'done').length;
          const llmTotal = Math.max(llmAgents.length, 1);
          const progress = running ? (llmDone / llmTotal) : 1;
          return (
            <path d={polyPath} fill="url(#radarFill)" stroke={fillColor} strokeWidth="1.4"
              strokeLinejoin="round" strokeOpacity="0.9"
              pathLength="1" strokeDasharray="1" strokeDashoffset={1 - progress}
              style={{
                transition: 'stroke-dashoffset 0.6s cubic-bezier(0.22,1,0.36,1)',
                filter: `drop-shadow(0 0 ${4 + avgConf*0.08}px ${fillColor}${Math.round(avgConf*0.6).toString(16).padStart(2,'0')})`,
              }}/>
          );
        })()}

        {/* Agent dots + labels */}
        {agents.map((ag, i) => {
          const outer = pointOf(i, maxR + 14);
          const dot = pointOf(i, maxR);
          const r = results[ag.id];
          const conf = r ? (r.confidence || 0) : 0;
          const confPt = pointOf(i, (conf/100)*maxR);
          const isTop = i === agents.reduce((best, a2, j) =>
            (results[a2.id]?.confidence||0) > (results[agents[best].id]?.confidence||0) ? j : best, 0);

          // Shape je Knoten-Typ
          const shape = ag.type === 'quant' ? 'square' : ag.type === 'data' ? 'diamond' : ag.type === 'ensemble' ? 'hexagon' : 'circle';
          const renderShape = (cx_, cy_, r_, fill, extraStyle = {}) => {
            if (shape === 'square') {
              return <rect x={cx_-r_} y={cy_-r_} width={r_*2} height={r_*2} fill={fill} style={extraStyle}/>;
            }
            if (shape === 'diamond') {
              return <polygon points={`${cx_},${cy_-r_} ${cx_+r_},${cy_} ${cx_},${cy_+r_} ${cx_-r_},${cy_}`} fill={fill} style={extraStyle}/>;
            }
            if (shape === 'hexagon') {
              const h = r_ * Math.sqrt(3) / 2;
              return <polygon points={`${cx_-r_},${cy_} ${cx_-r_/2},${cy_-h} ${cx_+r_/2},${cy_-h} ${cx_+r_},${cy_} ${cx_+r_/2},${cy_+h} ${cx_-r_/2},${cy_+h}`} fill={fill} style={extraStyle}/>;
            }
            return <circle cx={cx_} cy={cy_} r={r_} fill={fill} style={extraStyle}/>;
          };
          const isRunning = statuses[ag.id] === 'running';
          const isDone = statuses[ag.id] === 'done';
          const dataQ = r?.dataQuality;
          return (
            <g key={ag.id}>
              {/* Axis line gets glow if confidence > 0 */}
              {conf > 0 && (
                <line x1={cx} y1={cy} x2={dot.x} y2={dot.y}
                  stroke={ag.color} strokeWidth="0.8"
                  opacity={Math.min(0.5, conf/200)}/>
              )}
              {/* Axis endpoint dot (small) */}
              {renderShape(dot.x, dot.y, 2, ag.color, { opacity: 0.4 })}
              {/* Spinner ring on RUNNING nodes (LLM in progress, or data still fetching) */}
              {isRunning && (
                <g style={{ transformOrigin: `${dot.x}px ${dot.y}px`, animation: 'spin 1s linear infinite' }}>
                  <circle cx={dot.x} cy={dot.y} r="9" fill="none" stroke={ag.color} strokeWidth="1.5"
                    strokeDasharray="14 28" strokeLinecap="round" opacity="0.9"/>
                </g>
              )}
              {/* Confidence shape — animated entrance */}
              {conf > 0 && renderShape(confPt.x, confPt.y, isTop ? 5.5 : 3.8, ag.color, {
                filter: `url(#radarGlow) drop-shadow(0 0 ${conf*0.06}px ${ag.color})`,
                animation: `agentPulse ${1.5 + i*0.13}s ease-in-out infinite`,
                opacity: dataQ === 'unavailable' ? 0.35 : dataQ === 'partial' ? 0.7 : 1,
              })}
              {/* "Done" checkmark for LLM nodes that completed */}
              {isDone && ag.type !== 'data' && ag.type !== 'quant' && conf > 0 && (
                <circle cx={confPt.x + 5} cy={confPt.y - 5} r="2" fill="#22ff88" opacity="0.9"
                  style={{ animation: 'fadeUp 0.4s ease' }}/>
              )}
              {/* Label */}
              <text x={outer.x} y={outer.y + 3} textAnchor="middle" fontSize="6.5"
                fill={conf > 0 ? ag.color : 'rgba(255,255,255,0.25)'}
                fontFamily="JetBrains Mono" fontWeight="700"
                opacity={dataQ === 'unavailable' ? 0.4 : 1}>
                {ag.name}
              </text>
              {conf > 0 && (
                <text x={outer.x} y={outer.y + 11} textAnchor="middle" fontSize="6"
                  fill={dataQ === 'unavailable' ? 'rgba(255,255,255,0.3)' : 'rgba(255,255,255,0.6)'}
                  fontFamily="JetBrains Mono">
                  {dataQ === 'unavailable' ? '— ' : ''}{conf}%
                </text>
              )}
            </g>
          );
        })}

        {/* Wave-arrival ripple — fires once per agent completion */}
        {waveRipple > 0 && (
          <circle key={`ripple_${waveRipple}`} cx={cx} cy={cy} r="0" fill="none"
            stroke={fillColor} strokeWidth="2" opacity="0.85"
            style={{ animation: 'waveRipple 0.9s ease-out forwards' }}/>
        )}

        {/* Center dot */}
        <circle cx={cx} cy={cy} r="3" fill={fillColor} opacity="0.6"/>

        {/* === ANALYSIS-MODE EFFECTS === */}
        {running && (() => {
          const runningIdx = agents.findIndex(a => statuses[a.id] === 'running');
          const doneIdxs = agents.map((a,i) => statuses[a.id]==='done'?i:null).filter(v=>v!==null);
          // Permanent-Pulse für Quant-, Data-, Ensemble-Knoten (alle nicht-LLM-Worker)
          const permanentIdxs = agents.map((a,i) => (a.type==='quant' || a.type==='data' || a.type==='ensemble')?i:null).filter(v=>v!==null);
          return (
            <>
              {/* Rotating sweep line (radar-style) */}
              <g style={{ transformOrigin:`${cx}px ${cy}px`, animation:'spin 3s linear infinite' }}>
                <defs>
                  <linearGradient id="sweepGrad" x1="0" y1="0" x2="1" y2="0">
                    <stop offset="0%"  stopColor={fillColor} stopOpacity="0"/>
                    <stop offset="100%" stopColor={fillColor} stopOpacity="0.55"/>
                  </linearGradient>
                </defs>
                <path d={`M ${cx} ${cy} L ${cx + maxR} ${cy} A ${maxR} ${maxR} 0 0 0 ${cx + Math.cos(-Math.PI/3.5)*maxR} ${cy + Math.sin(-Math.PI/3.5)*maxR} Z`} fill="url(#sweepGrad)" opacity="0.55"/>
              </g>
              {/* Pulsing center node */}
              <circle cx={cx} cy={cy} r="6" fill="none" stroke={fillColor} strokeWidth="1.5" style={{ animation:'livePing 1.4s ease-out infinite' }}/>
              <circle cx={cx} cy={cy} r="6" fill="none" stroke={fillColor} strokeWidth="1.2" style={{ animation:'livePing 1.4s ease-out infinite 0.7s' }}/>
              {/* Permanent halo on data + quant nodes (always-on signal) */}
              {permanentIdxs.map(i => {
                const ag = agents[i];
                const r = results[ag.id]?.confidence || 0;
                if (r === 0) return null;
                const p = pointOf(i, (r/100) * maxR);
                return <circle key={`perm_${i}`} cx={p.x} cy={p.y} r="6" fill="none" stroke={ag.color} strokeWidth="1" opacity="0.5" style={{animation:`livePing ${1.8 + (i%3)*0.3}s ease-out infinite ${(i%5)*0.2}s`}}/>;
              })}
              {/* Trail to all completed agents */}
              {doneIdxs.map(i => {
                const p = pointOf(i, maxR * 0.92);
                return <line key={`trail_${i}`} x1={cx} y1={cy} x2={p.x} y2={p.y} stroke={agents[i].color} strokeWidth="1" opacity="0.25" strokeDasharray="2,3"/>;
              })}
              {/* Active agent: traveling particle from center to its outer dot */}
              {runningIdx >= 0 && (() => {
                const target = pointOf(runningIdx, maxR * 0.95);
                const ag = agents[runningIdx];
                return (
                  <g>
                    {/* Beam */}
                    <line x1={cx} y1={cy} x2={target.x} y2={target.y}
                      stroke={ag.color} strokeWidth="1.5" opacity="0.5"
                      style={{ animation:'rimGlow 0.9s ease-in-out infinite', filter:'url(#radarGlow)' }}/>
                    {/* Particle dot moving along the beam */}
                    <circle r="3.5" fill={ag.color} filter="url(#radarGlow)">
                      <animate attributeName="cx" from={cx} to={target.x} dur="1.1s" repeatCount="indefinite"/>
                      <animate attributeName="cy" from={cy} to={target.y} dur="1.1s" repeatCount="indefinite"/>
                      <animate attributeName="opacity" values="0;1;1;0" keyTimes="0;0.15;0.85;1" dur="1.1s" repeatCount="indefinite"/>
                    </circle>
                    {/* Secondary trailing particle */}
                    <circle r="2" fill="#ffffff" opacity="0.85">
                      <animate attributeName="cx" from={cx} to={target.x} dur="1.1s" begin="0.25s" repeatCount="indefinite"/>
                      <animate attributeName="cy" from={cy} to={target.y} dur="1.1s" begin="0.25s" repeatCount="indefinite"/>
                      <animate attributeName="opacity" values="0;0.9;0" keyTimes="0;0.5;1" dur="1.1s" begin="0.25s" repeatCount="indefinite"/>
                    </circle>
                    {/* Halo at active agent */}
                    <circle cx={target.x} cy={target.y} r="9" fill="none" stroke={ag.color} strokeWidth="1.5" opacity="0.6"
                      style={{ animation:'livePing 1.2s ease-out infinite' }}/>
                    <circle cx={target.x} cy={target.y} r="14" fill="none" stroke={ag.color} strokeWidth="1" opacity="0.35"
                      style={{ animation:'livePing 1.6s ease-out infinite 0.4s' }}/>
                  </g>
                );
              })()}
            </>
          );
        })()}

        {/* Average confidence label */}
        {avgConf > 0 && (
          <text x={cx} y={cy + 14} textAnchor="middle" fontSize="10"
            fill={fillColor} fontFamily="JetBrains Mono" fontWeight="800">
            Ø {Math.round(avgConf)}%
          </text>
        )}
      </svg>
    </div>
  );
}

Object.assign(window, { AgentPipeline, GlowBorder, LigaAnalyseScreen, KonfidenzRadar });
