/* ============================================================
   FLOWSHIELD — Findings / Scans / Reports / Surface / misc
   ============================================================ */
function DemoBanner({ what }) {
  return (
    <div className="mono" style={{ display:'flex', alignItems:'center', gap:9, fontSize:11.5, color:'var(--amber)', background:'color-mix(in srgb,var(--amber) 10%,transparent)', border:'1px solid color-mix(in srgb,var(--amber) 35%,transparent)', borderRadius:8, padding:'9px 13px', marginBottom:16 }}>
      <Icon name="alert" size={15} /> DATOS DE EJEMPLO · este workspace aún no tiene {what} reales. Lanza un escaneo y aparecerán aquí automáticamente.
    </div>
  );
}

function csvDownload(name, header, rows) {
  const esc = (x) => '"' + String(x == null ? '' : x).replace(/"/g, '""') + '"';
  const csv = [header, ...rows].map(r => r.map(esc).join(',')).join('\n');
  const a = document.createElement('a');
  a.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' }));
  a.download = name; document.body.appendChild(a); a.click(); a.remove();
}

function Findings() {
  const [filter, setFilter] = useStateL('all');
  const [sevF, setSevF] = useStateL('all');
  const [raw, setRaw] = useStateL(null);     // null = cargando
  const [counts, setCounts] = useStateL(null);
  const [demo, setDemo] = useStateL(false);

  const load = () => {
    window.fsApi('/api/results/findings', { method:'GET' })
      .then(d => {
        const items = (d && d.findings) || [];
        if (items.length === 0) { setDemo(true); setRaw(window.SEED_FINDINGS); setCounts(null); }
        else { setDemo(false); setRaw(items); setCounts(d.counts); }
      })
      .catch(() => { setDemo(true); setRaw(window.SEED_FINDINGS); setCounts(null); });
  };
  useEffectL(() => { load(); }, []);

  const all = raw || [];
  const data = all.filter(f => (filter==='all'||f.status===filter) && (sevF==='all'||f.sev===sevF));
  const c = counts ? counts.sev : all.reduce((a,f)=>{a[f.sev]=(a[f.sev]||0)+1;return a;},{});
  const total = counts ? counts.total : all.length;
  const openN = counts ? counts.status.open : all.filter(f=>f.status==='open').length;
  const fixedN = counts ? counts.status.fixed : all.filter(f=>f.status==='fixed').length;

  const cycle = async (f) => {
    if (demo) return;
    const next = f.status==='open' ? 'triage' : f.status==='triage' ? 'fixed' : 'open';
    try { await window.fsApi('/api/results/findings/'+f.id, { method:'PATCH', body:{ status: next } }); load(); }
    catch (e) { window.alert('No se pudo cambiar el estado: ' + (e.message||e)); }
  };
  const exportCsv = () => csvDownload('flowshield-hallazgos.csv',
    ['id','hallazgo','objetivo','severidad','cvss','estado','origen','fecha'],
    all.map(f=>[f.code||f.id, f.name, f.target, f.sev, f.cvss, f.status, f.source, f.date]));
  const [pdfBusy, setPdfBusy] = useStateL(false);
  const exportPdf = async () => {
    if (demo) return;
    setPdfBusy(true);
    try { await window.fsDownloadPdf('/api/report/findings', {}); } catch (e) { window.alert('No se pudo generar el informe: ' + (e.message||e)); }
    setPdfBusy(false);
  };

  return (
    <div className="enter theme-red">
      <PageHeader title="Hallazgos" crumb="OPERACIONES / HALLAZGOS"
        sub="Vulnerabilidades consolidadas de todos los escaneos del workspace, con CVSS y trazabilidad de origen."
        right={<><Btn variant="ghost" icon="reload" onClick={load}>Actualizar</Btn><Btn variant="ghost" icon="download" onClick={exportCsv}>CSV</Btn><Btn variant="primary" icon="report" className="theme-red" style={{ opacity:(demo||pdfBusy)?0.5:1, pointerEvents:pdfBusy?'none':'auto' }} onClick={exportPdf}>{pdfBusy?'Generando…':'Informe PDF'}</Btn></>} />

      {demo && <DemoBanner what="hallazgos" />}

      <div style={{ display:'grid', gridTemplateColumns:'repeat(4,1fr)', gap:16, marginBottom:24 }}>
        <Stat label="Total hallazgos" value={total} icon="findings" accent="var(--fs)" />
        <Stat label="Críticos" value={c.crit||0} icon="alert" accent="var(--sev-crit)" />
        <Stat label="Abiertos" value={openN} icon="x" accent="var(--sev-high)" />
        <Stat label="Resueltos" value={fixedN} icon="check" accent="var(--ok)" />
      </div>

      <div style={{ display:'flex', gap:8, marginBottom:18, flexWrap:'wrap' }}>
        {[['all','Todos'],['open','Abiertos'],['triage','Triaje'],['fixed','Resueltos']].map(([k,l])=>(
          <button key={k} onClick={()=>setFilter(k)} className="chip" style={{ background:filter===k?'var(--red-soft)':'var(--bg-panel)', borderColor:filter===k?'var(--red)':'var(--line-bright)', color:filter===k?'var(--red)':'var(--txt-dim)' }}>{l}</button>
        ))}
        <div style={{ width:1, background:'var(--line)', margin:'0 4px' }} />
        {[['all','Toda severidad'],['crit','Crítico'],['high','Alto'],['med','Medio'],['low','Bajo']].map(([k,l])=>(
          <button key={k} onClick={()=>setSevF(k)} className="chip" style={{ background:sevF===k?'var(--bg-elevated)':'var(--bg-panel)', borderColor:sevF===k?'var(--txt-dim)':'var(--line-bright)', color:sevF===k?'var(--txt-hi)':'var(--txt-dim)' }}>{l}</button>
        ))}
      </div>

      <div className="panel" style={{ overflow:'hidden' }}>
        <table className="tbl">
          <thead><tr><th>ID</th><th>Hallazgo</th><th>Objetivo</th><th>Severidad</th><th>CVSS</th><th>Estado</th><th>Origen</th><th>Fecha</th></tr></thead>
          <tbody>
            {raw === null && <tr><td colSpan={8} style={{ color:'var(--txt-mute)', fontSize:12.5 }}>Cargando…</td></tr>}
            {raw !== null && data.length === 0 && <tr><td colSpan={8} style={{ color:'var(--txt-mute)', fontSize:12.5 }}>Sin hallazgos para este filtro.</td></tr>}
            {data.map(f => (
              <tr key={f.id}>
                <td className="mono" style={{ color:'var(--red)' }}>{f.code || f.id}</td>
                <td style={{ color:'var(--txt-hi)', fontWeight:500, maxWidth:320 }}>{f.name}</td>
                <td className="mono" style={{ color:'var(--txt-dim)', fontSize:12 }}>{f.target}</td>
                <td><Sev level={f.sev} pulse={f.sev==='crit'} /></td>
                <td><span className="mono" style={{ fontWeight:600, color:`var(--sev-${f.sev==='crit'?'crit':f.sev})` }}>{f.cvss}</span></td>
                <td onClick={()=>cycle(f)} title={demo?'':'Cambiar estado (abierto → triaje → resuelto)'} style={{ cursor:demo?'default':'pointer' }}><StatusBadge status={f.status} /></td>
                <td className="mono" style={{ fontSize:11.5, color:'var(--txt-dim)' }}>{f.source}</td>
                <td className="mono" style={{ fontSize:11.5, color:'var(--txt-mute)' }}>{rptDate(f.date)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      <ChipCSS />
    </div>
  );
}

const _TEAM_LABEL = { red:'RED', blue:'BLUE', amber:'NORM', fs:'—' };
const _TEAM_COLOR = { red:'var(--red)', blue:'var(--blue)', amber:'var(--amber)', fs:'var(--txt-dim)' };

function Scans({ onNav, onRelaunch }) {
  const [raw, setRaw] = useStateL(null);
  const [demo, setDemo] = useStateL(false);

  const load = () => {
    window.fsApi('/api/results/scans', { method:'GET' })
      .then(d => {
        const items = (d && d.scans) || [];
        if (items.length === 0) { setDemo(true); setRaw(window.SEED_SCANS); }
        else { setDemo(false); setRaw(items); }
      })
      .catch(() => { setDemo(true); setRaw(window.SEED_SCANS); });
  };
  useEffectL(() => { load(); }, []);

  const del = async (s) => {
    if (demo) return;
    if (!window.confirm('¿Borrar este escaneo y sus hallazgos?')) return;
    try { await window.fsApi('/api/results/scans/'+s.id, { method:'DELETE' }); load(); } catch (e) { window.alert('No se pudo borrar el escaneo: ' + (e.message||e)); }
  };

  const list = raw || [];
  return (
    <div className="enter theme-fs">
      <PageHeader title="Escaneos" crumb="CONFIGURACIÓN / ESCANEOS"
        sub="Historial completo de escaneos del workspace actual."
        right={<><Btn variant="ghost" icon="reload" onClick={load}>Actualizar</Btn><Btn variant="primary" icon="bolt" className="theme-fs" onClick={()=>onNav && onNav('redteam')}>Nuevo escaneo</Btn></>} />
      {demo && <DemoBanner what="escaneos" />}
      <div className="panel" style={{ overflow:'hidden' }}>
        <table className="tbl">
          <thead><tr><th>ID</th><th>Herramienta</th><th>Equipo</th><th>Objetivo</th><th>Estado</th><th>Resumen</th><th>Fecha</th><th></th></tr></thead>
          <tbody>
            {raw === null && <tr><td colSpan={8} style={{ color:'var(--txt-mute)', fontSize:12.5 }}>Cargando…</td></tr>}
            {raw !== null && list.length === 0 && <tr><td colSpan={8} style={{ color:'var(--txt-mute)', fontSize:12.5 }}>Aún no hay escaneos.</td></tr>}
            {list.map(s => {
              const team = s.team || (s.module==='redteam'?'red':s.module==='blueteam'?'blue':s.module==='compliance'?'amber':'fs');
              return (
              <tr key={s.id}>
                <td className="mono" style={{ color:'var(--fs)' }}>{s.code || s.id}</td>
                <td style={{ color:'var(--txt-hi)', fontWeight:500 }}>{s.tool}</td>
                <td><span className="badge" style={{ color:_TEAM_COLOR[team], borderColor:`color-mix(in srgb,${_TEAM_COLOR[team]} 35%,transparent)`, background:'transparent' }}>{_TEAM_LABEL[team]}</span></td>
                <td className="mono" style={{ color:'var(--txt-dim)', fontSize:12 }}>{s.target}</td>
                <td>{s.status==='running' ? <span style={{ display:'flex', alignItems:'center', gap:8 }}><StatusBadge status="running" /><div className="bar" style={{ width:60 }}><span style={{ width:(s.progress||0)+'%' }} /></div></span> : <StatusBadge status="done" />}</td>
                <td>{s.found && (s.found.crit||s.found.high||s.found.med||s.found.low) ? <div style={{ display:'flex', gap:5 }}>{['crit','high','med','low'].map(k=>s.found[k]?<span key={k} className={'badge '+window.sevClass(k)} style={{ padding:'1px 7px', fontSize:10 }}>{s.found[k]}</span>:null)}</div> : <span className="mono" style={{ color:'var(--txt-mute)', fontSize:11 }}>—</span>}</td>
                <td className="mono" style={{ fontSize:11.5, color:'var(--txt-mute)' }}>{rptDate(s.date)}</td>
                <td style={{ textAlign:'right', whiteSpace:'nowrap' }}>{!demo && <>
                  {onRelaunch && <Btn variant="ghost" size="sm" icon="play" onClick={()=>onRelaunch(s)} title="Re-escanear con los mismos parámetros">Re-escanear</Btn>}
                  <Btn variant="ghost" size="sm" icon="trash" onClick={()=>del(s)} style={{ color:'var(--danger)' }} />
                </>}</td>
              </tr>
            );})}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function rptDate(iso) {
  try { return new Date(iso).toLocaleString('es-ES', { dateStyle:'medium', timeStyle:'short' }); }
  catch { return iso || '—'; }
}

function Reports() {
  const [reports, setReports] = useStateL(null);   // null = cargando
  const [tab, setTab] = useStateL('all');           // all | red | blue | amber
  const [gen, setGen] = useStateL(false);           // panel generador abierto
  const [scans, setScans] = useStateL(null);        // escaneos Blue Team para generar
  const [busy, setBusy] = useStateL('');            // id en curso (generar)

  const refresh = () => window.fsApi('/api/report/list', { method:'GET' })
    .then(d => setReports(d.reports || [])).catch(()=> setReports([]));
  useEffectL(() => { refresh(); }, []);

  const toggleGen = () => {
    setGen(g => !g);
    if (scans === null) window.fsApi('/api/blueteam/scans', { method:'GET' })
      .then(d => setScans(d.scans || [])).catch(()=> setScans([]));
  };

  const genFromScan = async (s) => {
    setBusy('gen:'+s.scan_id);
    try { await window.fsDownloadPdf('/api/report/blueteam', s); await refresh(); }
    catch(e) { window.alert('No se pudo generar el informe: ' + (e.message||e)); } finally { setBusy(''); }
  };

  const del = async (id) => {
    if (!window.confirm('¿Eliminar este informe de la biblioteca?')) return;
    try { await window.fsApi('/api/report/'+encodeURIComponent(id), { method:'DELETE' });
      setReports(rs => (rs||[]).filter(r => r.id !== id)); } catch(e) { window.alert('No se pudo eliminar el informe: ' + (e.message||e)); }
  };

  const TABS = [['all','Todos'],['red','Red Team'],['blue','Blue Team'],['amber','Cumplimiento']];
  const list = (reports||[]).filter(r => tab==='all' || r.team===tab);

  return (
    <div className="enter theme-fs">
      <PageHeader title="Informes" crumb="CONFIGURACIÓN / INFORMES"
        sub="Biblioteca de informes técnicos y ejecutivos generados a partir de tus escaneos, hallazgos y cumplimiento. Cada PDF que generas queda archivado aquí."
        right={<Btn variant="primary" icon="plus" className="theme-fs" onClick={toggleGen}>{gen?'Cerrar':'Crear informe'}</Btn>} />

      {gen && (
        <div className="panel enter" style={{ padding:'16px 18px', marginBottom:20 }}>
          <div className="eyebrow" style={{ marginBottom:10 }}>Generar informe desde un escaneo Blue Team</div>
          {scans === null ? (
            <div style={{ color:'var(--txt-mute)', fontSize:13 }}>Cargando escaneos…</div>
          ) : !scans.length ? (
            <div style={{ color:'var(--txt-mute)', fontSize:13, lineHeight:1.6 }}>No hay escaneos Blue Team registrados. Lanza uno en <strong style={{color:'var(--txt-hi)'}}>Blue Team → Nuevo scan</strong>. Los informes de Red Team y Normativa se archivan automáticamente al pulsar «Informe PDF» en sus paneles.</div>
          ) : (
            <div style={{ display:'grid', gap:8 }}>
              {scans.slice(0,20).map(s => { const su=s.summary||{}; return (
                <div key={s.scan_id} style={{ display:'flex', alignItems:'center', gap:12, padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, flexWrap:'wrap' }}>
                  <div style={{ flex:1, minWidth:160 }}>
                    <div style={{ fontSize:12.5, color:'var(--txt-hi)' }}>{s.os} · {s.target}</div>
                    <div className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)', marginTop:2, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{rptDate(s.ts)} · {s.profile_title||s.profile}</div>
                  </div>
                  <span className="disp" style={{ fontSize:14, fontWeight:700, color:'var(--blue)' }}>{su.score||0}%</span>
                  <Btn variant="solid" size="sm" icon="report" className="theme-blue" onClick={()=>genFromScan(s)}>{busy==='gen:'+s.scan_id?'Generando…':'Generar PDF'}</Btn>
                </div>
              );})}
            </div>
          )}
        </div>
      )}

      <div className="tabs" style={{ marginBottom:22 }}>
        {TABS.map(([k,l])=><button key={k} onClick={()=>setTab(k)} className={'tab'+(tab===k?' active':'')}>{l}</button>)}
      </div>

      {reports === null ? (
        <div className="panel" style={{ padding:'18px 22px', color:'var(--txt-mute)', fontSize:13 }}>Cargando informes…</div>
      ) : !list.length ? (
        <Empty icon="report" title="Sin informes todavía"
          sub="Genera uno con «Crear informe», o pulsa «Informe PDF» en Red Team / Blue Team / Normativa: todos se archivan aquí." />
      ) : (
        <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fill,minmax(300px,1fr))', gap:14 }}>
          {list.map(r => (
            <div key={r.id} className={'tool-card theme-'+(r.team||'fs')} style={{ flexDirection:'column' }}>
              <span className="bracket tl" /><span className="bracket tr" /><span className="bracket bl" /><span className="bracket br" />
              <div style={{ display:'flex', justifyContent:'space-between', width:'100%', alignItems:'center' }}>
                <div className="tool-ico"><Icon name="report" size={22} /></div>
                <span className="badge sev-info">{r.fmt||'PDF'}</span>
              </div>
              <div className="tool-title" style={{ marginTop:14, fontSize:14 }}>{r.title}</div>
              <div style={{ display:'flex', justifyContent:'space-between', width:'100%', marginTop:12, alignItems:'center', gap:8 }}>
                <span className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)' }}>{rptDate(r.date)}</span>
                <div style={{ display:'flex', gap:6 }}>
                  <Btn variant="ghost" size="sm" icon="download" onClick={()=>{ if(!r.available) return; const a=document.createElement('a'); a.href='/api/report/file/'+r.id; a.download=r.filename||''; document.body.appendChild(a); a.click(); a.remove(); }} style={{ opacity:r.available?1:0.4 }} title={r.available?'Descargar PDF':'Archivo no disponible'}>Descargar</Btn>
                  <Btn variant="ghost" size="sm" icon="x" onClick={()=>del(r.id)} title="Eliminar informe">Borrar</Btn>
                </div>
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

const SEED_SURFACE = [
  { ip:'10.0.4.21', host:'web-prod-01', os:'Ubuntu 22.04', port:'443', svc:'nginx 1.24', tech:'PHP 8.2', risk:'crit' },
  { ip:'10.0.4.32', host:'db-core-02', os:'RHEL 9', port:'5432', svc:'PostgreSQL 14.2', tech:'—', risk:'high' },
  { ip:'23.214.88.10', host:'app.flowia.es', os:'Linux', port:'443', svc:'Cloudflare', tech:'React · Node', risk:'crit' },
  { ip:'23.214.88.14', host:'mail.flowia.es', os:'Linux', port:'993', svc:'Dovecot', tech:'—', risk:'med' },
  { ip:'192.168.10.1', host:'edge-fw-01', os:'Cisco IOS', port:'22', svc:'SSH', tech:'—', risk:'low' },
];

function Surface() {
  const [rows, setRows] = useStateL(null);
  const [counts, setCounts] = useStateL(null);
  const [demo, setDemo] = useStateL(false);

  const load = () => {
    window.fsApi('/api/results/surface', { method:'GET' })
      .then(d => {
        const items = (d && d.assets) || [];
        if (items.length === 0) { setDemo(true); setRows(SEED_SURFACE); setCounts(null); }
        else { setDemo(false); setRows(items); setCounts(d.counts); }
      })
      .catch(() => { setDemo(true); setRows(SEED_SURFACE); setCounts(null); });
  };
  useEffectL(() => { load(); }, []);

  const list = rows || [];
  const exportCsv = () => csvDownload('flowshield-superficie.csv',
    ['ip','hostname','so','puerto','servicio','tecnologia','riesgo'],
    list.map(r=>[r.ip, r.host, r.os, r.port, r.svc, r.tech, r.risk]));
  const atRisk = counts ? counts.at_risk : list.filter(r=>r.risk==='crit'||r.risk==='high').length;
  const [pdfBusy, setPdfBusy] = useStateL(false);
  const exportPdf = async () => {
    if (demo) return;
    setPdfBusy(true);
    try { await window.fsDownloadPdf('/api/report/surface', {}); } catch (e) { window.alert('No se pudo generar el informe: ' + (e.message||e)); }
    setPdfBusy(false);
  };

  return (
    <div className="enter theme-fs">
      <PageHeader title="Superficie de ataque" crumb="OPERACIONES / SUPERFICIE"
        sub="Vista centralizada de hosts, puertos, servicios y tecnologías descubiertas en los escaneos del workspace."
        right={<><Btn variant="ghost" icon="reload" onClick={load}>Actualizar</Btn><Btn variant="ghost" icon="download" onClick={exportCsv}>CSV</Btn><Btn variant="primary" icon="report" className="theme-fs" style={{ opacity:(demo||pdfBusy)?0.5:1, pointerEvents:pdfBusy?'none':'auto' }} onClick={exportPdf}>{pdfBusy?'Generando…':'Informe PDF'}</Btn></>} />
      {demo && <DemoBanner what="activos descubiertos" />}
      <div style={{ display:'grid', gridTemplateColumns:'repeat(4,1fr)', gap:16, marginBottom:24 }}>
        <Stat label="Hosts" value={counts ? counts.hosts : new Set(list.map(r=>r.host)).size} icon="server" accent="var(--fs)" />
        <Stat label="Puertos abiertos" value={counts ? counts.ports : list.filter(r=>r.port&&r.port!=='—').length} icon="port" accent="var(--blue)" />
        <Stat label="Servicios" value={counts ? counts.services : new Set(list.map(r=>r.svc).filter(s=>s&&s!=='—')).size} icon="network" accent="var(--amber)" />
        <Stat label="Activos en riesgo" value={atRisk} icon="alert" accent="var(--red)" />
      </div>
      <div className="panel" style={{ overflow:'hidden' }}>
        <table className="tbl">
          <thead><tr><th>IP</th><th>Hostname</th><th>SO</th><th>Puerto</th><th>Servicio</th><th>Tecnología</th><th>Riesgo</th></tr></thead>
          <tbody>
            {rows === null && <tr><td colSpan={7} style={{ color:'var(--txt-mute)', fontSize:12.5 }}>Cargando…</td></tr>}
            {rows !== null && list.length === 0 && <tr><td colSpan={7} style={{ color:'var(--txt-mute)', fontSize:12.5 }}>Sin activos. Lanza un Port Scanner, Subdomain Finder o Fingerprint.</td></tr>}
            {list.map((r,i)=>(
              <tr key={r.id||i}>
                <td className="mono" style={{ color:'var(--fs)' }}>{r.ip}</td>
                <td className="mono" style={{ color:'var(--txt-hi)' }}>{r.host}</td>
                <td style={{ color:'var(--txt-dim)', fontSize:12.5 }}>{r.os}</td>
                <td className="mono" style={{ color:'var(--txt)' }}>{r.port}</td>
                <td style={{ color:'var(--txt-dim)', fontSize:12.5 }}>{r.svc}</td>
                <td className="mono" style={{ color:'var(--txt-mute)', fontSize:11.5 }}>{r.tech}</td>
                <td><Sev level={r.risk} pulse={r.risk==='crit'} /></td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function Handlers() {
  const h = [
    { name:'XSS Exploiter', icon:'xss', desc:'Explota un XSS extrayendo cookies, contenido HTML, capturas o pulsaciones de teclado.' },
    { name:'HTTP Request Loggers', icon:'http', desc:'Captura peticiones HTTP entrantes: IP origen, método, parámetros, User-Agent, SO, cabeceras.' },
  ];
  return (
    <div className="enter theme-red">
      <PageHeader title="Handlers" crumb="CONFIGURACIÓN / HANDLERS"
        sub="Un handler espera y almacena conexiones entrantes de los sistemas explotados." />
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:16 }}>
        {h.map((x,i)=>(
          <div key={i} className="tool-card theme-red" style={{ flexDirection:'column', minHeight:150, opacity:0.62, filter:'grayscale(.6)', cursor:'not-allowed' }}>
            <span className="bracket tl" /><span className="bracket tr" /><span className="bracket bl" /><span className="bracket br" />
            <div style={{ display:'flex', justifyContent:'space-between', width:'100%', alignItems:'flex-start' }}>
              <div className="tool-ico"><Icon name={x.icon} size={24} /></div>
              <span className="badge sev-info" style={{ height:'fit-content' }}><Icon name="lock" size={12} />PRÓXIMAMENTE</span>
            </div>
            <div className="tool-title" style={{ marginTop:14 }}>{x.name}</div>
            <div className="tool-desc">{x.desc}</div>
            <div style={{ flex:1 }} />
            <div className="mono" style={{ marginTop:16, fontSize:9.5, color:'var(--txt-mute)', letterSpacing:'.12em' }}>REQUIERE MÓDULO DE EXPLOTACIÓN · PRÓXIMAMENTE</div>
          </div>
        ))}
      </div>
    </div>
  );
}

const _INT_CATALOG = [
  { n:'Microsoft Teams', d:'Resumen instantáneo de resultados en tu canal.', c:'#5059C9', kind:'teams' },
  { n:'Slack', d:'Digest de escaneos y alertas por indicadores de riesgo.', c:'#E01E5A', kind:'slack' },
  { n:'Discord', d:'Recibe el resumen de tus escaneos en tu canal.', c:'#5865F2', kind:'discord' },
  { n:'Webhook', d:'POST JSON a tu endpoint ante cada escaneo o hallazgo.', c:'#00F5C0', kind:'webhook' },
  { n:'Jira', d:'Crea y asigna tareas desde los hallazgos.', c:'#2684FF', kind:null },
  { n:'AWS', d:'Importa automáticamente tus targets EC2 y S3.', c:'#FF9900', kind:null },
  { n:'GitHub Actions', d:'Testea apps web en cada despliegue.', c:'#AEBAD0', kind:null },
  { n:'Burp Suite', d:'Envía issues de Burp a tus Findings en segundos.', c:'#FF6633', kind:null },
];
const _KIND_LABEL = { slack:'Slack', teams:'Microsoft Teams', discord:'Discord', webhook:'Webhook' };

function Integrations() {
  const [list, setList] = useStateL(null);       // null = cargando
  const [form, setForm] = useStateL(null);        // {kind,name,url} | null
  const [busy, setBusy] = useStateL('');           // id/acción en curso
  const [msg, setMsg] = useStateL(null);           // {type,text}

  const load = () => window.fsApi('/api/integrations', { method:'GET' })
    .then(d => setList((d&&d.integrations)||[])).catch(()=>setList([]));
  useEffectL(() => { load(); }, []);

  const openForm = (kind) => { setMsg(null); setForm({ kind, name:_KIND_LABEL[kind]||'', url:'' }); };
  const create = async () => {
    if (!form || !form.url.trim()) return;
    setBusy('create'); setMsg(null);
    try {
      await window.fsApi('/api/integrations', { body:{ kind:form.kind, name:form.name, url:form.url.trim() } });
      setForm(null); load(); setMsg({ type:'ok', text:'Integración añadida.' });
    } catch (e) { setMsg({ type:'err', text:e.message||'No se pudo añadir.' }); }
    setBusy('');
  };
  const toggle = async (it) => {
    setBusy(it.id);
    try { await window.fsApi('/api/integrations/'+it.id+'/enabled', { body:{ enabled: !it.enabled } }); load(); }
    catch (e) { setMsg({ type:'err', text:e.message||'Error.' }); }
    setBusy('');
  };
  const test = async (it) => {
    setBusy('test:'+it.id); setMsg(null);
    try { await window.fsApi('/api/integrations/'+it.id+'/test', { method:'POST' }); setMsg({ type:'ok', text:'Mensaje de prueba enviado a «'+it.name+'».' }); }
    catch (e) { setMsg({ type:'err', text:e.message||'El webhook no respondió.' }); }
    setBusy('');
  };
  const del = async (it) => {
    if (!window.confirm('¿Eliminar la integración «'+it.name+'»?')) return;
    setBusy(it.id);
    try { await window.fsApi('/api/integrations/'+it.id, { method:'DELETE' }); load(); }
    catch (e) { setMsg({ type:'err', text:e.message||'Error.' }); }
    setBusy('');
  };

  const connected = list || [];
  return (
    <div className="enter theme-fs">
      <PageHeader title="Integraciones" crumb="CONFIGURACIÓN / INTEGRACIONES"
        sub="Conecta aplicaciones y servicios externos para recibir las actualizaciones más relevantes de tus escaneos." />

      {msg && <div className="mono" style={{ fontSize:12, marginBottom:14, padding:'9px 13px', borderRadius:8, color: msg.type==='ok'?'var(--ok)':'var(--danger)', background:`color-mix(in srgb,${msg.type==='ok'?'var(--ok)':'var(--danger)'} 10%,transparent)`, border:`1px solid color-mix(in srgb,${msg.type==='ok'?'var(--ok)':'var(--danger)'} 35%,transparent)` }}>{msg.text}</div>}

      {/* connected */}
      {connected.length > 0 && (
        <div className="panel" style={{ overflow:'hidden', marginBottom:20 }}>
          <div className="eyebrow" style={{ margin:0, padding:'14px 18px', borderBottom:'1px solid var(--line)' }}>Conectadas</div>
          <table className="tbl">
            <thead><tr><th>Integración</th><th>Tipo</th><th>Endpoint</th><th>Estado</th><th></th></tr></thead>
            <tbody>
              {connected.map(it => (
                <tr key={it.id}>
                  <td style={{ color:'var(--txt-hi)', fontWeight:500 }}>{it.name}</td>
                  <td className="mono" style={{ fontSize:12, color:'var(--txt-dim)' }}>{_KIND_LABEL[it.kind]||it.kind}</td>
                  <td className="mono" style={{ fontSize:11.5, color:'var(--txt-mute)' }}>{it.url_masked}</td>
                  <td><span className={'badge '+(it.enabled?'sev-ok':'sev-info')}>{it.enabled?'Activa':'Pausada'}</span></td>
                  <td style={{ textAlign:'right', whiteSpace:'nowrap' }}>
                    <Btn variant="ghost" size="sm" onClick={()=>test(it)}>{busy==='test:'+it.id?'Enviando…':'Probar'}</Btn>
                    <Btn variant="ghost" size="sm" onClick={()=>toggle(it)}>{it.enabled?'Pausar':'Activar'}</Btn>
                    <Btn variant="ghost" size="sm" icon="trash" onClick={()=>del(it)} style={{ color:'var(--danger)' }} />
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {/* add form */}
      {form && (
        <div className="panel enter" style={{ padding:'18px 20px', marginBottom:20 }}>
          <div className="eyebrow" style={{ marginBottom:12 }}>Nueva integración · {_KIND_LABEL[form.kind]}</div>
          <div style={{ display:'grid', gap:12 }}>
            <div>
              <label className="mono" style={{ fontSize:11, color:'var(--txt-dim)', display:'block', marginBottom:6 }}>NOMBRE</label>
              <input className="input" value={form.name} onChange={e=>setForm(f=>({...f,name:e.target.value}))} placeholder="p.ej. #seguridad" style={{ width:'100%' }} />
            </div>
            <div>
              <label className="mono" style={{ fontSize:11, color:'var(--txt-dim)', display:'block', marginBottom:6 }}>URL DEL WEBHOOK ({_KIND_LABEL[form.kind]} incoming webhook)</label>
              <input className="input" value={form.url} onChange={e=>setForm(f=>({...f,url:e.target.value}))} placeholder="https://…" style={{ width:'100%' }} />
              <div className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)', marginTop:6 }}>Solo URLs públicas. Las direcciones internas/metadata se rechazan por seguridad.</div>
            </div>
            <div style={{ display:'flex', gap:10 }}>
              <Btn variant="solid" className="theme-fs" onClick={create} style={{ opacity: form.url.trim()?1:0.5 }}>{busy==='create'?'Añadiendo…':'Añadir integración'}</Btn>
              <Btn variant="ghost" onClick={()=>setForm(null)}>Cancelar</Btn>
            </div>
          </div>
        </div>
      )}

      {/* catalog */}
      <div className="eyebrow theme-fs">Disponibles</div>
      <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fill,minmax(280px,1fr))', gap:14 }}>
        {_INT_CATALOG.map((x,i)=>{
          const soon = !x.kind;
          return (
          <div key={i} className="panel" style={{ padding:20, transition:'border-color .2s, transform .18s', cursor: soon?'default':'pointer', opacity: soon?0.7:1 }} onMouseEnter={e=>{ if(!soon){e.currentTarget.style.borderColor=x.c;e.currentTarget.style.transform='translateY(-2px)';} }} onMouseLeave={e=>{e.currentTarget.style.borderColor='var(--line)';e.currentTarget.style.transform='none';}}>
            <div style={{ display:'flex', justifyContent:'space-between', alignItems:'flex-start' }}>
              <div style={{ width:42, height:42, borderRadius:10, display:'grid', placeItems:'center', background:`color-mix(in srgb,${x.c} 16%,transparent)`, color:x.c, marginBottom:14, fontFamily:'var(--font-display)', fontWeight:700, fontSize:18 }}>{x.n[0]}</div>
              {soon && <span className="badge sev-info" style={{ fontSize:9 }}>PRÓXIMAMENTE</span>}
            </div>
            <div className="disp" style={{ fontSize:15, fontWeight:600, color:'var(--txt-hi)', marginBottom:6 }}>{x.n}</div>
            <div style={{ fontSize:12.5, color:'var(--txt-dim)', lineHeight:1.5, marginBottom:14 }}>{x.d}</div>
            <button disabled={soon} onClick={()=>!soon && openForm(x.kind)} style={{ background:'none', border:'none', color: soon?'var(--txt-mute)':x.c, fontSize:12, cursor: soon?'default':'pointer', fontFamily:'var(--font-mono)', display:'flex', alignItems:'center', gap:6 }}>{soon?'en preparación':<>conectar <Icon name="chevron" size={13} /></>}</button>
          </div>
        );})}
      </div>
    </div>
  );
}

function Settings({ initialTab }) {
  const tabs = [
    { id:'workspace', label:'Workspace', icon:'folder' },
    { id:'vpn', label:'Perfiles VPN', icon:'lock' },
    { id:'notif', label:'Notificaciones', icon:'alert' },
    { id:'wordlists', label:'Wordlists', icon:'hash' },
    { id:'license', label:'Licencia', icon:'cert' },
  ];
  const [tab, setTab] = useStateL(initialTab || 'workspace');
  useEffectL(()=>{ if(initialTab) setTab(initialTab); }, [initialTab]);

  return (
    <div className="enter theme-fs">
      <PageHeader title="Ajustes" crumb="CONFIGURACIÓN / AJUSTES"
        sub="Configuración del workspace, perfiles VPN, notificaciones, wordlists y licencia." />
      <div style={{ display:'grid', gridTemplateColumns:'230px 1fr', gap:24, alignItems:'start' }}>
        <div className="panel" style={{ padding:8, display:'flex', flexDirection:'column', gap:2, position:'sticky', top:0 }}>
          {tabs.map(t => (
            <button key={t.id} onClick={()=>setTab(t.id)} className="set-tab" style={{ color: tab===t.id?'var(--fs)':'var(--txt-dim)', background: tab===t.id?'var(--fs-soft)':'transparent' }}>
              <Icon name={t.icon} size={16} />{t.label}
            </button>
          ))}
        </div>
        <div className="fade-soft">
          {tab==='workspace' && <WorkspacePanel />}
          {tab==='vpn' && <VpnPanel />}
          {tab==='notif' && <NotifPanel />}
          {tab==='wordlists' && <WordlistsPanel />}
          {tab==='license' && <LicensePanel />}
        </div>
      </div>
      <style>{`.set-tab{display:flex;align-items:center;gap:10px;font-family:var(--font-body);font-weight:500;font-size:13.5px;border:none;border-radius:8px;padding:11px 13px;cursor:pointer;transition:all .16s;text-align:left}.set-tab:hover{background:var(--bg-panel-2)!important;color:var(--txt-hi)!important}`}</style>
    </div>
  );
}

function WorkspacePanel() {
  const [u, setU] = useStateL(null);   // {plan,name,email,tenant_name,role,assets,scans_month,reports}
  useEffectL(() => {
    let alive = true;
    window.fsApi('/api/account/usage', { method:'GET' })
      .then(d => { if(alive) setU(d); })
      .catch(() => { if(alive) setU('na'); });
    return () => { alive = false; };
  }, []);
  const ok = u && u !== 'na';
  const email = ok ? (u.email || '') : '';
  const display = email ? email.split('@')[0] : '—';
  const initial = (String(display||'?').trim()[0] || '?').toUpperCase();
  const planName = ok ? (u.name || u.plan || '—') : '—';
  const role = ok ? (u.role || 'owner') : '';
  const roleLabel = role === 'owner' ? 'Propietario' : role === 'admin' ? 'Administrador' : 'Operador';
  return (
    <div>
      <div className="panel" style={{ padding:24, marginBottom:16 }}>
        <div className="eyebrow">Cuenta del operador</div>
        <div style={{ display:'flex', alignItems:'center', gap:16, marginBottom:20 }}>
          <div style={{ width:54, height:54, borderRadius:12, background:'linear-gradient(135deg,var(--fs),#29E0FF)', display:'grid', placeItems:'center', color:'#04110D', fontFamily:'var(--font-display)', fontWeight:700, fontSize:24 }}>{initial}</div>
          <div>
            <div className="disp" style={{ fontSize:18, fontWeight:700, color:'var(--txt-hi)' }}>{display}</div>
            <div className="mono" style={{ fontSize:11.5, color:'var(--txt-mute)', marginTop:3 }}>{email || '—'}</div>
          </div>
        </div>
        <div style={{ display:'grid', gap:12 }}>
          <SetRow k="Rol" v={roleLabel} />
          <SetRow k="Workspace" v={ok ? (u.tenant_name || '—') : '—'} />
          <SetRow k="Plan" v={planName} ok />
        </div>
      </div>
      <div className="panel" style={{ padding:24 }}>
        <div className="eyebrow">Límites del plan · {planName}</div>
        <div style={{ display:'grid', gridTemplateColumns:'repeat(3,1fr)', gap:16 }}>
          <LimitMeter label="Activos" v={ok ? u.assets.used : 0} max={ok ? u.assets.max : 1} />
          <LimitMeter label="Escaneos/mes" v={ok ? u.scans_month.used : 0} max={ok ? u.scans_month.max : 1} />
          <LimitMeter label="Informes" v={ok ? u.reports.used : 0} max={ok ? u.reports.max : 1} />
        </div>
        <TokensMeter />
      </div>
    </div>
  );
}

function VpnPanel() {
  const [profiles, setProfiles] = useStateL(null);   // null = cargando
  const [busy, setBusy] = useStateL(false);
  const [err, setErr] = useStateL('');
  const fileRef = useRefL(null);

  const load = () => window.fsApi('/api/vpn/profiles', { method:'GET' })
    .then(d => setProfiles((d&&d.profiles)||[])).catch(()=>setProfiles([]));
  useEffectL(() => { load(); }, []);

  const onFile = async (e) => {
    const file = e.target.files && e.target.files[0];
    if (!file) return; e.target.value='';
    setBusy(true); setErr('');
    try { const fd = new FormData(); fd.append('file', file); fd.append('name', file.name); await window.fsApiForm('/api/vpn/profiles', fd); load(); }
    catch (e) { setErr(e.message || 'No se pudo subir el perfil.'); }
    setBusy(false);
  };
  const del = async (p) => {
    if (!window.confirm('¿Borrar el perfil «'+p.name+'»?')) return;
    try { await window.fsApi('/api/vpn/profiles/'+p.id, { method:'DELETE' }); load(); }
    catch (e) { setErr(e.message || 'No se pudo borrar.'); }
  };

  const list = profiles || [];
  return (
    <div>
      <div className="panel" style={{ padding:22, marginBottom:16, display:'flex', gap:16, alignItems:'center' }}>
        <div className="tool-ico theme-fs" style={{ width:48, height:48 }}><Icon name="lock" size={24} /></div>
        <div style={{ flex:1 }}>
          <div className="disp" style={{ fontSize:16, fontWeight:700, color:'var(--txt-hi)' }}>Perfiles VPN</div>
          <div style={{ fontSize:12.5, color:'var(--txt-dim)', marginTop:3 }}>Sube y gestiona tus perfiles OpenVPN para escaneo de redes internas. El túnel en vivo llegará próximamente.</div>
        </div>
        <input ref={fileRef} type="file" accept=".ovpn,text/plain" style={{ display:'none' }} onChange={onFile} />
        <Btn variant="primary" icon="plus" className="theme-fs" onClick={()=>{ if(!busy && fileRef.current) fileRef.current.click(); }}>{busy?'Subiendo…':'Subir .ovpn'}</Btn>
      </div>
      {err && <div className="panel" style={{ padding:'10px 14px', marginBottom:12, color:'var(--danger)', fontSize:12.5 }}>{err}</div>}
      <div className="panel" style={{ overflow:'hidden' }}>
        <table className="tbl">
          <thead><tr><th>Perfil</th><th>Remote</th><th>Tamaño</th><th>Estado</th><th></th></tr></thead>
          <tbody>
            {profiles === null && <tr><td colSpan={5} style={{ color:'var(--txt-mute)', fontSize:12.5 }}>Cargando…</td></tr>}
            {profiles !== null && list.length === 0 && <tr><td colSpan={5} style={{ color:'var(--txt-mute)', fontSize:12.5 }}>No hay perfiles. Sube un fichero .ovpn.</td></tr>}
            {list.map((p)=>(
              <tr key={p.id}>
                <td className="mono" style={{ color:'var(--txt-hi)' }}><span style={{ display:'inline-flex', alignItems:'center', gap:9 }}><Icon name="lock" size={15} style={{ color:'var(--fs)' }} />{p.name}</span></td>
                <td style={{ color:'var(--txt-dim)', fontSize:12.5 }}>{p.region}</td>
                <td className="mono" style={{ color:'var(--txt)', fontSize:12.5 }}>{wlFmtBytes(p.size)}</td>
                <td><span className="badge sev-info" title="El túnel en vivo llegará próximamente">Próximamente</span></td>
                <td style={{ textAlign:'right' }}><Btn variant="ghost" size="sm" icon="trash" onClick={()=>del(p)} style={{ color:'var(--danger)' }}>Borrar</Btn></td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function NotifPanel() {
  const [rules, setRules] = useStateL(null);       // null = cargando
  const [channels, setChannels] = useStateL(null);
  const [saving, setSaving] = useStateL(false);
  const [err, setErr] = useStateL('');
  const items = [
    ['crit','Hallazgo crítico detectado','Avisa al instante ante cualquier severidad crítica.'],
    ['high','Hallazgo de severidad alta',''],
    ['scanDone','Escaneo completado',''],
    ['newAsset','Nuevo activo en la superficie de ataque',''],
    ['ssl','Certificado SSL próximo a caducar','Aviso con 30 días de antelación.'],
    ['weekly','Resumen semanal de postura',''],
  ];
  useEffectL(() => {
    window.fsApi('/api/notifications/rules', { method:'GET' }).then(d => setRules(d.rules||{})).catch(()=>setRules({}));
    window.fsApi('/api/integrations', { method:'GET' }).then(d => setChannels((d&&d.integrations)||[])).catch(()=>setChannels([]));
  }, []);

  const toggle = async (k) => {
    if (!rules) return;
    const next = { ...rules, [k]: !rules[k] };
    setRules(next); setSaving(true); setErr('');
    try { const d = await window.fsApi('/api/notifications/rules', { method:'PUT', body:{ rules:{ [k]: next[k] } } }); setRules(d.rules); }
    catch (e) { setErr(e.message||'No se pudo guardar.'); setRules(r=>({ ...r, [k]: !next[k] })); }   // revertir
    setSaving(false);
  };

  return (
    <div>
      <div className="panel" style={{ padding:22, marginBottom:16 }}>
        <div className="eyebrow">Canales de notificación</div>
        {channels === null ? <div className="mono" style={{ fontSize:12, color:'var(--txt-mute)' }}>Cargando…</div>
         : channels.length === 0 ? <div style={{ fontSize:12.5, color:'var(--txt-dim)', lineHeight:1.6 }}>No hay canales configurados. Añade uno en <strong style={{ color:'var(--txt-hi)' }}>Integraciones</strong> (Slack, Teams, Discord o Webhook) y las alertas se enviarán ahí.</div>
         : <div style={{ display:'grid', gap:10 }}>
            {channels.map((c,i)=>(
              <div key={c.id} style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'10px 0', borderBottom: i<channels.length-1?'1px solid var(--line)':'none' }}>
                <span className="mono" style={{ fontSize:12.5, color:'var(--txt)' }}>{(_KIND_LABEL[c.kind]||c.kind)} · {c.name}</span>
                {c.enabled ? <span className="badge sev-ok">Activo</span> : <span className="badge sev-info">Pausado</span>}
              </div>
            ))}
          </div>}
      </div>
      <div className="panel" style={{ padding:22 }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center' }}>
          <div className="eyebrow">Reglas de alerta</div>
          {saving && <span className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)' }}>guardando…</span>}
        </div>
        {err && <div style={{ color:'var(--danger)', fontSize:12, marginBottom:10 }}>{err}</div>}
        <div style={{ display:'grid', gap:14 }}>
          {items.map(([k,label,desc])=>(
            <div key={k} style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:14 }}>
              <div><div style={{ fontSize:13, color:'var(--txt-hi)' }}>{label}</div>{desc && <div style={{ fontSize:11.5, color:'var(--txt-mute)', marginTop:2 }}>{desc}</div>}</div>
              <Toggle label="" on={rules ? !!rules[k] : false} onClick={()=>toggle(k)} />
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function wlFmtBytes(n) {
  if (n == null) return '—';
  if (n < 1024) return n + ' B';
  if (n < 1024*1024) return (n/1024).toFixed(1) + ' K';
  return (n/(1024*1024)).toFixed(1) + ' M';
}

function WordlistsPanel() {
  const [lists, setLists] = useStateL([]);
  const [cats, setCats] = useStateL([]);
  const [loading, setLoading] = useStateL(true);
  const [upCat, setUpCat] = useStateL('url-fuzzer');
  const [busy, setBusy] = useStateL(false);
  const [err, setErr] = useStateL('');
  const fileRef = useRefL(null);

  const load = () => {
    setLoading(true);
    window.fsApi('/api/wordlists', { method:'GET' })
      .then(d => { setLists((d && d.wordlists) || []); setCats((d && d.categories) || []); })
      .catch(() => setErr('No se pudieron cargar las wordlists.'))
      .finally(() => setLoading(false));
  };
  useEffectL(() => { load(); }, []);

  const catLabel = (id) => { const c = cats.find(c => c.id === id); return c ? c.label : id; };

  const onFile = async (e) => {
    const file = e.target.files && e.target.files[0];
    if (file) { e.target.value = ''; await doUpload(file); }
  };
  const doUpload = async (file) => {
    setBusy(true); setErr('');
    try {
      const fd = new FormData();
      fd.append('file', file);
      fd.append('category', upCat);
      fd.append('name', file.name);
      await window.fsApiForm('/api/wordlists', fd);
      load();
    } catch (e) { setErr(e.message || 'Error al subir el fichero.'); }
    setBusy(false);
  };
  const del = async (id) => {
    if (!window.confirm('¿Borrar esta wordlist?')) return;
    try { await window.fsApi('/api/wordlists/' + encodeURIComponent(id), { method:'DELETE' }); load(); }
    catch (e) { setErr(e.message || 'No se pudo borrar.'); }
  };

  return (
    <div>
      <div className="panel" style={{ padding:22, marginBottom:16, display:'flex', gap:16, alignItems:'center', flexWrap:'wrap' }}>
        <div className="tool-ico theme-fs" style={{ width:48, height:48 }}><Icon name="hash" size={24} /></div>
        <div style={{ flex:1, minWidth:220 }}>
          <div className="disp" style={{ fontSize:16, fontWeight:700, color:'var(--txt-hi)' }}>Wordlists</div>
          <div style={{ fontSize:12.5, color:'var(--txt-dim)', marginTop:3 }}>Diccionarios para fuzzing, subdominios, vhosts, APIs y credenciales. Catalógalos por uso y aparecerán en el escáner correspondiente.</div>
        </div>
        <div style={{ display:'inline-flex', gap:9, alignItems:'center' }}>
          <select className="rt-sel" value={upCat} onChange={e => setUpCat(e.target.value)} style={{ minWidth:180 }}>
            {cats.map(c => <option key={c.id} value={c.id}>{c.label}</option>)}
          </select>
          <input ref={fileRef} type="file" accept=".txt,.lst,.dic,text/plain" style={{ display:'none' }} onChange={onFile} />
          <Btn variant="primary" icon="plus" className="theme-fs"
            onClick={() => { if (!busy && fileRef.current) fileRef.current.click(); }}>{busy ? 'Subiendo…' : 'Subir wordlist'}</Btn>
        </div>
      </div>
      {err && <div className="panel" style={{ padding:'10px 14px', marginBottom:12, color:'var(--danger)', fontSize:12.5 }}>{err}</div>}
      <div className="panel" style={{ overflow:'hidden' }}>
        <table className="tbl">
          <thead><tr><th>Nombre</th><th>Categoría</th><th>Entradas</th><th>Tamaño</th><th></th></tr></thead>
          <tbody>
            {loading && <tr><td colSpan={5} style={{ color:'var(--txt-mute)', fontSize:12.5 }}>Cargando…</td></tr>}
            {!loading && lists.length === 0 && <tr><td colSpan={5} style={{ color:'var(--txt-mute)', fontSize:12.5 }}>No hay wordlists.</td></tr>}
            {lists.map((l) => (
              <tr key={l.id}>
                <td className="mono" style={{ color:'var(--txt-hi)' }}>
                  <span style={{ display:'inline-flex', alignItems:'center', gap:9 }}>
                    <Icon name="doc" size={15} style={{ color:'var(--fs)' }} />{l.name}
                    {l.builtin
                      ? <span className="badge" style={{ padding:'1px 7px', fontSize:9, color:'var(--fs)' }}>INCORPORADA</span>
                      : <span className="badge sev-low" style={{ padding:'1px 7px', fontSize:9 }}>SUBIDA</span>}
                  </span>
                </td>
                <td style={{ color:'var(--txt-dim)', fontSize:12.5 }}>{catLabel(l.category)}</td>
                <td className="mono" style={{ color:'var(--txt)', fontSize:12.5 }}>{l.entries != null ? l.entries.toLocaleString('es') : '—'}</td>
                <td className="mono" style={{ color:'var(--txt-mute)', fontSize:12 }}>{wlFmtBytes(l.bytes)}</td>
                <td style={{ textAlign:'right', whiteSpace:'nowrap' }}>
                  <a className="btn btn-ghost btn-sm" href={'/api/wordlists/' + encodeURIComponent(l.id) + '/download'}
                     style={{ textDecoration:'none' }}><Icon name="download" size={13} /> Descargar</a>
                  {!l.builtin && <Btn variant="ghost" size="sm" icon="trash" onClick={() => del(l.id)} style={{ color:'var(--danger)' }}>Borrar</Btn>}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

const PLANS = [
  { id:'pentester', name:'Pentester', theme:'red', sub:'Escáneres ligeros · Red Team',
    tag:'Reconocimiento y escáneres livianos para auditar tu superficie de ataque.',
    m:'19,99', y:'16,66',
    feats:['Escaneo de puertos y servicios','Análisis TLS/SSL','DNS, WHOIS y subdominios','Reconocimiento y fingerprint web','Detección de WAF','Informes PDF y biblioteca'] },
  { id:'purple', name:'Purple Team', theme:'fs', popular:true, plus:'Pentester', sub:'Red Team + Blue Team',
    tag:'Ofensiva con más profundidad y hardening defensivo con OpenSCAP.',
    m:'29,99', y:'24,99',
    feats:['Escaneo web de vulnerabilidades (nuclei · CVEs)','WordPress scanner (WPScan)','Fuzzing de rutas y vhosts','Módulo Blue Team: hardening CIS','Remediación guiada por bloques','Escaneo remoto por SSH'] },
  { id:'security', name:'Security Team', theme:'amber', plus:'Purple Team', sub:'Red + Blue + Normativa',
    tag:'Suite completa: ofensiva, defensiva y cumplimiento normativo.',
    m:'39,99', y:'33,32',
    feats:['Módulo Normativa: RGPD / LOPDGDD','Cookies, CMP y terceros (Playwright)','Evidencias y capturas de pantalla','Informes ejecutivos de cumplimiento','Soporte prioritario'] },
];

function LicensePanel() {
  const [yearly, setYearly] = useStateL(false);
  const plan = usePlan();
  const current = plan ? plan.get() : 'pentester';
  return (
    <div>
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'flex-start', flexWrap:'wrap', gap:16, marginBottom:22 }}>
        <div>
          <div className="eyebrow theme-fs" style={{ marginBottom:8 }}>Cambia tu plan</div>
          <h2 className="disp" style={{ fontSize:22, fontWeight:700, color:'var(--txt-hi)' }}>Elige lo que mejor encaje con tu misión</h2>
          <div style={{ marginTop:10, display:'inline-flex', alignItems:'center', gap:9 }}>
            <span className="badge" style={{ color:'var(--fs)', borderColor:'color-mix(in srgb,var(--fs) 35%,transparent)', background:'var(--fs-soft)' }}><span className="dot pulse" style={{ background:'currentColor' }} />PLAN ACTUAL · {(plan?plan.name():'Pentester').toUpperCase()}</span>
          </div>
        </div>
        {/* monthly / yearly toggle */}
        <div style={{ display:'inline-flex', background:'var(--bg-void)', border:'1px solid var(--line-bright)', borderRadius:99, padding:4 }}>
          <button onClick={()=>setYearly(false)} className="bill" style={{ background: !yearly?'var(--bg-elevated)':'transparent', color: !yearly?'var(--txt-hi)':'var(--txt-dim)' }}>Mensual</button>
          <button onClick={()=>setYearly(true)} className="bill" style={{ background: yearly?'var(--bg-elevated)':'transparent', color: yearly?'var(--txt-hi)':'var(--txt-dim)' }}>Anual <span style={{ color:'var(--ok)', fontSize:10 }}>−2 meses</span></button>
        </div>
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'repeat(3,1fr)', gap:16, marginBottom:22 }}>
        {PLANS.map(p => (
          <div key={p.id} className={'tool-card theme-'+p.theme} style={{ flexDirection:'column', cursor:'default', padding:22, borderColor: (current===p.id||p.popular)?'var(--acc)':'var(--line)', boxShadow: (current===p.id||p.popular)?'0 0 0 1px var(--acc),0 16px 50px -26px var(--acc-glow)':'none' }}>
            <span className="bracket tl" /><span className="bracket tr" /><span className="bracket bl" /><span className="bracket br" />
            <div style={{ display:'flex', justifyContent:'space-between', width:'100%', alignItems:'center' }}>
              <div className="disp" style={{ fontSize:19, fontWeight:700, color:'var(--txt-hi)' }}>{p.name}</div>
              {current===p.id ? <span className="badge" style={{ color:'var(--ok)', borderColor:'color-mix(in srgb,var(--ok) 40%,transparent)', background:'color-mix(in srgb,var(--ok) 12%,transparent)' }}>PLAN ACTUAL</span>
               : p.popular && <span className="badge" style={{ color:'var(--acc)', borderColor:'color-mix(in srgb,var(--acc) 40%,transparent)', background:'var(--acc-soft)' }}>POPULAR</span>}
            </div>
            <div style={{ fontSize:12, color:'var(--txt-dim)', lineHeight:1.5, marginTop:8, minHeight:52, position:'relative', zIndex:1 }}>{p.tag}</div>
            <div style={{ display:'flex', alignItems:'baseline', gap:6, marginTop:14 }}>
              <span className="disp" style={{ fontSize:34, fontWeight:700, color:'var(--acc)' }}>{yearly?p.y:p.m} €</span>
              <span className="mono" style={{ fontSize:11, color:'var(--txt-mute)' }}>/mes</span>
            </div>
            <div className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)', marginTop:4 }}>{p.sub}{yearly?' · facturado anual':''}</div>
            <button className="plan-btn" style={{ marginTop:16, opacity: current===p.id?0.6:1, cursor: current===p.id?'default':'pointer' }}
              onClick={()=> current!==p.id && window.fsPlan && window.fsPlan.set(p.id)}>
              {current===p.id ? 'Plan activo' : 'Seleccionar '+p.name}</button>
            <div style={{ width:'100%', height:1, background:'var(--line)', margin:'18px 0 14px' }} />
            {p.plus && <div className="mono" style={{ fontSize:10.5, color:'var(--acc)', marginBottom:10 }}>Todo en {p.plus}, más:</div>}
            <div style={{ display:'grid', gap:9, width:'100%', position:'relative', zIndex:1 }}>
              {p.feats.map((f,i)=>(
                <div key={i} style={{ display:'flex', gap:9, alignItems:'flex-start' }}>
                  <Icon name="check" size={14} style={{ color:'var(--acc)', flexShrink:0, marginTop:2 }} />
                  <span style={{ fontSize:12.5, color:'var(--txt)' }}>{f}</span>
                </div>
              ))}
            </div>
          </div>
        ))}
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:16 }}>
        <div className="panel" style={{ padding:22 }}>
          <div className="eyebrow theme-fs">Incluido en todos los planes</div>
          <div style={{ display:'grid', gap:10 }}>
            {['Acceso a la API real','Re-escaneos ilimitados','Histórico de escaneos','Biblioteca de informes PDF','Exporta evidencias y resultados'].map((f,i)=>(
              <div key={i} style={{ display:'flex', gap:9, alignItems:'center' }}><Icon name="check" size={14} style={{ color:'var(--fs)' }} /><span style={{ fontSize:12.5, color:'var(--txt)' }}>{f}</span></div>
            ))}
          </div>
        </div>
        <div className="panel" style={{ padding:22 }}>
          <div className="eyebrow theme-fs">Complementos opcionales</div>
          <div style={{ display:'flex', flexWrap:'wrap', gap:10 }}>
            {['Escaneo de red interno','Informes y correos con tu marca','Bolsa extra de activos','Soporte prioritario 24/7'].map((f,i)=>(
              <span key={i} className="addon">{f}</span>
            ))}
          </div>
          <div style={{ marginTop:18, padding:'14px 16px', background:'var(--bg-void)', border:'1px dashed var(--line-bright)', borderRadius:10 }}>
            <div className="disp" style={{ fontSize:14, color:'var(--txt-hi)', marginBottom:4 }}>¿Necesitas un plan a medida?</div>
            <div style={{ fontSize:12, color:'var(--txt-dim)', marginBottom:12 }}>Volumen, on-premise o requisitos específicos de cumplimiento.</div>
            <Btn variant="ghost" size="sm" iconR="chevron" onClick={()=>{ window.location.href='mailto:ventas@flowia.es?subject='+encodeURIComponent('Plan a medida FlowShield'); }}>Contactar con ventas</Btn>
          </div>
        </div>
      </div>

      <style>{`
        .bill{font-family:var(--font-display);font-weight:600;font-size:12.5px;border:none;border-radius:99px;padding:8px 16px;cursor:pointer;transition:all .18s;display:flex;align-items:center;gap:6px}
        .plan-btn{position:relative;width:100%;font-family:var(--font-display);font-weight:700;font-size:13px;letter-spacing:.03em;color:#04110D;background:var(--acc);border:none;border-radius:8px;padding:12px;cursor:pointer;overflow:hidden;box-shadow:0 6px 22px -8px var(--acc-glow);transition:transform .15s,box-shadow .2s;z-index:1}
        .plan-btn:hover{transform:translateY(-2px);box-shadow:0 12px 30px -8px var(--acc-glow),0 0 22px -4px var(--acc-glow)}
        .plan-btn::before{content:'';position:absolute;top:0;left:-120%;width:55%;height:100%;background:linear-gradient(100deg,transparent,rgba(255,255,255,.45),transparent);transform:skewX(-20deg);transition:left .55s}
        .plan-btn:hover::before{left:150%}
        .addon{font-family:var(--font-body);font-size:12px;color:var(--txt);padding:7px 13px;border:1px solid var(--line-bright);border-radius:99px;background:var(--bg-void)}
      `}</style>
    </div>
  );
}

function SetRow({ k, v, ok }) {
  return <div style={{ display:'flex', justifyContent:'space-between', padding:'11px 0', borderBottom:'1px solid var(--line)' }}><span style={{ fontSize:13, color:'var(--txt-dim)' }}>{k}</span><span className="mono" style={{ fontSize:12.5, color: ok?'var(--ok)':'var(--txt-hi)' }}>{v}</span></div>;
}
function TokensMeter() {
  const [u, setU] = useStateL(null);   // {used, limit, period} | 'na' | null
  useEffectL(() => {
    let alive = true;
    window.fsApi('/api/assistant/usage', { method:'GET' })
      .then(d => { if(alive) setU(d); })
      .catch(() => { if(alive) setU('na'); });
    return () => { alive = false; };
  }, []);
  if (u === null) return null;
  if (u === 'na' || !u.limit) {
    return (
      <div style={{ marginTop:18 }}>
        <div style={{ display:'flex', justifyContent:'space-between', marginBottom:8 }}>
          <span style={{ fontSize:13, color:'var(--txt-dim)' }}>Tokens IA</span>
          <span className="mono" style={{ fontSize:11.5, color:'var(--txt-mute)' }}>No incluido en tu plan</span>
        </div>
        <div className="bar"><span style={{ width:'0%' }} /></div>
      </div>
    );
  }
  const pct = Math.min(100, Math.round((u.used / u.limit) * 100));
  const full = u.used >= u.limit;
  const fmt = (n) => n.toLocaleString('es-ES');
  return (
    <div style={{ marginTop:18 }}>
      <div style={{ display:'flex', justifyContent:'space-between', marginBottom:8 }}>
        <span style={{ fontSize:13, color:'var(--txt-dim)' }}>Tokens IA <span className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)' }}>· {u.period}</span></span>
        <span className="mono" style={{ fontSize:11.5, color: full ? 'var(--red)' : 'var(--txt-hi)' }}>{fmt(u.used)} / {fmt(u.limit)}</span>
      </div>
      <div className="bar"><span style={{ width:pct+'%', background: full ? 'var(--red)' : undefined }} /></div>
    </div>
  );
}
function LimitMeter({ label, v, max }) {
  return (
    <div>
      <div style={{ display:'flex', justifyContent:'space-between', marginBottom:8 }}><span style={{ fontSize:12, color:'var(--txt-dim)' }}>{label}</span><span className="mono" style={{ fontSize:11.5, color:'var(--txt-hi)' }}>{v}/{max}</span></div>
      <div className="bar"><span style={{ width:(v/max*100)+'%' }} /></div>
    </div>
  );
}
function ChipCSS(){ return <style>{`.chip{font-family:var(--font-body);font-weight:500;font-size:12.5px;padding:7px 14px;border:1px solid;border-radius:99px;cursor:pointer;transition:all .18s}`}</style>; }

Object.assign(window, { Findings, Scans, Reports, Surface, Handlers, Integrations, Settings });
