const { useEffect, useMemo, useRef, useState } = React;


const clamp = (v,min,max)=>Math.max(min, Math.min(max,v));
const iso = (d=new Date())=> new Date(d).toISOString();
const fmt = (d)=> iso(d).slice(0,19).replace('T',' ');
const uid = (p='id')=> `${p}-${Math.random().toString(36).slice(2,8)}${Date.now().toString(36).slice(-4)}`;

function downloadFile(filename, text, type="text/plain"){
  const blob = new Blob([text], {type}); const url = URL.createObjectURL(blob);
  const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url);
}


const STORAGE_KEY = 'regulus_state_v3'; 
const LICENSED_WINDOWS_UTC = [
  { start:"00:00", end:"00:20" }, { start:"06:00", end:"06:20" },
  { start:"12:00", end:"12:20" }, { start:"18:00", end:"18:20" },
];
const inWindowUTC = (date=new Date())=>{
  const hh=String(date.getUTCHours()).padStart(2,'0');
  const mm=String(date.getUTCMinutes()).padStart(2,'0');
  const t=`${hh}:${mm}`;
  return LICENSED_WINDOWS_UTC.some(w=>w.start<=t && t<=w.end);
};

function ensureMetricsShape(s){
 
  if(!s.metrics) s.metrics = {};
  if(!s.metrics.ground_truth) s.metrics.ground_truth = []; 
  if(!s.metrics.detections) s.metrics.detections = [];     
  if(!s.metrics.availability) s.metrics.availability = {}; 
  if(!s.metrics.autonomous) s.metrics.autonomous = { cases: 0, successes: 0 }; 
  return s;
}

function loadState(){
  const raw = localStorage.getItem(STORAGE_KEY);
  if (raw){
    try {
      const parsed = JSON.parse(raw);
      return ensureMetricsShape(parsed);
    } catch {}
  }
  const seed = {
    sats: [
      { id:'SAT-001', name:'ISS-Demo', orbit:'LEO', status:'Healthy', signal:98, packets:1200, last_seen:iso(Date.now()-3_600_000),
        golden_hash:'sha256:9f2c…7a1', attested_at: iso(Date.now()-3_600_000),
        quarantine:false, allow_list_only:false, anomalies_24h:0, eol_reserve_mps:15, eol_min_mps:10
      },
      { id:'SAT-002', name:'Starlink-Demo', orbit:'LEO', status:'Alert', signal:42, packets:300, last_seen:iso(Date.now()-200_000),
        golden_hash:'sha256:1ab3…fe5', attested_at: iso(Date.now()-5*3_600_000),
        quarantine:true, allow_list_only:true, anomalies_24h:2, eol_reserve_mps:11, eol_min_mps:10
      },
      { id:'SAT-003', name:'OneWeb-Demo', orbit:'LEO', status:'Healthy', signal:88, packets:980, last_seen:iso(Date.now()-500_000),
        golden_hash:'sha256:7e44…c01', attested_at: iso(Date.now()-2*3_600_000),
        quarantine:false, allow_list_only:false, anomalies_24h:0, eol_reserve_mps:20, eol_min_mps:10
      },
    ],
    alerts: [
      { id:uid('al'), sat_id:'SAT-002', type:'Spoofing', time:iso(Date.now()-60_000), severity:'High', resolved:false },
    ],
    cdms: [
      { id:'CDM-1001', sat_id:'SAT-002', tca:iso(Date.now()+18*3_600_000), miss_distance_m:650, rel_speed_kms:12.4, prob:1.2e-4 },
      { id:'CDM-1002', sat_id:'SAT-001', tca:iso(Date.now()+36*3_600_000), miss_distance_m:1800, rel_speed_kms:10.1, prob:2.5e-5 },
    ],
    spectrum_log: [],
    metrics: {
      ground_truth: [], detections: [], availability: {}, autonomous: {cases:0, successes:0}
    }
  };
  // init availability map
  seed.sats.forEach(s=>{ seed.metrics.availability[s.id] = { up_ms:0, down_ms:0, lastStamp: Date.now(), lastUp: true }; });
  localStorage.setItem(STORAGE_KEY, JSON.stringify(seed));
  return seed;
}
function saveState(state){ localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); }


const DETECTOR = { PROB: 0.82, MIN_LAT_MS: 800, MAX_LAT_MS: 5000, MATCH_WINDOW_MS: 10*60*1000 };

function computeDetectionStats(s){
  const types = new Set(['Spoofing','Jamming']);
  const gts = s.metrics.ground_truth.filter(g=>types.has(g.type));
  const dets = s.metrics.detections.filter(d=>types.has(d.type));


  const unmatchedGT = new Set(gts.map(g=>g.id));
  let TP=0, FP=0, FN=0; let latencies=[];

  
  const byKey = {};
  gts.sort((a,b)=>new Date(a.occurredAt)-new Date(b.occurredAt)).forEach(g=>{
    const k = `${g.sat_id}|${g.type}`; (byKey[k] ||= []).push(g);
  });

  dets.sort((a,b)=>new Date(a.time)-new Date(b.time)).forEach(d=>{
    const k = `${d.sat_id}|${d.type}`; const arr = byKey[k]||[];
    let matched=null;
    for(const g of arr){
      if(!unmatchedGT.has(g.id)) continue;
      const dt = new Date(d.time) - new Date(g.occurredAt);
      if(dt>=0 && dt<=DETECTOR.MATCH_WINDOW_MS){ matched=g; break; }
    }
    if(matched){
      unmatchedGT.delete(matched.id); TP++; latencies.push(new Date(d.time)-new Date(matched.occurredAt));
    } else { FP++; }
  });
  
  const now = Date.now();
  for(const g of gts){
    if(unmatchedGT.has(g.id) && (now - new Date(g.occurredAt).getTime())>DETECTOR.MATCH_WINDOW_MS){ FN++; }
  }
  const denom = TP+FP+FN;
  const accuracy = denom? Math.round((TP/denom)*100) : 0;
  const avgLatencyMs = latencies.length? Math.round(latencies.reduce((a,b)=>a+b,0)/latencies.length) : 0;
  return { TP, FP, FN, accuracy, avgLatencyMs };
}

function computeAutonomousMitigation(s){

  const blocks = s.spectrum_log.filter(r=>r.action==='TX_ENABLE' && r.allowed===false);
  const total = s.spectrum_log.filter(r=>r.action==='TX_ENABLE').length;
 
  const extraCases = s.metrics.autonomous.cases; const extraSucc = s.metrics.autonomous.successes;
  const cases = total + extraCases; const succ = blocks.length + extraSucc;
  const rate = cases? Math.round((succ/cases)*100):0;
  return { cases, succ, rate };
}

function computeAvailability(s){
 
  const entries = Object.values(s.metrics.availability);
  if(entries.length===0) return { avgPct: 0 };
  const avgs = entries.map(e=>{
    const up = e.up_ms; const down = e.down_ms; const tot = up+down; return tot? (up/tot):1;
  });
  const avgPct = Math.round((avgs.reduce((a,b)=>a+b,0)/avgs.length)*100);
  return { avgPct };
}


const api = {
  listSats(){ const s=loadState(); return s.sats; },
  listAlerts(){ const s=loadState(); return s.alerts.sort((a,b)=>new Date(b.time)-new Date(a.time)); },
  listCdms(){ const s=loadState(); return s.cdms.sort((a,b)=>new Date(a.tca)-new Date(b.tca)); },
  listSpectrumLog(){ return loadState().spectrum_log; },

  attemptTx(sat_id, reason='Manual downlink'){
    const s = ensureMetricsShape(loadState()); const allowed = inWindowUTC();
    s.spectrum_log.unshift({ time: iso(), sat_id, action:'TX_ENABLE', allowed, reason: allowed?'Inside licensed window':'Outside licensed window' });
    if(!allowed){ s.alerts.unshift({ id:uid('al'), sat_id, type:'SpectrumViolation', time:iso(), severity:'Medium', resolved:false }); }
    saveState(s);
    return { allowed, windowsUTC: LICENSED_WINDOWS_UTC };
  },

  toggleQuarantine(sat_id){
    const s=ensureMetricsShape(loadState());
    s.sats = s.sats.map(x => x.id===sat_id ? { ...x, quarantine: !x.quarantine, allow_list_only: !x.allow_list_only, status: !x.quarantine? 'Alert': 'Healthy' } : x);
    s.alerts.unshift({ id:uid('al'), sat_id, type: s.sats.find(x=>x.id===sat_id).quarantine?'QuarantineOn':'QuarantineOff', time:iso(), severity: s.sats.find(x=>x.id===sat_id).quarantine?'High':'Low', resolved:false });
    saveState(s);
    return s.sats.find(x=>x.id===sat_id);
  },

  spendDV(sat_id, delta_v){
    const s=ensureMetricsShape(loadState());
    const sat = s.sats.find(x=>x.id===sat_id);
    sat.eol_reserve_mps = Math.max(0, +(sat.eol_reserve_mps - delta_v).toFixed(1));
    let policy_breach = false;
    if (sat.eol_reserve_mps < sat.eol_min_mps){
      policy_breach = true;
      s.alerts.unshift({ id:uid('al'), sat_id, type:'EOL_Reserve_Breached', time:iso(), severity:'High', resolved:false });
    }
    saveState(s);
    return { policy_breach, sat };
  },

  rotateKeysAndAttest(sat_id){
    const s=ensureMetricsShape(loadState()); const sat = s.sats.find(x=>x.id===sat_id);
    sat.attested_at = iso(); 
    s.alerts.unshift({ id:uid('al'), sat_id, type:'KeyRotation', time:iso(), severity:'Low', resolved:true });
    saveState(s); return sat;
  },

  registerSat({id,name,orbit='LEO',eol_reserve_mps=12,eol_min_mps=10}){
    const s=ensureMetricsShape(loadState());
    if (s.sats.some(x=>x.id===id)) return { ok:false, error:'ID exists' };
    s.sats.unshift({ id, name, orbit, status:'Healthy', signal:95, packets:0, last_seen:iso(), golden_hash:'sha256:xxxx…', attested_at:iso(), quarantine:false, allow_list_only:false, anomalies_24h:0, eol_reserve_mps, eol_min_mps });
   
    s.metrics.availability[id] = { up_ms:0, down_ms:0, lastStamp: Date.now(), lastUp: true };
    saveState(s); return { ok:true };
  },

  deleteSat(sat_id){
  const s = ensureMetricsShape(loadState());
  s.sats = s.sats.filter(x => x.id !== sat_id);
  if (s.metrics?.availability) delete s.metrics.availability[sat_id];
  s.alerts = (s.alerts || []).filter(a => a.sat_id !== sat_id);
  s.cdms = (s.cdms || []).filter(c => c.sat_id !== sat_id);
  s.spectrum_log = (s.spectrum_log || []).filter(r => r.sat_id !== sat_id);
  saveState(s);
  return true;
},

 
  injectThreat({sat_id, type='Spoofing'}){
    const s=ensureMetricsShape(loadState());
    const gt = { id: uid('gt'), sat_id, type, occurredAt: iso() };
    s.metrics.ground_truth.push(gt);
    
    s.alerts.unshift({ id:uid('al'), sat_id, type:`${type}_Occurred`, time:gt.occurredAt, severity:'High', resolved:false });
    saveState(s);

   
    const willDetect = Math.random() < DETECTOR.PROB;
    if(willDetect){
      const latency = Math.round(DETECTOR.MIN_LAT_MS + Math.random()*(DETECTOR.MAX_LAT_MS - DETECTOR.MIN_LAT_MS));
      setTimeout(()=>{ api.recordDetection({sat_id, type}); }, latency);
    }
    return gt;
  },

  recordDetection({sat_id, type='Spoofing'}){
    const s=ensureMetricsShape(loadState());
    
    const nowIso = iso();
    const windowStart = Date.now() - DETECTOR.MATCH_WINDOW_MS;
    const gt = s.metrics.ground_truth
      .filter(g=>g.sat_id===sat_id && g.type===type && !s.metrics.detections.some(d=>d.matched_gt_id===g.id))
      .filter(g=> new Date(g.occurredAt).getTime() >= windowStart)
      .sort((a,b)=>new Date(a.occurredAt)-new Date(b.occurredAt))[0] || null;

    const det = { id:uid('det'), sat_id, type, time: nowIso, matched_gt_id: gt? gt.id: undefined };
    s.metrics.detections.push(det);
    
    s.alerts.unshift({ id:uid('al'), sat_id, type, time:nowIso, severity:'High', resolved:false });
    saveState(s);
    return det;
  },

  
  getKPIs(){
    const s = ensureMetricsShape(loadState());
    const det = computeDetectionStats(s);
    const auto = computeAutonomousMitigation(s);
    const avail = computeAvailability(s);
    return {
      detection_accuracy: det.accuracy, // %
      anomaly_detection_latency_ms: det.avgLatencyMs,
      autonomous_mitigation_success_rate: auto.rate, // %
      availability_pct: avail.avgPct // %
    };
  },
};


const DONKI_SEEN_KEY = 'regulus_donki_seen_v1';
const DONKI_API_KEY = 'rQhjlfex0wNwftqWXhhV5fUF6c5MTp68rnAowwBc'; 
function markDonkiSeen(ids){
  const raw = localStorage.getItem(DONKI_SEEN_KEY);
  const set = raw ? new Set(JSON.parse(raw)) : new Set();
  ids.forEach(id=>set.add(id));
  localStorage.setItem(DONKI_SEEN_KEY, JSON.stringify(Array.from(set)));
}
function getDonkiSeen(){
  const raw = localStorage.getItem(DONKI_SEEN_KEY);
  return raw ? new Set(JSON.parse(raw)) : new Set();
}

async function fetchDonkiEvents({ startDate, endDate, apiKey = DONKI_API_KEY, mapToSats = (event)=>[] } = {}) {
  try {
    const endpoints = ['CME','FLR','GST']; 
    const seen = getDonkiSeen();
    const newSeenIds = [];

    for(const ep of endpoints){
      const url = `https://api.nasa.gov/DONKI/${ep}?startDate=${startDate}&endDate=${endDate}&api_key=${apiKey}`;
      try {
        const res = await fetch(url);
        if(!res.ok){
          console.warn(`DONKI ${ep} fetch failed:`, res.status);
          continue;
        }
        const arr = await res.json();
        if(!Array.isArray(arr)) continue;

        for(const ev of arr){
          const evId = ev.activityID || ev.eventID || ev.cmeID || ev.flrID || ev.messageID || JSON.stringify(ev);
          if(!evId || seen.has(evId)) continue; 

          const evTime = ev.startTime || ev.beginTime || ev.time || ev.eventTime || ev.messageTime || ev.observedTime || new Date().toISOString();
          const summary = ev.note || ev.instruments?.join(',') || ev.description || ev.message || (evId+' (DONKI)');

          let type = 'Jamming';
          if(ep === 'FLR') type = 'Spoofing';
          if(ep === 'CME') type = 'Jamming';
          if(ep === 'GST') type = 'Jamming';

          // map event to sats
          const affectedSats = mapToSats(ev) || [];
          if(affectedSats.length === 0){
            const s = loadState();
            affectedSats.push(...s.sats.filter(x=>x.orbit==='LEO').map(x=>x.id)); 
          }

          affectedSats.forEach(sat_id=>{
            api.injectThreat({ sat_id, type });
            const s2 = ensureMetricsShape(loadState());
            s2.alerts.unshift({ id: uid('al'), sat_id, type: `${ep}_DONKI`, time: evTime, severity:'Medium', resolved:false, note: summary });
            saveState(s2);
          });

          newSeenIds.push(evId);
        }
      } catch(err){
        console.warn('DONKI fetch error for', ep, err);
      }
    }

    markDonkiSeen(newSeenIds);
    try { window.dispatchEvent(new Event('donki-updated')); } catch(e){}
    return true;
  } catch(err){
    console.error('fetchDonkiEvents error', err);
    return false;
  }
}


function debrisRiskForSat(s, upcomingCdm){
  const control = s.quarantine ? 25 : (s.status==='Alert' ? 15 : 5);
  const anomalies = clamp((s.anomalies_24h||0)*6,0,24);
  let conjunction = 0;
  if (upcomingCdm){
    const hoursToTca = (new Date(upcomingCdm.tca)-Date.now())/3_600_000;
    const timeFactor = hoursToTca<=24 ? (24-hoursToTca)/24 : 0;
    const missFactor = upcomingCdm.miss_distance_m<1000 ? 1 : (upcomingCdm.miss_distance_m<2000?0.4:0.1);
    conjunction = clamp(30*timeFactor*missFactor,0,30);
  }
  const eolMargin = s.eol_reserve_mps<s.eol_min_mps ? 20 : ((s.eol_reserve_mps-s.eol_min_mps)<3?10:2);
  return clamp(Math.round(control+anomalies+conjunction+eolMargin),0,100);
}


function Badge({children,color='gray'}){
  const cls={
    gray:'bg-gray-200 text-gray-800 dark:bg-gray-700 dark:text-gray-100',
    green:'bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-200',
    red:'bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-200',
    yellow:'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/40 dark:text-yellow-100',
    blue:'bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-100'
  }[color]; return <span className={`px-2 py-1 rounded-full text-xs font-semibold ${cls}`}>{children}</span>;
}

function TelemetryChart({ sat, dark }) {
  const n = 30;
  const ptsSignal = Array.from({length:n}, (_,i)=> ({
    x: (i/(n-1))*100,
    y: 100 - clamp(sat.signal + (Math.random()*10-5), 10, 100)
  }));
  const ptsPackets = Array.from({length:n}, (_,i)=> ({
    x: (i/(n-1))*100,
    y: 100 - clamp((sat.packets%200)/2 + Math.random()*10, 0, 100)
  }));
  const path = (pts)=> pts.map((p,i)=> `${i?'L':'M'} ${p.x},${p.y}`).join(' ');

  const bgFill   = dark ? '#0f172a' : '#ffffff'; 
  const grid     = dark ? '#334155' : '#e5e7eb';  

  return (
    <svg viewBox="0 0 100 100" preserveAspectRatio="none" className="w-full h-40">
      <rect x="0" y="0" width="100" height="100" fill={bgFill} />
      {[20,40,60,80].map(y=>(
        <line key={y} x1="0" y1={y} x2="100" y2={y} stroke={grid} strokeWidth="0.5" />
      ))}
      
      <path d={path(ptsPackets)} fill="none" stroke="#E43700" strokeWidth="1.5" />
     
      <path d={path(ptsSignal)} fill="none" stroke="#2E96F5" strokeWidth="1.5" />
    </svg>
  );
}


function App() {
 
  const [dark, setDark] = React.useState(() => {
    const saved = localStorage.getItem("regulus_theme");
    return saved ? saved === "dark" : true;
  });
  React.useEffect(() => {
    document.documentElement.classList.toggle("dark", dark);
    localStorage.setItem("regulus_theme", dark ? "dark" : "light");
  }, [dark]);

  
  React.useEffect(()=> {
    let mounted = true;
    const key = DONKI_API_KEY; 
    const pollMinutes = 5;


    const mapToSats = (ev) => {
      
      const cur = loadState();
      return cur.sats.filter(s=>s.orbit === 'LEO').map(s=>s.id);
    };

    (async ()=>{
      const start = new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString().slice(0,10);      const end = new Date().toISOString().slice(0,10);
      await fetchDonkiEvents({ startDate: start, endDate: end, apiKey: key, mapToSats });
      if(mounted) { /* trigger UI refresh */ window.dispatchEvent(new Event('donki-updated')); }
    })();

    const id = setInterval(async ()=>{
      const start = new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString().slice(0,10);      const end = new Date().toISOString().slice(0,10);
      await fetchDonkiEvents({ startDate: start, endDate: end, apiKey: key, mapToSats });
      if(mounted) { window.dispatchEvent(new Event('donki-updated')); }
    }, pollMinutes * 60_000);

    const onDonki = ()=>{ /* noop here; App will sync using existing intervals elsewhere */ };
    window.addEventListener('donki-updated', onDonki);

    return ()=>{ mounted=false; clearInterval(id); window.removeEventListener('donki-updated', onDonki); };
  }, []); 

  const [sats,setSats]=useState(api.listSats());
  const [alerts,setAlerts]=useState(api.listAlerts());
  const [cdms,setCdms]=useState(api.listCdms());
  const [view,setView]=useState('dashboard'); 
  const [selected,setSelected]=useState(sats[0]||null);
  const [txCounters,setTxCounters]=useState({blocked:0,allowed:0});
  const tickRef = useRef(null);

  
  function updateAvailabilityTracker(){
    const s = loadState(); ensureMetricsShape(s);
    const now = Date.now();
    s.sats.forEach(sa=>{
      const av = s.metrics.availability[sa.id] || { up_ms:0, down_ms:0, lastStamp: now, lastUp: true };
      const statusUp = (now - new Date(sa.last_seen).getTime()) < 10_000; // 10s threshold
      const dt = Math.max(0, now - (av.lastStamp||now));
      if(statusUp) av.up_ms += dt; else av.down_ms += dt;
      av.lastStamp = now; av.lastUp = statusUp;
      s.metrics.availability[sa.id] = av;
    });
    saveState(s);
  }


  useEffect(()=>{
    tickRef.current = setInterval(()=>{
      const s = loadState(); ensureMetricsShape(s);
      s.sats = s.sats.map(x=>({
        ...x,
        signal: clamp(x.signal + ((Math.random()*8-4)|0), 10, 100),
        packets: x.packets + (Math.random()*20|0),
        last_seen: iso()
      }));
      saveState(s);
     
      setSats(s.sats);
      setAlerts(api.listAlerts());
      setCdms(api.listCdms());
      if(selected){ setSelected(s.sats.find(y=>y.id===selected.id) || null); }
      
      updateAvailabilityTracker();
    }, 2000);
    return ()=> clearInterval(tickRef.current);
  },[selected?.id]);

 
  const satToCdm = useMemo(()=>{
    const map={}; cdms.forEach(c=>{ if(!map[c.sat_id] || new Date(c.tca)<new Date(map[c.sat_id].tca)) map[c.sat_id]=c;});
    return map;
  },[cdms]);

  const fleetRisk = useMemo(()=>{
    const scores = sats.map(s=>debrisRiskForSat(s, satToCdm[s.id]));
    const avg = scores.length? Math.round(scores.reduce((a,b)=>a+b,0)/scores.length) : 0;
    const tc24 = cdms.filter(c=> (new Date(c.tca)-Date.now())/3_600_000 <= 24).length;
    return { avg, tc24 };
  },[sats,cdms,satToCdm]);


  function attemptTX(s){
    const res = api.attemptTx(s.id,'Manual downlink');
    setTxCounters(p=>({blocked:p.blocked+(res.allowed?0:1),allowed:p.allowed+(res.allowed?1:0)}));
    setAlerts(api.listAlerts());
    alert(res.allowed? 'TX allowed (inside window)':'TX blocked (outside licensed window)');
  }
  function toggleQuarantine(s){
    api.toggleQuarantine(s.id);
    setSats(api.listSats()); setAlerts(api.listAlerts());
  }
  function spendDV(s,amount=0.5){
    const {policy_breach} = api.spendDV(s.id, amount);
    setSats(api.listSats()); setAlerts(api.listAlerts());
    if(policy_breach) alert('Policy breach: EOL ∆V reserve below minimum!');
  }
  function rotateKeys(s){
    api.rotateKeysAndAttest(s.id);
    setSats(api.listSats()); setAlerts(api.listAlerts());
  }
  function registerSat(form){
    const r = api.registerSat(form);
    if(!r.ok) return alert(r.error||'Failed');
    setSats(api.listSats()); setView('dashboard');
  }

  function deleteSat(s){
  if (!confirm(`Delete ${s.name} (${s.id})? This will also remove its alerts, CDMs, and spectrum log entries.`)) return;
  api.deleteSat(s.id);
  const updated = api.listSats();
  setSats(updated);
  setAlerts(api.listAlerts());
  setCdms(api.listCdms());
  if (selected?.id === s.id){
    setSelected(updated[0] || null);
    setView('dashboard');
  }
}

  function exportCompliance(){
    const payload = compliancePayload();
    downloadFile(`Regulus_Compliance_${Date.now()}.json`, JSON.stringify(payload,null,2), 'application/json');
  }
  function exportSpectrum(){
    const rows = api.listSpectrumLog();
    const header = 'time,sat_id,action,allowed,reason\n';
    const csv = header + rows.map(r=>`${fmt(r.time)},${r.sat_id},${r.action},${r.allowed},${r.reason}`).join('\n');
    downloadFile(`Regulus_Spectrum_${Date.now()}.csv`, csv, 'text/csv');
  }

  function compliancePayload(){
    const s=loadState(); const kpi = api.getKPIs();
    return {
      generatedAt: iso(),
      mission: 'Regulus Demo Fleet',
      disposalPlan: { method:'Controlled deorbit', eolPolicy:'Maintain ∆V reserve >= min until passivation' },
      passivationChecklist:['Battery discharge','Vent residuals','Disable TX','Revoke command keys'],
      collisionAvoidancePolicy: { cdmIntake:'SSA (mock)', screening:'24/48/72h', decisionFlow:'Minimal avoidance allowed in quarantine' },
      securityControls: { commandAuth:'ECDSA (concept)', allowList:true, quarantineMode:true, signedUpdates:true, attestation:'Golden image hash + timestamp' },
      spectrum: { licensedWindowsUTC: LICENSED_WINDOWS_UTC, recentLog: s.spectrum_log.slice(0,200) },
      KPIs: kpi,
      satellites: s.sats.map(x=>({
        id:x.id, name:x.name, status:x.status, quarantine:x.quarantine,
        eolReserve_mps:x.eol_reserve_mps, policyMin_mps:x.eol_min_mps,
        attestedAt:x.attested_at, goldenHash:x.golden_hash, anomalies24h:x.anomalies_24h,
        nextCDM: s.cdms.find(c=>c.sat_id===x.id) || null
      }))
    }
  }

  return (
    <div className="min-h-screen bg-slate-50 text-slate-800 dark:bg-slate-900 dark:text-slate-100">
      <div className="w-full p-6">

        <header className="flex items-center justify-between">
          <div className="flex items-center gap-4">
            <div className="w-12 h-12">
<img
  src="assets/logo.jpeg"
  alt="Regulus Logo"
  className="w-full h-full object-contain rounded-lg cursor-pointer"
  onClick={() => setView('dashboard')}
/>

            </div>
            <div>
              <h1 className="text-2xl font-semibold">Regulus</h1>
              <p className="text-sm text-slate-500 dark:text-slate-400">LEO SafetyOps — Cyber • Debris • Compliance</p>
            </div>
          </div>
          <div className="flex items-center gap-4">
            <div className="text-xs text-slate-500 dark:text-slate-400">
              Spectrum (session): <span className="font-semibold text-red-600">{txCounters.blocked}</span> blocked / <span className="font-semibold text-green-600">{txCounters.allowed}</span> allowed
            </div>
            <button
  className="px-4 py-2 bg-[#2E96F5] text-white rounded-md"
  onClick={() => setView('simulator')}
>
  Open Simulator
</button>

            <button className="px-4 py-2 border rounded-md dark:border-slate-600" onClick={()=>setView('reports')}>Reports</button>

           
            <button
  className="px-4 py-2 border rounded-md flex items-center gap-2 dark:border-slate-600"
  onClick={() => setDark(d => !d)}
  aria-label="Toggle color theme"
  aria-pressed={dark}
  title={dark ? 'Switch to light mode' : 'Switch to dark mode'}
>
  <span className="font-medium">{dark ? 'Mode: Light' : 'Mode: Dark'}</span>
</button>

          </div>
        </header>

       
        <div className="mt-6 grid grid-cols-12 gap-6">
        
          <aside className="col-span-3 bg-white rounded-lg p-4 shadow-sm dark:bg-slate-800 dark:border dark:border-slate-700">
            <div className="mb-4">
              <h3 className="text-sm font-semibold text-slate-600 dark:text-slate-300">Fleet Overview</h3>
              <div className="mt-2 grid grid-cols-3 gap-2">
                <SquareStat title="Debris-Risk (avg)" value={fleetRisk.avg} color={fleetRisk.avg>50?'red':fleetRisk.avg>30?'yellow':'green'} />
                <SquareStat title="Upcoming TCAs (24h)" value={fleetRisk.tc24} color={fleetRisk.tc24>0?'yellow':'green'} />
                <SquareStat title="Active Alerts" value={alerts.length} color={alerts.length>0?'red':'green'} />
              </div>
            </div>

           
            <div className="mt-4">
              <h3 className="text-sm font-semibold text-slate-600 dark:text-slate-300">KPI Snapshot</h3>
              {(()=>{ const k = api.getKPIs(); return (
                <div className="mt-2 grid grid-cols-2 gap-2">
                  <Tile title="Detection Acc." value={`${k.detection_accuracy}%`} />
                  <Tile title="Avg Latency" value={`${Math.round(k.anomaly_detection_latency_ms/100)/10}s`} />
                  <Tile title="Auto-Mitigation" value={`${k.autonomous_mitigation_success_rate}%`} />
                  <Tile title="Availability" value={`${k.availability_pct}%`} />
                </div>
              ); })()}
            </div>

            <div className="mt-6">
              <h3 className="text-sm font-semibold text-slate-600 dark:text-slate-300">Satellites</h3>
              <div className="mt-3 space-y-2">
{sats.map(s=>(
  <div
    key={s.id}
    className="flex items-center justify-between p-2 rounded hover:bg-slate-50 dark:hover:bg-slate-700/50 cursor-pointer"
    onClick={()=>{ setSelected(s); setView('telemetry'); }}
  >
    <div>
      <div className="text-sm font-medium">{s.name}</div>
      <div className="text-xs text-slate-400">{s.id} • {s.orbit}</div>
    </div>

    <div className="flex items-center gap-2">
      {s.quarantine && <Badge color="yellow">Quarantine</Badge>}
      {s.status==='Alert'
        ? <Badge color="red">Alert</Badge>
        : <Badge color="green">{s.signal}%</Badge>}

      <button
        className="ml-2 px-2 py-1 text-xs text-red-600 border border-red-600 rounded hover:bg-red-600 hover:text-white"
        onClick={(e)=>{ e.stopPropagation(); deleteSat(s); }}
      >
        Delete
      </button>
    </div>
  </div>
))}

              </div>
            </div>

            <div className="mt-6">
              <h3 className="text-sm font-semibold text-slate-600 dark:text-slate-300">Quick Actions</h3>
              <div className="mt-2 space-y-2">
                <button className="w-full text-left px-3 py-2 border rounded dark:border-slate-600" onClick={()=>setView('register')}>Register Satellite</button>
                <button className="w-full text-left px-3 py-2 border rounded dark:border-slate-600" onClick={()=>setView('kms')}>Key Management</button>
                <button className="w-full text-left px-3 py-2 border rounded dark:border-slate-600" onClick={exportCompliance}>Export Compliance JSON</button>
                <button className="w-full text-left px-3 py-2 border rounded dark:border-slate-600" onClick={exportSpectrum}>Export Spectrum CSV</button>
              </div>
            </div>

            <div className="mt-6">
              <h3 className="text-sm font-semibold text-slate-600 dark:text-slate-300">Active Alerts</h3>
              <div className="mt-2">
                {alerts.length===0 ? <div className="text-xs text-slate-400">No active alerts</div> :
                  alerts.slice(0,5).map(a=>(
                    <div key={a.id} className="p-2 border rounded mb-2 dark:border-slate-700">
                      <div className="text-sm font-medium">{a.sat_id}</div>
                      <div className="text-xs text-slate-500 dark:text-slate-400">{a.type} • {fmt(a.time)}</div>
                      <div className="mt-2"><Badge color={a.severity==='High'?'red':'yellow'}>{a.severity}</Badge></div>
                    </div>
                ))}
              </div>
            </div>
          </aside>

          {/* Main */}
          <main className="col-span-9">
            {view==='dashboard' && <Dashboard sats={sats} cdms={cdms} satToCdm={satToCdm} />}
            {view==='telemetry' && <Telemetry sat={selected} cdm={selected? satToCdm[selected.id]:null} onAttemptTX={attemptTX} onToggleQ={toggleQuarantine} onSpendDV={spendDV} onRotate={rotateKeys} dark={dark} />}
            {view==='register' && <Register onCreate={registerSat} />}
            {view==='kms' && <KMS />}
            {view==='reports' && <Reports sats={sats} cdms={cdms} onCompliance={exportCompliance} onSpectrum={exportSpectrum} />}
            {view==='simulator' && <Simulator sats={sats} onSpoof={(s)=>{api.injectThreat({sat_id:s.id,type:'Spoofing'}); toggleQuarantine(s);}} onJam={(s)=>{api.injectThreat({sat_id:s.id,type:'Jamming'});}} onInjectCDM={(s)=>{const st=loadState(); st.cdms.unshift(api.injectCDM ? api.injectCDM(s.id) : { id: 'CDM-'+Math.floor(Math.random()*1e6), sat_id: s.id, tca: iso(Date.now()+6*3_600_000), miss_distance_m: 900, rel_speed_kms: 11.1, prob: 1e-4}); saveState(st); setCdms(api.listCdms());}} onTX={attemptTX} />}
          </main>
        </div>
      </div>
    </div>
  );
}


function SquareStat({title,value,color='green'}){
  const cls=color==='red'?'text-red-600':color==='yellow'?'text-yellow-600':'text-green-600';
  return <div className="bg-slate-50 p-3 rounded border border-slate-200 dark:bg-slate-800 dark:border-slate-700"><div className="text-[11px] text-slate-500 dark:text-slate-400">{title}</div><div className={`text-xl font-semibold ${cls}`}>{value}</div></div>;
}

function Dashboard({ sats, cdms, satToCdm }){
  return (
    <div>
      <div className="grid grid-cols-3 gap-4">
        <StatCard title="Satellites" value={sats.length} />
        <StatCard title="Healthy" value={sats.filter(s=>s.status==='Healthy').length} />
        <StatCard title="In Quarantine" value={sats.filter(s=>s.quarantine).length} color="yellow" />
      </div>

      <div className="mt-6 bg-white rounded-lg p-4 shadow-sm dark:bg-slate-800 dark:border dark:border-slate-700">
        <div className="flex items-center justify-between">
          <h2 className="text-lg font-semibold">LEO Safety Feed</h2>
          <div className="text-sm text-slate-500 dark:text-slate-400">CDMs, Spectrum & Telemetry</div>
        </div>

        <div className="mt-4 space-y-3">
          {sats.map(s=>{
            const cdm=satToCdm[s.id];
            const risk=debrisRiskForSat(s,cdm);
            return (
              <div key={s.id} className="p-3 border rounded dark:border-slate-700">
                <div className="flex items-center justify-between">
                  <div>
                    <div className="font-medium">{s.name} <span className="text-xs text-slate-400">{s.id}</span></div>
                    <div className="text-xs text-slate-500 dark:text-slate-400">Last seen: {fmt(s.last_seen)}</div>
                    <div className="text-xs text-slate-500 dark:text-slate-400 mt-1">
                      Debris-Risk: <span className={risk>50?'text-red-600':risk>30?'text-yellow-600':'text-green-600'}>{risk}/100</span>
                      {cdm && <> • Next TCA in ~{Math.max(0,Math.round((new Date(cdm.tca)-Date.now())/3_600_000))}h • Miss {cdm.miss_distance_m} m</>}
                    </div>
                  </div>
                  <div className="flex items-center gap-2">
                    <button className="px-3 py-1 border rounded dark:border-slate-600" onClick={()=>api.attemptTx(s.id)}>Attempt TX</button>
                    <button className="px-3 py-1 bg-[#2E96F5] text-white rounded" onClick={()=>alert('Open Telemetry to accept avoidance & show risk drop')}>Avoidance</button>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}
function StatCard({title,value,color='green'}){
  const cls=color==='red'?'text-red-600':color==='yellow'?'text-yellow-600':'text-green-600';
  return <div className="bg-white p-4 rounded-lg shadow-sm dark:bg-slate-800 dark:border dark:border-slate-700"><div className="text-sm text-slate-500 dark:text-slate-400">{title}</div><div className={`text-2xl font-semibold ${cls}`}>{value}</div></div>;
}


function Telemetry({ sat, cdm, onAttemptTX, onToggleQ, onSpendDV, onRotate, dark }){
  if(!sat) return <div className="bg-white rounded-lg p-6 shadow-sm dark:bg-slate-800 dark:border dark:border-slate-700">Select a satellite from the sidebar</div>;
  const [debugging, setDebugging] = useState(false);
  const risk = debrisRiskForSat(sat, cdm);
  function acceptAvoidance(){
  
    const s=loadState(); s.alerts.unshift({ id:uid('al'), sat_id:sat.id, type:'AvoidanceCommandAccepted', time:iso(), severity:'Medium', resolved:true }); saveState(s);
    api.spendDV(sat.id, 0.2);
    alert('Avoidance burn accepted (demo). Debris risk will drop if close to TCA.');
  }
  return (
    <div className="bg-white rounded-lg p-6 shadow-sm dark:bg-slate-800 dark:border dark:border-slate-700">
      <div className="flex items-start justify-between">
        <div>
          <h2 className="text-xl font-semibold">{sat.name} <span className="text-sm text-slate-400">{sat.id}</span></h2>
          <div className="text-sm text-slate-500 dark:text-slate-400">Orbit: {sat.orbit} • Last seen: {fmt(sat.last_seen)}</div>
        </div>
        <div className="text-right">
          <Badge color={sat.status==='Alert'?'red':'green'}>{sat.status}</Badge>
          {sat.quarantine && <div className="mt-1"><Badge color="yellow">Quarantine</Badge></div>}
          <div className="text-xs text-slate-400 mt-1">Signal: {sat.signal}%</div>
        </div>
      </div>

      <div className="mt-6 grid grid-cols-3 gap-4">
        <div className="col-span-2">
          <div className="bg-slate-50 p-4 rounded dark:bg-slate-800 dark:border dark:border-slate-700">
            <div className="text-sm text-slate-500 dark:text-slate-400">Telemetry</div>
            <div className="mt-3 bg-white border rounded p-3 dark:bg-slate-900 dark:border-slate-700">
              <TelemetryChart sat={sat} dark={dark} />
            </div>

            <div className="mt-4 grid grid-cols-3 gap-3">
              <Small label="Packets/sec" value={Math.max(0, Math.round(sat.packets/10))} />
              <Small label="BER" value={`${(Math.random()*0.05).toFixed(3)}`} />
              <Small label="SNR" value={`${(sat.signal/2).toFixed(1)} dB`} />
            </div>

            <div className="mt-6">
              <h3 className="text-sm font-semibold">Command Console</h3>
              <div className="mt-2 flex gap-2 flex-wrap">
                <button className="px-3 py-2 bg-[#EAFE07] text-[#07173F] rounded" onClick={()=>{api.injectThreat({sat_id:sat.id,type:'Spoofing'}); onToggleQ(sat);}}>Simulate Spoof</button>
                <button className="px-3 py-2 bg-[#E43700] text-white rounded" onClick={()=>api.injectThreat({sat_id:sat.id,type:'Jamming'})}>Simulate Jam</button>
                <button className="px-3 py-2 bg-[#2E96F5] text-white rounded" onClick={()=>onToggleQ(sat)}>{sat.quarantine?'Exit Quarantine':'Enter Quarantine'}</button>
                <button className="px-3 py-2 border rounded dark:border-slate-600" onClick={()=>onAttemptTX(sat)}>Attempt TX (window enforced)</button>
                <button className="px-3 py-2 border rounded dark:border-slate-600" onClick={()=>onRotate(sat)}>Rotate Keys & Attest</button>
                <button className="px-3 py-2 border rounded dark:border-slate-600" onClick={()=>onSpendDV(sat,0.5)}>Spend 0.5 m/s</button>
<button
  className="px-3 py-2 border rounded dark:border-slate-600"
  onClick={() => {
    setDebugging(true);

    requestAnimationFrame(() => {
      api.recordDetection({ sat_id: sat.id, type: 'Spoofing' });
      const s = ensureMetricsShape(loadState());
      s.alerts = [];
      saveState(s);
      setAlerts(api.listAlerts());
    });

    setTimeout(() => setDebugging(false), 600);
  }}
>
  Simulate Detection
</button>



              </div>
            </div>
          </div>
        </div>

        <div className="space-y-4">
          <div className="bg-white p-4 rounded border dark:bg-slate-800 dark:border-slate-700">
            <div className="text-sm text-slate-500 dark:text-slate-400">Debris-Risk</div>
            <div className={`text-2xl font-semibold ${risk>50?'text-red-600':risk>30?'text-yellow-600':'text-green-600'}`}>{risk}/100</div>
            <div className="text-xs text-slate-500 dark:text-slate-400 mt-2">Drivers: quarantine {sat.quarantine?'✔':'✖'}, anomalies {sat.anomalies_24h||0}, EOL margin {(sat.eol_reserve_mps - sat.eol_min_mps).toFixed(1)} m/s{cdm?`, TCA in ~${Math.max(0, Math.round((new Date(cdm.tca)-Date.now())/3_600_000))}h`:''}</div>
          </div>

          <div className="bg-white p-4 rounded border dark:bg-slate-800 dark:border-slate-700">
            <h3 className="text-sm font-semibold">TCA / Conjunction</h3>
            {cdm ? (
              <div className="text-sm">
                <div>ID: {cdm.id}</div>
                <div>TCA: {fmt(cdm.tca)}</div>
                <div>Miss: {cdm.miss_distance_m} m • Rel v: {cdm.rel_speed_kms} km/s</div>
                <button className="mt-2 w-full px-3 py-2 bg-[#2E96F5] text-white rounded" onClick={acceptAvoidance}>Accept Avoidance Command</button>
              </div>
            ) : <div className="text-sm text-slate-500 dark:text-slate-400">No CDM in queue</div>}
          </div>

          <div className="bg-white p-4 rounded border dark:bg-slate-800 dark:border-slate-700">
            <h3 className="text-sm font-semibold">EOL ∆V Escrow</h3>
            <div className="text-sm">Reserved: <b>{(sat.eol_reserve_mps||0).toFixed(1)} m/s</b> (Policy min {sat.eol_min_mps} m/s)</div>
            <div className="text-xs text-slate-500 dark:text-slate-400 mt-1">Actions breaching policy trigger alerts; deorbit capability preserved.</div>
          </div>

          <div className="bg-white p-4 rounded border dark:bg-slate-800 dark:border-slate-700">
            <h3 className="text-sm font-semibold">Attestation</h3>
            <div className="text-xs">Golden Image: {sat.golden_hash}</div>
            <div className="text-xs">Last Attested: {fmt(sat.attested_at)}</div>
          </div>
        </div>
      </div>

          {debugging && (
      <div className="fixed inset-0 flex items-center justify-center bg-white/70 dark:bg-slate-900/80 z-50">
        <div className="bg-white dark:bg-slate-800 p-6 rounded-lg shadow-lg text-center">
          <h3 className="text-lg font-semibold text-slate-700 dark:text-slate-200">Debugging & Cleaning</h3>
          <div className="mt-4 w-64 h-3 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden">
            <div className="h-full bg-[#2E96F5] animate-pulse" style={{ width: '100%' }}></div>
          </div>
          <p className="mt-3 text-sm text-slate-500 dark:text-slate-400">Please wait…</p>
        </div>
      </div>
    )}
    </div>
    


  );

  
}
function Small({label,value}){return <div className="bg-white p-3 rounded border dark:bg-slate-800 dark:border-slate-700"><div className="text-xs text-slate-400">{label}</div><div className="text-lg font-semibold">{value}</div></div>;}


function Register({ onCreate }){
  const [form,setForm]=useState({ id:'SAT-00X', name:'NewSat', orbit:'LEO', eol_reserve_mps:12, eol_min_mps:10 });
  return (
    <div className="bg-white rounded-lg p-6 shadow-sm dark:bg-slate-800 dark:border dark:border-slate-700">
      <h2 className="text-lg font-semibold mb-4">Register Satellite</h2>
      <div className="grid grid-cols-2 gap-4">
        <Field label="Satellite ID" value={form.id} onChange={v=>setForm({...form,id:v})}/>
        <Field label="Name" value={form.name} onChange={v=>setForm({...form,name:v})}/>
        <Select label="Orbit" value={form.orbit} onChange={v=>setForm({...form,orbit:v})} options={['LEO','MEO','GEO']}/>
        <Field label="EOL ∆V Reserve (m/s)" type="number" value={form.eol_reserve_mps} onChange={v=>setForm({...form,eol_reserve_mps:+v})}/>
        <Field label="Policy Min ∆V (m/s)" type="number" value={form.eol_min_mps} onChange={v=>setForm({...form,eol_min_mps:+v})}/>
      </div>
      <div className="mt-4 flex gap-2">
        <button className="px-4 py-2 bg-[#2E96F5] text-white rounded" onClick={()=>onCreate(form)}>Create</button>
        <button className="px-4 py-2 border rounded dark:border-slate-600" onClick={()=>history.back()}>Cancel</button>
      </div>
    </div>
  );
}
function Field({label,value,onChange,type='text'}){return(<label className="block"><div className="text-sm text-slate-500 dark:text-slate-400">{label}</div><input className="mt-1 w-full border rounded px-3 py-2 dark:bg-slate-900 dark:border-slate-700" value={value} type={type} onChange={e=>onChange(e.target.value)} /></label>)}
function Select({label,value,onChange,options}){return(<label className="block"><div className="text-sm text-slate-500 dark:text-slate-400">{label}</div><select className="mt-1 w-full border rounded px-3 py-2 dark:bg-slate-900 dark:border-slate-700" value={value} onChange={e=>onChange(e.target.value)}>{options.map(o=><option key={o}>{o}</option>)}</select></label>)}


function KMS(){
  const rows=[
    { id:'key-1', type:'ECDSA', sat:'ISS-Demo', created:'2025-08-01', status:'Active' },
    { id:'key-2', type:'AES-256', sat:'OneWeb-Demo', created:'2025-07-15', status:'Expired' },
  ];
  return (
    <div className="bg-white rounded-lg p-6 shadow-sm dark:bg-slate-800 dark:border dark:border-slate-700">
      <h2 className="text-lg font-semibold">Key Management</h2>
      <p className="text-sm text-slate-500 dark:text-slate-400">Command-signing & link encryption key lifecycle (demo).</p>
      <div className="mt-4">
        <table className="w-full text-sm">
          <thead className="text-left text-slate-500 dark:text-slate-400"><tr><th>ID</th><th>Type</th><th>Satellite</th><th>Created</th><th>Status</th></tr></thead>
          <tbody>{rows.map(k=>(
            <tr key={k.id} className="border-t dark:border-slate-700">
              <td className="py-2">{k.id}</td><td>{k.type}</td><td>{k.sat}</td><td>{k.created}</td>
              <td><Badge color={k.status==='Active'?'green':'gray'}>{k.status}</Badge></td>
            </tr>
          ))}</tbody>
        </table>
      </div>
      <div className="mt-4"><button className="px-3 py-2 bg-[#2E96F5] text-white rounded" onClick={()=>alert('Generate keypair (demo)')}>Generate Keypair</button></div>
      <div className="mt-2 text-xs text-slate-500 dark:text-slate-400">Policy: rotate command keys every 14 days or post-incident; attest firmware post-rotation.</div>
    </div>
  );
}


function Reports({ sats, cdms, onCompliance, onSpectrum }){
  const satsWithEolOk = sats.filter(s=>s.eol_reserve_mps>=s.eol_min_mps).length;
  const k = api.getKPIs();
  return (
    <div className="bg-white rounded-lg p-6 shadow-sm dark:bg-slate-800 dark:border dark:border-slate-700">
      <h2 className="text-lg font-semibold">Reports & Exports</h2>
      <p className="text-sm text-slate-500 dark:text-slate-400">Audit artifacts for regulators, insurers, and mission assurance.</p>

      <div className="mt-4 grid grid-cols-2 gap-4">
        <div className="p-4 border rounded dark:border-slate-700">
          <div className="text-sm text-slate-500 dark:text-slate-400">Cyber, Debris & Compliance Report</div>
          <div className="text-xs text-slate-500 dark:text-slate-400">Disposal plan, conjunction alerts, EOL reserves, spectrum records, KPIs, and command security overview.</div>
          <div className="mt-3"><button className="px-3 py-2 bg-[#2E96F5] text-white rounded" onClick={onCompliance}>Download JSON</button></div>
        </div>
        <div className="p-4 border rounded dark:border-slate-700">
          <div className="text-sm text-slate-500 dark:text-slate-400">Spectrum Compliance Log</div>
          <div className="text-xs text-slate-500 dark:text-slate-400">TX enable/deny against licensed UTC windows.</div>
          <div className="mt-3"><button className="px-3 py-2 border rounded dark:border-slate-600" onClick={onSpectrum}>Download CSV</button></div>
        </div>
      </div>

      <div className="mt-6 grid grid-cols-4 gap-4">
        <Tile title="Satellites" value={sats.length}/>
        <Tile title="EOL Reserve OK" value={`${satsWithEolOk}/${sats.length}`} />
        <Tile title="CDMs in Queue" value={cdms.length} />
        <Tile title="Availability" value={`${k.availability_pct}%`} />
      </div>

      <div className="mt-4 grid grid-cols-4 gap-4">
        <Tile title="Detection Accuracy" value={`${k.detection_accuracy}%`} />
        <Tile title="Avg Detect Latency" value={`${Math.round(k.anomaly_detection_latency_ms/100)/10}s`} />
        <Tile title="Auto-Mitigation" value={`${k.autonomous_mitigation_success_rate}%`} />
        <Tile title="Spectrum Blocks (auto)" value={`${api.listSpectrumLog().filter(r=>!r.allowed).length}`} />
      </div>
    </div>
  );
}
function Tile({title,value}){return <div className="bg-slate-50 p-3 rounded border border-slate-200 dark:bg-slate-800 dark:border-slate-700"><div className="text-[11px] text-slate-500 dark:text-slate-400">{title}</div><div className="text-xl font-semibold">{value}</div></div>;}


function Simulator({ sats, onSpoof, onJam, onInjectCDM, onTX }){
  const [debuggingSim, setDebuggingSim] = useState(false);

  return (
    <div className="bg-white rounded-lg p-6 shadow-sm dark:bg-slate-800 dark:border dark:border-slate-700">
      <h2 className="text-lg font-semibold">Simulator & Sandbox</h2>
      <p className="text-sm text-slate-500 dark:text-slate-400">Run scenarios to test detection, quarantine, spectrum enforcement, and avoidance.</p>
      <div className="mt-4 grid grid-cols-3 gap-4">
        {sats.map(s=>(
          <div key={s.id} className="p-4 border rounded dark:border-slate-700">
            <div className="font-semibold">{s.name}</div>
            <div className="text-xs text-slate-400">{s.id}</div>
            <div className="mt-3 space-y-2">
              <button className="w-full px-3 py-2 bg-[#E43700] text-white rounded" onClick={()=>onSpoof(s)}>Inject Spoof</button>
              <button className="w-full px-3 py-2 bg-[#EAFE07] text-[#07173F] rounded" onClick={()=>onJam(s)}>Inject Jam</button>
              <button className="w-full px-3 py-2 bg-[#2E96F5] text-white rounded" onClick={()=>onInjectCDM(s)}>Inject CDM</button>
              <button className="w-full px-3 py-2 border rounded dark:border-slate-600" onClick={()=>onTX(s)}>Attempt TX (window)</button>
<button
  className="w-full px-3 py-2 border rounded dark:border-slate-600"
  onClick={() => {
    setDebuggingSim(true);

    requestAnimationFrame(() => {
      api.recordDetection({ sat_id: s.id, type: 'Spoofing' });

      const st = ensureMetricsShape(loadState());
      st.alerts = [];
      saveState(st);
      // App's interval will refresh the UI
    });

    setTimeout(() => setDebuggingSim(false), 600);
  }}
>
  Simulate Detection
</button>







            </div>
          </div>
        ))}
      </div>

      {debuggingSim && (
  <div className="fixed inset-0 flex items-center justify-center bg-white/70 dark:bg-slate-900/80 z-50">
    <div className="bg-white dark:bg-slate-800 p-6 rounded-lg shadow-lg text-center">
      <h3 className="text-lg font-semibold text-slate-700 dark:text-slate-200">Debugging & Cleaning</h3>
      <div className="mt-4 w-64 h-3 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden">
        <div className="h-full bg-[#2E96F5] animate-pulse" style={{ width: '100%' }}></div>
      </div>
      <p className="mt-3 text-sm text-slate-500 dark:text-slate-400">Please wait…</p>
    </div>
  </div>
)}

    </div>
  );
}


ReactDOM.createRoot(document.getElementById('root')).render(<App />);

