/* ---- security tab ---- */
let SEC = null, SEC_ENV = "", SEC_AT = 0, secBusy = false;
let secBriefMd = "", secBriefAt = 0, secBriefBusy = false;
let secRowBusy = {}, secRowMsg = {}, secHist = {}, secHistOpen = {}, secNoteOpen = {};
let secFlash = null, secResOpen = false, secDirty = false;

const SEC_ORDER = ["critical", "high", "medium", "low"];
const SEC_CHIP = { critical: "sc-crit", high: "sc-high", medium: "sc-med", low: "sc-low" };
const SEC_SRC_URL = { guardduty: "guardduty", cloudtrail: "trail", iam: "iam", network: "sg" };
const SEC_STATUS = ["open", "in_progress", "resolved", "accepted"];
const SEC_STATUS_LABEL = { open: "Open", in_progress: "In progress", resolved: "Resolved", accepted: "Accepted" };
const SEC_STATUS_OPT = { open: "Open", in_progress: "In progress", resolved: "Resolved", accepted: "Accepted risk" };
const SEC_STATUS_CLS = { open: "ss-open", in_progress: "ss-prog", resolved: "ss-done", accepted: "ss-acc" };
const SEC_EVENT_LABEL = { detected: "Detected", status: "Status changed", verified: "Verified", verify_failed: "Verification failed", reappeared: "Reappeared", gone: "Auto-resolved" };

function loadSecurityTab() {
	if (secBusy) return;
	if (!SEC || SEC_ENV !== ENV || Date.now() - SEC_AT > 600000) fetchSecurity(false);
	else renderSecurity();
}

function secResetTracking() {
	secRowBusy = {}; secRowMsg = {}; secHist = {}; secHistOpen = {}; secNoteOpen = {};
	secFlash = null; secDirty = false;
}

function secEnvChanged() {
	SEC = null; SEC_ENV = ""; SEC_AT = 0;
	secBriefMd = ""; secBriefAt = 0;
	secResetTracking();
	secResOpen = false;
	if (!$("#viewSecurity").classList.contains("active")) return;
	if (secBusy) renderSecuritySkeleton();
	else fetchSecurity(false);
}

async function fetchSecurity(fresh) {
	if (secBusy) return;
	secBusy = true;
	const envKey = ENV;
	if (!SEC || SEC_ENV !== envKey) renderSecuritySkeleton();
	else {
		const b = $("#secRescan");
		if (b) { b.disabled = true; b.textContent = "Scanning…"; }
	}
	try {
		const r = await fetch(`/api/security?env=${encodeURIComponent(envKey)}${fresh ? "&fresh=1" : ""}`, { cache: "no-store" });
		SEC = await r.json();
	} catch { SEC = { ok: false, reason: "server" }; }
	SEC_ENV = envKey;
	SEC_AT = Date.now();
	secBusy = false;
	secResetTracking();
	if (envKey !== ENV) {
		if ($("#viewSecurity").classList.contains("active")) fetchSecurity(false);
		return;
	}
	renderSecurity();
	if (typeof renderIssues === "function") renderIssues();
}

function securityIssues() {
	const out = [];
	if (!SEC?.ok || SEC_ENV !== ENV) return out;
	const s = SEC.summary || {};
	if (s.critical > 0) out.push({ k: "sec-crit", sev: "p1", tx: `<b>${+s.critical} critical security finding${s.critical === 1 ? "" : "s"}</b> — review the Security tab.` });
	else if (s.high > 0) out.push({ k: "sec-high", sev: "p2", tx: `<b>${+s.high} high-severity security finding${s.high === 1 ? "" : "s"}</b> — review the Security tab.` });
	const rea = secTrack().reappeared24h;
	if (rea > 0) out.push({ k: "sec-rea", sev: "p1", tx: `<b>${rea} security finding${rea === 1 ? "" : "s"} reappeared</b> after being resolved — review the Security tab.` });
	return out;
}

function renderSecuritySkeleton() {
	const el = $("#secInner");
	if (!el) return;
	el.innerHTML = `
		<div class="sec-head"><div class="skl" style="width:64px;height:64px;border-radius:16px"></div><div style="flex:1"><div class="skl" style="height:18px;width:130px;margin-bottom:8px"></div><div class="skl" style="height:12px;width:190px"></div></div></div>
		<div class="skl" style="height:90px;margin-top:22px"></div>
		<div class="skl" style="height:56px;margin-top:10px"></div>
		<div class="skl" style="height:56px;margin-top:10px"></div>`;
}

function renderSecurity() {
	const el = $("#secInner");
	if (!el) return;
	if (!SEC) return renderSecuritySkeleton();
	if (!SEC.ok) {
		const msg = SEC.reason === "auth" ? "session expired · " + escHtml(ssoManualCmd())
			: SEC.reason === "no-workspace" ? "no workspace selected"
			: SEC.reason === "no-env" ? "no environments configured"
			: escHtml(SEC.detail || "security data unavailable — is the sync server running?");
		el.innerHTML = `<h2>Security</h2><div class="tiles"><div class="tile"><div class="num">–</div><div class="lbl">${msg}</div></div></div>`;
		return;
	}
	el.innerHTML = secHeadHtml() + secBriefingHtml() + secFindingsHtml() + secAttacksHtml() + secResolvedHtml() + secCoverageHtml();
	bindSecurity(el);
}

function secStatusOf(f) {
	const s = String(f?.status || "open");
	return SEC_STATUS.includes(s) ? s : "open";
}

function secFindings() {
	return (Array.isArray(SEC?.findings) ? SEC.findings : []).filter((f) => secStatusOf(f) !== "resolved");
}

function secResolvedList() {
	return Array.isArray(SEC?.resolved) ? SEC.resolved : [];
}

function secFind(fp) {
	return (Array.isArray(SEC?.findings) ? SEC.findings : []).find((f) => String(f.fp) === String(fp));
}

function secTrack() {
	const t = (SEC && SEC.tracking) || {};
	const list = secFindings();
	const now = Date.now();
	const cnt = (s) => list.filter((f) => secStatusOf(f) === s).length;
	const rea = list.filter((f) => f.reappearedAt && now - new Date(f.reappearedAt).getTime() < 86400000).length;
	const pick = (v, fb) => (secDirty || v == null ? fb : +v || 0);
	return {
		open: pick(t.open, cnt("open")),
		inProgress: pick(t.inProgress, cnt("in_progress")),
		resolved: pick(t.resolved, secResolvedList().length),
		accepted: pick(t.accepted, cnt("accepted")),
		reappeared24h: pick(t.reappeared24h, rea),
	};
}

function secChipsHtml(s) {
	return SEC_ORDER.map((k) => (s?.[k] > 0 ? `<span class="sec-chip ${SEC_CHIP[k]}">${+s[k]} ${k}</span>` : "")).join("");
}

function secHeadHtml() {
	const grade = String(SEC.score?.grade || "?").toUpperCase().charAt(0);
	const t = secTrack();
	return `<div class="sec-head">
		<span class="sec-grade sg-${escHtml(grade.toLowerCase())}">${escHtml(grade)}</span>
		<div class="sec-headmeta">
			<div class="sec-num">${escHtml(String(SEC.score?.value ?? "–"))}</div>
			<div class="sec-scanned">posture score · scanned ${agoTxt(SEC.fetchedAt)}</div>
			<div class="sec-track">Open ${t.open} · In progress ${t.inProgress} · Resolved ${t.resolved} · Accepted ${t.accepted}</div>
		</div>
		<button class="btn" id="secRescan">Rescan</button>
		<span class="sec-chips">${secChipsHtml(SEC.summary)}</span>
	</div>`;
}

function secBriefingHtml() {
	return `<h2>Security briefing
		<button class="btn" id="secBriefBtn" style="margin-left:10px">${secBriefAt ? "Regenerate" : "Generate"}</button>
		<span class="hint" id="secBriefMeta">${secBriefAt ? "generated " + agoTxt(secBriefAt) : ""}</span></h2>
	<div class="ai-box${secBriefBusy ? " thinking" : ""}" id="secBriefBox" style="${secBriefMd || secBriefBusy ? "" : "display:none"}">${secBriefBusy ? "Reading findings, attack signals and coverage — writing the briefing…" : secBriefMd}</div>`;
}

async function runSecBriefing(fresh) {
	if (secBriefBusy) return;
	secBriefBusy = true;
	const btn = $("#secBriefBtn"), box = $("#secBriefBox");
	if (btn) btn.disabled = true;
	if (box) {
		box.style.display = "block";
		box.className = "ai-box thinking";
		box.textContent = "Reading findings, attack signals and coverage — writing the briefing…";
	}
	try {
		const r = await fetch(`/api/ai/security${fresh ? "?fresh=1" : ""}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ env: ENV }) });
		const d = await r.json();
		if (d.ok) {
			secBriefMd = md(d.answer);
			secBriefAt = d.generatedAt || Date.now();
		} else {
			secBriefMd = escHtml(d.detail || "briefing unavailable");
			secBriefAt = 0;
		}
	} catch {
		secBriefMd = "briefing unavailable — is the sync server running?";
		secBriefAt = 0;
	}
	secBriefBusy = false;
	const box2 = $("#secBriefBox");
	if (box2) {
		box2.className = "ai-box";
		box2.style.display = "block";
		box2.innerHTML = secBriefMd;
		bindCopies(box2);
	}
	const btn2 = $("#secBriefBtn");
	if (btn2) { btn2.disabled = false; btn2.textContent = secBriefAt ? "Regenerate" : "Generate"; }
	const meta = $("#secBriefMeta");
	if (meta) meta.textContent = secBriefAt ? "generated " + agoTxt(secBriefAt) : "";
}

function secAgeTxt(f) {
	let d = f.ageDays;
	if (d == null && f.firstSeen) d = (Date.now() - new Date(f.firstSeen).getTime()) / 86400000;
	if (d == null || !isFinite(d)) return "";
	const n = Math.floor(+d);
	return n < 1 ? "seen today" : `seen ${n}d`;
}

function secStatusSelHtml(fp, sel, busy) {
	return `<select class="sec-sel" data-fp="${escHtml(fp)}"${busy ? " disabled" : ""}>` +
		SEC_STATUS.map((s) => `<option value="${s}"${s === sel ? " selected" : ""}>${SEC_STATUS_OPT[s]}</option>`).join("") + `</select>`;
}

function secHistBodyHtml(fp) {
	const h = secHist[fp];
	if (!h || h.loading) return `<div class="sec-hist-empty">loading history…</div>`;
	if (h.error) return `<div class="sec-hist-empty">${escHtml(h.error)}</div>`;
	const ev = Array.isArray(h.events) ? h.events : [];
	if (!ev.length) return `<div class="sec-hist-empty">No history recorded yet.</div>`;
	return ev.map((e) => `<div class="sec-hev">
		<span class="at">${escHtml(agoTxt(e.at))}</span>
		<span class="sec-hev-k">${escHtml(SEC_EVENT_LABEL[String(e.kind)] || String(e.kind || "Event"))}</span>
		<span class="sec-hev-d">${escHtml(e.detail || "")}</span>
	</div>`).join("");
}

function secFindRowHtml(f, sev) {
	const fp = String(f.fp || "");
	const st = secStatusOf(f);
	const src = String(f.source || "").toLowerCase();
	const url = SEC_SRC_URL[src] ? awsConsoleUrl(SEC_SRC_URL[src]) : "";
	const rea = !!f.reappearedAt;
	const busy = !!secRowBusy[fp];
	const age = secAgeTxt(f);
	const msg = secRowMsg[fp];
	const note = f.note ? `<div class="sec-note">${st === "accepted" ? "Accepted: " : "Note: "}${escHtml(f.note)}</div>` : "";
	const noteEdit = secNoteOpen[fp]
		? `<div class="sec-note-edit">
			<input class="sec-note-in" data-fp="${escHtml(fp)}" placeholder="Why is this risk accepted? (optional)" value="${escHtml(secNoteOpen[fp].v || "")}">
			<button class="btn sec-note-ok" data-fp="${escHtml(fp)}">Accept risk</button>
			<button class="btn sec-note-x" data-fp="${escHtml(fp)}">Cancel</button>
		</div>`
		: "";
	const acts = fp
		? `<div class="sec-act">
			${secStatusSelHtml(fp, secNoteOpen[fp] ? "accepted" : st, busy)}
			<button class="btn sec-verify" data-fp="${escHtml(fp)}"${busy ? " disabled" : ""}>Verify</button>
			<button class="btn sec-hist" data-fp="${escHtml(fp)}">${secHistOpen[fp] ? "Hide history" : "History"}</button>
		</div>`
		: "";
	return `<div class="sec-find${rea ? " reappeared" : ""}" data-fp="${escHtml(fp)}">
		<span class="sec-chip ${SEC_CHIP[sev]}">${sev}</span>
		<div class="sec-find-main">
			<div class="sec-find-title">
				<b>${escHtml(f.title)}</b>
				${src ? `<span class="sec-src">${escHtml(src)}</span>` : ""}
				<span class="sec-st ${SEC_STATUS_CLS[st]}">${SEC_STATUS_LABEL[st]}</span>
				${rea ? `<span class="sec-st ss-rea">Reappeared</span>` : ""}
				${age ? `<span class="sec-age">${escHtml(age)}</span>` : ""}
			</div>
			${f.detail ? `<div class="sec-find-detail">${escHtml(f.detail)}</div>` : ""}
			${f.resource ? `<div class="sec-find-res">${escHtml(f.resource)}</div>` : ""}
			${note}
			${acts}
			${noteEdit}
			${msg ? `<div class="sec-msg ${escHtml(msg.k)}">${escHtml(msg.tx)}</div>` : ""}
			${secHistOpen[fp] ? `<div class="sec-tl">${secHistBodyHtml(fp)}</div>` : ""}
		</div>
		${f.at ? `<span class="sec-find-at">${agoTxt(f.at)}</span>` : ""}
		${extLink(url, "open")}
	</div>`;
}

function secFindingsHtml() {
	const list = secFindings();
	const t = secTrack();
	let h = `<h2>Findings</h2>`;
	if (t.reappeared24h > 0) h += `<div class="sec-banner">${t.reappeared24h} finding${t.reappeared24h === 1 ? "" : "s"} reappeared after being resolved.</div>`;
	if (secFlash) h += `<div class="sec-flash ${escHtml(secFlash.k)}">${escHtml(secFlash.tx)}</div>`;
	if (!list.length) return h + `<div class="no-issues">No active findings. Posture is clean.</div>`;
	const sevOf = (f) => (SEC_ORDER.includes(f.sev) ? f.sev : "low");
	SEC_ORDER.forEach((sev) => {
		const rows = list.filter((f) => sevOf(f) === sev)
			.map((f, i) => [f, i])
			.sort((a, b) => (b[0].reappearedAt ? 1 : 0) - (a[0].reappearedAt ? 1 : 0) || a[1] - b[1])
			.map((x) => x[0]);
		if (!rows.length) return;
		h += `<div class="sec-group">${sev} · ${rows.length}</div>`;
		h += rows.map((f) => secFindRowHtml(f, sev)).join("");
	});
	return h;
}

function secResolvedHtml() {
	const list = secResolvedList();
	if (!list.length) return "";
	let h = `<h2 class="sec-res-h">Resolved (last 30 days)
		<span class="sec-cnt">${list.length}</span>
		<button class="btn" id="secResToggle">${secResOpen ? "Hide" : "Show"}</button></h2>`;
	h += `<div id="secResList"${secResOpen ? "" : ` style="display:none"`}>` + list.map((r) => {
		const src = String(r.source || "").toLowerCase();
		return `<div class="sec-res">
			<span class="sec-chip ${SEC_CHIP[SEC_ORDER.includes(r.sev) ? r.sev : "low"]}">${escHtml(r.sev || "low")}</span>
			<span>${escHtml(r.title)}</span>
			${src ? `<span class="sec-src">${escHtml(src)}</span>` : ""}
			${r.verifiedAt ? `<span class="sec-res-ok">✓ verified</span>` : ""}
			<span class="sec-res-at">${r.resolvedAt ? escHtml(agoTxt(r.resolvedAt)) : ""}</span>
		</div>`;
	}).join("") + `</div>`;
	return h;
}

function secShortActor(a) {
	const s = String(a || "");
	const tail = s.includes(":") ? s.split(":").pop() : s;
	return tail.length > 48 ? "…" + tail.slice(-47) : tail;
}

function secAttacksHtml() {
	const a = SEC.attacks || {};
	const login = +a.consoleLoginFailures24h || 0;
	const denied = +a.accessDenied24h || 0;
	const actors = Array.isArray(a.deniedTopActors) ? a.deniedTopActors : [];
	const signals = Array.isArray(a.logSignals) ? a.logSignals : [];
	let h = `<h2>Attack signals · last 24h</h2>`;
	if (!login && !denied && !actors.length && !signals.length)
		return h + `<div class="no-issues">No attack signals in the last 24 hours.</div>`;
	h += `<div class="tiles sec-tiles">
		<div class="tile${login > 0 ? " t-crit" : ""}"><div class="num">${login}</div><div class="lbl">console login failures</div></div>
		<div class="tile${denied > 0 ? " t-crit" : ""}"><div class="num">${denied}</div><div class="lbl">access denied events</div></div>
	</div>`;
	if (actors.length) {
		h += `<div class="sec-sub">Top denied actors</div><div class="act-list">` +
			actors.map((x) => `<div class="act-row"><span class="sec-actor" title="${escHtml(x.actor)}">${escHtml(secShortActor(x.actor))}</span><span class="who">${+x.count || 0}</span></div>`).join("") + `</div>`;
	}
	if (signals.length) {
		h += `<div class="sec-sub">Log signals</div>` + signals.map((s) => {
			const sample = String(s.sample || "");
			const long = sample.length > 160;
			return `<div class="sec-sig${long ? " x" : ""}"${long ? ` title="click to expand"` : ""}>
				<div class="sec-sig-head"><b>${escHtml(s.label || s.pattern || "signal")}</b><span class="sec-cnt">${+s.count || 0}</span></div>
				${sample ? `<div class="sec-sig-sample sec-sig-trunc">${escHtml(long ? sample.slice(0, 160) + "…" : sample)}</div>` : ""}
				${long ? `<div class="sec-sig-sample sec-sig-full">${escHtml(sample)}</div>` : ""}
			</div>`;
		}).join("");
	}
	return h;
}

function secCoverageHtml() {
	const c = SEC.coverage || {};
	const items = [["GuardDuty", c.guardduty], ["Security Hub", c.securityhub], ["ECR scanning", c.ecrScanning], ["CloudTrail", c.cloudtrail]];
	return `<p class="cfg-note sec-cov">` + items.map(([n, on]) => on ? `${n} on` : `${n} off (enable in AWS console)`).join(" · ") + `</p>`;
}

function secMergeFinding(fin) {
	if (!SEC) return;
	secDirty = true;
	const fp = String(fin.fp || "");
	if (!Array.isArray(SEC.findings)) SEC.findings = [];
	const list = SEC.findings;
	const i = list.findIndex((f) => String(f.fp) === fp);
	const prev = i >= 0 ? list[i] : {};
	const next = { ...prev, ...fin };
	if (secStatusOf(next) === "resolved") {
		if (i >= 0) list.splice(i, 1);
		if (!Array.isArray(SEC.resolved)) SEC.resolved = [];
		const res = SEC.resolved;
		const row = {
			fp,
			title: next.title,
			sev: next.sev,
			source: next.source,
			resolvedAt: next.resolvedAt || Date.now(),
			verifiedAt: next.verifiedAt,
		};
		const j = res.findIndex((r) => String(r.fp) === fp);
		if (j >= 0) res[j] = row; else res.unshift(row);
		return;
	}
	if (i >= 0) list[i] = next; else list.push(next);
}

function secStatusPick(fp, val) {
	if (!fp || secRowBusy[fp]) return;
	secFlash = null;
	if (val === "accepted") {
		secNoteOpen[fp] = { v: secNoteOpen[fp]?.v || "" };
		delete secRowMsg[fp];
		renderSecurity();
		return;
	}
	delete secNoteOpen[fp];
	secApplyStatus(fp, val, "");
}

async function secApplyStatus(fp, status, note) {
	if (!fp || secRowBusy[fp]) return;
	delete secNoteOpen[fp];
	secFlash = null;
	secRowBusy[fp] = true;
	secRowMsg[fp] = { k: "pending", tx: status === "resolved" ? "verifying against AWS…" : "saving…" };
	renderSecurity();
	const envKey = ENV;
	const body = { env: envKey, fp, status };
	if (note) body.note = note;
	let d = null;
	try {
		const r = await fetch("/api/security/status", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
		d = await r.json();
	} catch { d = null; }
	delete secRowBusy[fp];
	if (envKey !== ENV) return;
	if (!d || !d.ok || !d.finding) {
		secRowMsg[fp] = { k: "err", tx: (d && d.detail) || "could not update this finding — is the sync server running?" };
		renderSecurity();
		return;
	}
	const v = d.verification || {};
	const fin = { ...d.finding };
	if (status === "resolved" && v.verified === false && secStatusOf(fin) === "resolved") fin.status = "in_progress";
	secMergeFinding(fin);
	if (status === "resolved" && v.verified === true) {
		delete secRowMsg[fp];
		secFlash = { k: "ok", tx: "Verified — no longer present in AWS" };
	} else if (status === "resolved") {
		secRowMsg[fp] = { k: "warn", tx: "Still present in AWS — kept as In progress" + (v.reason ? " · " + v.reason : "") };
	} else if (secStatusOf(fin) === "accepted") {
		secRowMsg[fp] = { k: "ok", tx: "Accepted risk — excluded from the posture score" };
	} else {
		secRowMsg[fp] = { k: "ok", tx: "Status set to " + SEC_STATUS_LABEL[secStatusOf(fin)] };
	}
	renderSecurity();
	if (typeof renderIssues === "function") renderIssues();
}

async function secVerify(fp) {
	if (!fp || secRowBusy[fp]) return;
	secFlash = null;
	secRowBusy[fp] = true;
	secRowMsg[fp] = { k: "pending", tx: "checking against AWS…" };
	renderSecurity();
	const envKey = ENV;
	let d = null;
	try {
		const r = await fetch("/api/security/verify", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ env: envKey, fp }) });
		d = await r.json();
	} catch { d = null; }
	delete secRowBusy[fp];
	if (envKey !== ENV) return;
	if (!d || !d.ok) {
		secRowMsg[fp] = { k: "err", tx: (d && d.detail) || "verification failed — is the sync server running?" };
		renderSecurity();
		return;
	}
	const at = d.checkedAt || Date.now();
	const when = agoTxt(at);
	if (d.present) {
		secDirty = true;
		const f = secFind(fp);
		if (f) f.lastSeen = at;
		secRowMsg[fp] = { k: "warn", tx: `Checked ${when} — still present` + (d.evidence ? " · " + d.evidence : "") };
	} else {
		delete secRowMsg[fp];
		secMergeFinding({ ...(secFind(fp) || { fp }), status: "resolved", resolvedAt: at, verifiedAt: at });
		secFlash = { k: "ok", tx: `Checked ${when} — not present, marked resolved` };
	}
	renderSecurity();
	if (typeof renderIssues === "function") renderIssues();
}

async function secToggleHist(fp) {
	if (!fp) return;
	if (secHistOpen[fp]) {
		delete secHistOpen[fp];
		renderSecurity();
		return;
	}
	secHistOpen[fp] = true;
	const need = !secHist[fp] || !!secHist[fp].error;
	if (need) secHist[fp] = { loading: true };
	renderSecurity();
	if (!need) return;
	const envKey = ENV;
	let d = null;
	try {
		const r = await fetch(`/api/security/history?env=${encodeURIComponent(envKey)}&fp=${encodeURIComponent(fp)}`, { cache: "no-store" });
		d = await r.json();
	} catch { d = null; }
	if (envKey !== ENV) return;
	secHist[fp] = d && d.ok
		? { events: Array.isArray(d.events) ? d.events : [] }
		: { error: (d && d.detail) || "history unavailable — is the sync server running?" };
	renderSecurity();
}

function bindSecurity(el) {
	const rescan = $("#secRescan", el);
	if (rescan) rescan.onclick = () => fetchSecurity(true);
	const brief = $("#secBriefBtn", el);
	if (brief) {
		brief.disabled = secBriefBusy;
		brief.onclick = () => runSecBriefing(!!secBriefAt);
	}
	const box = $("#secBriefBox", el);
	if (box && secBriefMd && !secBriefBusy) bindCopies(box);
	$$(".sec-sig.x", el).forEach((s) => s.onclick = () => s.classList.toggle("open"));
	$$(".sec-sel", el).forEach((s) => s.onchange = () => secStatusPick(s.dataset.fp, s.value));
	$$(".sec-verify", el).forEach((b) => b.onclick = () => secVerify(b.dataset.fp));
	$$(".sec-hist", el).forEach((b) => b.onclick = () => secToggleHist(b.dataset.fp));
	$$(".sec-note-ok", el).forEach((b) => b.onclick = () => secApplyStatus(b.dataset.fp, "accepted", secNoteOpen[b.dataset.fp]?.v || ""));
	$$(".sec-note-x", el).forEach((b) => b.onclick = () => {
		delete secNoteOpen[b.dataset.fp];
		renderSecurity();
	});
	$$(".sec-note-in", el).forEach((i) => {
		i.oninput = () => { if (secNoteOpen[i.dataset.fp]) secNoteOpen[i.dataset.fp].v = i.value; };
		i.onkeydown = (e) => {
			if (e.key === "Enter") secApplyStatus(i.dataset.fp, "accepted", i.value);
			if (e.key === "Escape") { delete secNoteOpen[i.dataset.fp]; renderSecurity(); }
		};
	});
	const focus = $(".sec-note-in", el);
	if (focus) focus.focus();
	const res = $("#secResToggle", el);
	if (res) res.onclick = () => { secResOpen = !secResOpen; renderSecurity(); };
}
