const { useState, useEffect, useMemo } = React;

const LAVENDER = "#C9B7E6";
const LAVENDER_DIM = "#9B8CC4";
const BG = "#080810";
const SURFACE = "#0D0D1A";
const SURFACE2 = "#12121F";
const BORDER = "#1E1E35";
const TEXT = "#E4E4F0";
const MUTED = "#5A5A7A";
const CRITICAL = "#FF4560";
const HIGH = "#FF8C42";
const MEDIUM = "#FFD166";
const LOW = "#06D6A0";

const FONT_SANS = "'DM Sans', system-ui, sans-serif";
const FONT_DISPLAY = "'Inter', system-ui, sans-serif";
const FONT_MONO = "'IBM Plex Mono', monospace";

const THIS_YEAR = new Date().getFullYear();
const LAST_YEAR = THIS_YEAR - 1;

function cveYear(id) {
  const m = /^CVE-(\d{4})-/.exec(id || "");
  return m ? parseInt(m[1], 10) : null;
}

// Priority score: ransomware-flagged first, then most-recently-added.
function priorityScore(v) {
  const r = v.knownRansomwareCampaignUse === "Known" ? 1 : 0;
  const added = Date.parse(v.dateAdded || "") || 0;
  return r * 1e13 + added;
}

function severityFromAge(daysSinceAdded) {
  if (daysSinceAdded <= 30) return CRITICAL;
  if (daysSinceAdded <= 90) return HIGH;
  if (daysSinceAdded <= 365) return MEDIUM;
  return LOW;
}

function daysBetween(a, b) {
  return Math.round((a - b) / 86400000);
}

function Header({ count, lastUpdated }) {
  return (
    <header style={{
      borderBottom: `1px solid ${BORDER}`,
      background: `linear-gradient(180deg, ${SURFACE} 0%, ${BG} 100%)`,
      padding: "20px 40px",
      position: "sticky", top: 0, zIndex: 10,
      backdropFilter: "blur(8px)",
    }}>
      <div style={{ maxWidth: 1280, margin: "0 auto", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 24, flexWrap: "wrap" }}>
        <a href="/" style={{ textDecoration: "none", display: "flex", alignItems: "baseline", gap: 10 }}>
          <span style={{ fontFamily: FONT_DISPLAY, fontWeight: 800, fontSize: 22, color: TEXT, letterSpacing: "-0.02em" }}>VulnQL</span>
          <span style={{ fontFamily: FONT_MONO, fontSize: 11, color: LAVENDER, opacity: 0.8 }}>/ full feed</span>
        </a>
        <div style={{ display: "flex", alignItems: "center", gap: 16, fontFamily: FONT_MONO, fontSize: 11, color: MUTED }}>
          <span style={{ width: 6, height: 6, borderRadius: "50%", background: LOW, display: "inline-block", boxShadow: `0 0 8px ${LOW}` }} />
          <span>{count.toLocaleString()} entries · {LAST_YEAR}–{THIS_YEAR}</span>
          {lastUpdated && <span style={{ opacity: 0.6 }}>updated {lastUpdated}</span>}
        </div>
      </div>
    </header>
  );
}

function Filters({ q, setQ, yearFilter, setYearFilter, ransomOnly, setRansomOnly, total, shown }) {
  const pill = (active) => ({
    fontFamily: FONT_MONO, fontSize: 11, padding: "6px 12px",
    border: `1px solid ${active ? LAVENDER : BORDER}`,
    color: active ? LAVENDER : MUTED,
    background: active ? `${LAVENDER}10` : "transparent",
    borderRadius: 4, cursor: "pointer", letterSpacing: "0.06em",
    textTransform: "uppercase",
  });
  return (
    <div style={{ padding: "20px 40px", borderBottom: `1px solid ${BORDER}`, background: SURFACE }}>
      <div style={{ maxWidth: 1280, margin: "0 auto", display: "flex", gap: 16, alignItems: "center", flexWrap: "wrap" }}>
        <input
          value={q}
          onChange={e => setQ(e.target.value)}
          placeholder="search CVE id, vendor, product, description…"
          style={{
            flex: "1 1 280px", minWidth: 240,
            background: BG, border: `1px solid ${BORDER}`,
            color: TEXT, padding: "10px 14px", borderRadius: 6,
            fontFamily: FONT_MONO, fontSize: 13, outline: "none",
          }}
        />
        <div style={{ display: "flex", gap: 6 }}>
          {["all", String(THIS_YEAR), String(LAST_YEAR)].map(y => (
            <button key={y} onClick={() => setYearFilter(y)} style={pill(yearFilter === y)}>{y}</button>
          ))}
        </div>
        <button onClick={() => setRansomOnly(v => !v)} style={pill(ransomOnly)}>Ransomware only</button>
        <span style={{ fontFamily: FONT_MONO, fontSize: 11, color: MUTED, marginLeft: "auto" }}>
          showing {shown.toLocaleString()} / {total.toLocaleString()}
        </span>
      </div>
    </div>
  );
}

function Row({ v, rank }) {
  const added = Date.parse(v.dateAdded || "") || 0;
  const now = Date.now();
  const ageDays = added ? daysBetween(now, added) : 9999;
  const sev = severityFromAge(ageDays);
  const ransom = v.knownRansomwareCampaignUse === "Known";
  const [open, setOpen] = useState(false);
  return (
    <>
      <div
        onClick={() => setOpen(o => !o)}
        style={{
          display: "grid",
          gridTemplateColumns: "44px 150px 130px 1fr 110px 90px",
          gap: 14, padding: "12px 16px",
          borderBottom: `1px solid ${BORDER}`,
          cursor: "pointer",
          background: open ? SURFACE2 : "transparent",
          alignItems: "center",
        }}
        onMouseEnter={e => { if (!open) e.currentTarget.style.background = "#10101C"; }}
        onMouseLeave={e => { if (!open) e.currentTarget.style.background = "transparent"; }}
      >
        <span style={{ fontFamily: FONT_MONO, fontSize: 11, color: MUTED }}>#{rank}</span>
        <span style={{ fontFamily: FONT_MONO, fontSize: 12, color: LAVENDER, fontWeight: 600 }}>{v.cveID}</span>
        <span style={{ fontFamily: FONT_MONO, fontSize: 11, color: TEXT }}>{v.vendorProject}</span>
        <span style={{ fontSize: 13, color: TEXT, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
          {v.vulnerabilityName}
        </span>
        <span style={{ display: "flex", gap: 6, alignItems: "center" }}>
          {ransom && (
            <span style={{
              fontFamily: FONT_MONO, fontSize: 9, padding: "2px 6px",
              background: `${CRITICAL}20`, color: CRITICAL,
              border: `1px solid ${CRITICAL}40`, borderRadius: 3,
              letterSpacing: "0.08em",
            }}>RANSOM</span>
          )}
          <span style={{
            fontFamily: FONT_MONO, fontSize: 9, padding: "2px 6px",
            background: `${sev}15`, color: sev,
            border: `1px solid ${sev}40`, borderRadius: 3,
            letterSpacing: "0.08em",
          }}>{ageDays}d</span>
        </span>
        <span style={{ fontFamily: FONT_MONO, fontSize: 11, color: MUTED }}>{v.dateAdded}</span>
      </div>
      {open && (
        <div style={{ padding: "18px 24px 24px 76px", borderBottom: `1px solid ${BORDER}`, background: SURFACE2 }}>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 320px", gap: 32 }}>
            <div>
              <div style={{ fontSize: 10, color: MUTED, fontFamily: FONT_MONO, letterSpacing: "0.1em", marginBottom: 6 }}>DESCRIPTION</div>
              <div style={{ fontSize: 14, color: TEXT, lineHeight: 1.55, marginBottom: 18 }}>{v.shortDescription}</div>
              <div style={{ fontSize: 10, color: MUTED, fontFamily: FONT_MONO, letterSpacing: "0.1em", marginBottom: 6 }}>REQUIRED ACTION</div>
              <div style={{ fontSize: 14, color: TEXT, lineHeight: 1.55 }}>{v.requiredAction}</div>
              {v.notes && (
                <>
                  <div style={{ fontSize: 10, color: MUTED, fontFamily: FONT_MONO, letterSpacing: "0.1em", margin: "18px 0 6px" }}>NOTES</div>
                  <div style={{ fontSize: 12, color: MUTED, lineHeight: 1.55, wordBreak: "break-word" }}>{v.notes}</div>
                </>
              )}
            </div>
            <div>
              <div style={{ fontSize: 10, color: MUTED, fontFamily: FONT_MONO, letterSpacing: "0.1em", marginBottom: 10 }}>METADATA</div>
              {[
                ["Product", v.product],
                ["Date added", v.dateAdded],
                ["Ransomware", v.knownRansomwareCampaignUse],
                ["CWEs", Array.isArray(v.cwes) ? v.cwes.join(", ") : "—"],
              ].map(([k, val]) => (
                <div key={k} style={{ display: "flex", justifyContent: "space-between", padding: "6px 0", borderBottom: `1px dashed ${BORDER}` }}>
                  <span style={{ fontSize: 11, color: MUTED }}>{k}</span>
                  <span style={{ fontSize: 11, color: TEXT, fontFamily: FONT_MONO, textAlign: "right", maxWidth: 220, overflowWrap: "anywhere" }}>{val || "—"}</span>
                </div>
              ))}
              <a
                href={`https://nvd.nist.gov/vuln/detail/${v.cveID}`}
                target="_blank" rel="noopener noreferrer"
                style={{
                  display: "inline-block", marginTop: 16, padding: "8px 14px",
                  fontFamily: FONT_MONO, fontSize: 11, color: LAVENDER,
                  border: `1px solid ${LAVENDER}40`, borderRadius: 4,
                  textDecoration: "none", letterSpacing: "0.06em",
                }}
              >NVD detail →</a>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

function App() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [q, setQ] = useState("");
  const [yearFilter, setYearFilter] = useState("all");
  const [ransomOnly, setRansomOnly] = useState(false);

  useEffect(() => {
    let cancelled = false;
    fetch("/api/kev")
      .then(r => r.ok ? r.json() : Promise.reject(new Error("HTTP " + r.status)))
      .then(j => { if (!cancelled) setData(j); })
      .catch(e => { if (!cancelled) setError(String(e)); });
    return () => { cancelled = true; };
  }, []);

  const baseList = useMemo(() => {
    if (!data || !Array.isArray(data.vulnerabilities)) return [];
    return data.vulnerabilities
      .filter(v => {
        const y = cveYear(v.cveID);
        return y === THIS_YEAR || y === LAST_YEAR;
      })
      .sort((a, b) => priorityScore(b) - priorityScore(a));
  }, [data]);

  const filtered = useMemo(() => {
    const needle = q.trim().toLowerCase();
    return baseList.filter(v => {
      if (ransomOnly && v.knownRansomwareCampaignUse !== "Known") return false;
      if (yearFilter !== "all" && String(cveYear(v.cveID)) !== yearFilter) return false;
      if (needle) {
        const hay = [v.cveID, v.vendorProject, v.product, v.vulnerabilityName, v.shortDescription]
          .filter(Boolean).join(" ").toLowerCase();
        if (!hay.includes(needle)) return false;
      }
      return true;
    });
  }, [baseList, q, yearFilter, ransomOnly]);

  return (
    <div style={{ background: BG, color: TEXT, minHeight: "100vh", fontFamily: FONT_SANS }}>
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=DM+Sans:wght@300;400;500;600&family=IBM+Plex+Mono:wght@400;500;600;700&display=swap');
        * { box-sizing: border-box; }
        body { margin: 0; }
        ::-webkit-scrollbar { width: 8px; height: 8px; }
        ::-webkit-scrollbar-track { background: ${SURFACE}; }
        ::-webkit-scrollbar-thumb { background: ${BORDER}; border-radius: 3px; }
        ::-webkit-scrollbar-thumb:hover { background: ${LAVENDER_DIM}; }
      `}</style>

      <Header count={filtered.length} lastUpdated={data && data.dateReleased ? data.dateReleased.slice(0, 10) : ""} />

      <div style={{ borderBottom: `1px solid ${BORDER}`, padding: "32px 40px 24px", background: `radial-gradient(circle at 20% 0%, ${LAVENDER}08 0%, transparent 60%)` }}>
        <div style={{ maxWidth: 1280, margin: "0 auto" }}>
          <div style={{ fontFamily: FONT_MONO, fontSize: 11, color: LAVENDER, letterSpacing: "0.18em", marginBottom: 10 }}>{LAST_YEAR}–{THIS_YEAR} · SORTED BY PRIORITY</div>
          <h1 style={{ fontFamily: FONT_DISPLAY, fontWeight: 800, fontSize: 42, letterSpacing: "-0.02em", margin: 0 }}>
            Active Exploitation Feed
          </h1>
          <p style={{ color: MUTED, fontSize: 14, marginTop: 10, maxWidth: 720, lineHeight: 1.55 }}>
            Vulnerabilities under active exploitation, filtered to {LAST_YEAR}–{THIS_YEAR} CVE IDs. Priority places
            ransomware-linked entries first, then most-recently-added. Click any row for the required action and analyst notes.
          </p>
        </div>
      </div>

      <Filters
        q={q} setQ={setQ}
        yearFilter={yearFilter} setYearFilter={setYearFilter}
        ransomOnly={ransomOnly} setRansomOnly={setRansomOnly}
        total={baseList.length} shown={filtered.length}
      />

      <main style={{ maxWidth: 1280, margin: "0 auto", padding: "0 0 80px" }}>
        {error && (
          <div style={{ padding: 24, color: CRITICAL, fontFamily: FONT_MONO, fontSize: 13 }}>
            failed to load feed: {error}
          </div>
        )}
        {!data && !error && (
          <div style={{ padding: 60, textAlign: "center", color: MUTED, fontFamily: FONT_MONO, fontSize: 13 }}>
            loading feed…
          </div>
        )}
        {data && filtered.length === 0 && (
          <div style={{ padding: 60, textAlign: "center", color: MUTED }}>No matches.</div>
        )}
        {data && filtered.length > 0 && (
          <>
            <div style={{
              display: "grid",
              gridTemplateColumns: "44px 150px 130px 1fr 110px 90px",
              gap: 14, padding: "10px 16px",
              borderBottom: `1px solid ${BORDER}`,
              background: SURFACE,
              position: "sticky", top: 73, zIndex: 5,
            }}>
              {["#", "CVE ID", "Vendor", "Vulnerability", "Tags", "Added"].map(h => (
                <span key={h} style={{
                  fontSize: 10, color: MUTED, fontFamily: FONT_MONO,
                  letterSpacing: "0.1em", textTransform: "uppercase",
                }}>{h}</span>
              ))}
            </div>
            {filtered.map((v, i) => <Row key={v.cveID + i} v={v} rank={i + 1} />)}
          </>
        )}
      </main>

      <footer style={{ borderTop: `1px solid ${BORDER}`, padding: "24px 40px", color: MUTED, fontSize: 12, fontFamily: FONT_MONO }}>
        <div style={{ maxWidth: 1280, margin: "0 auto", display: "flex", justifyContent: "space-between", flexWrap: "wrap", gap: 12 }}>
          <span>© vulnql · live exploitation feed</span>
          <a href="/" style={{ color: LAVENDER, textDecoration: "none" }}>← back to vulnql.io</a>
        </div>
      </footer>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
