/* ---- lambdas tab ---- */
async function fetchLambdas() {
	try {
		const r = await fetch(`/api/lambdas?env=${encodeURIComponent(ENV)}`);
		LAMBDAS = await r.json();
	} catch { LAMBDAS = null; }
	renderLambdas(); renderIssues();
}
function renderLambdas() {
	const tb = $("#lamTable tbody");
	if (!LAMBDAS?.ok) { tb.innerHTML = `<tr><td colspan="7" style="color:var(--ink-3)">${LAMBDAS?.reason === "auth" ? "aws sso login needed" : "unavailable"}</td></tr>`; return; }
	$("#lamMeta").textContent = `${LAMBDAS.functions.length} functions · errors over 24h · ${agoTxt(LAMBDAS.fetchedAt)}`;
	tb.innerHTML = LAMBDAS.functions
		.sort((a, b) => (b.errors24h ?? 0) - (a.errors24h ?? 0))
		.map((f) => `<tr data-fn="${f.name}">
			<td><b>${f.name}</b></td><td>${f.runtime || ""}</td><td>${f.mem} MB</td><td>${f.timeout}s</td>
			<td class="${f.invocations24h ? "" : "zero"}">${Math.round(f.invocations24h ?? 0)}</td>
			<td class="${(f.errors24h ?? 0) > 0 ? "err" : "zero"}">${Math.round(f.errors24h ?? 0)}</td>
			<td>${extLink(awsConsoleUrl("lambda", f.name), "console")}<span style="color:var(--ink-3);margin-left:12px">logs ›</span></td></tr>`).join("");
	$$("tr[data-fn]", tb).forEach((tr) => tr.onclick = () => openLogs("/aws/lambda/" + tr.dataset.fn));
	$$("a.xl", tb).forEach((a) => a.onclick = (e) => e.stopPropagation());
}

/* ---- live logs tab ---- */
const LOG = { group: "", minutes: 15, filter: "", q: "", auto: true, follow: true, timer: null, lastEvents: [] };
async function fetchLogGroups() {
	try {
		const r = await fetch(`/api/loggroups?env=${encodeURIComponent(ENV)}`);
		const d = await r.json();
		const sel = $("#logGroup");
		sel.innerHTML = `<option value="">select log group…</option>` + (d.groups || []).map((g) => `<option value="${g}"${g === LOG.group ? " selected" : ""}>${g}</option>`).join("");
	} catch { /* offline */ }
}
function openLogs(group) {
	LOG.group = group;
	switchTab("viewLive");
	fetchLogGroups().then(() => { $("#logGroup").value = group; refreshLogs(); });
}
const logConsoleLink = document.createElement("a");
logConsoleLink.className = "xl";
logConsoleLink.target = "_blank";
logConsoleLink.rel = "noopener";
logConsoleLink.textContent = "console ↗";
logConsoleLink.style.display = "none";
$("#logMeta").after(logConsoleLink);
function syncLogConsoleLink() {
	if (LOG.group) { logConsoleLink.href = awsConsoleUrl("logGroup", LOG.group); logConsoleLink.style.display = ""; }
	else logConsoleLink.style.display = "none";
}
const logTools = document.createElement("div");
logTools.className = "logtools";
const logQInput = document.createElement("input");
logQInput.placeholder = "filter loaded events…";
logQInput.spellcheck = false;
const logCountEl = document.createElement("span");
logCountEl.className = "logcount";
const logFollowBtn = document.createElement("button");
logFollowBtn.className = "btn on";
logFollowBtn.textContent = "Follow tail";
logFollowBtn.title = "auto-scroll to newest events";
logTools.append(logQInput, logCountEl, logFollowBtn);
$("#logView").before(logTools);
$("#logView").classList.add("v2");
const LOG_OPEN = new Set();
logQInput.addEventListener("input", () => { LOG.q = logQInput.value; applyLogFilter(); });
logFollowBtn.onclick = () => {
	LOG.follow = !LOG.follow;
	logFollowBtn.classList.toggle("on", LOG.follow);
	if (LOG.follow) { const v = $("#logView"); v.scrollTop = v.scrollHeight; }
};
$("#logView").addEventListener("scroll", () => {
	const v = $("#logView");
	const atBottom = v.scrollHeight - v.scrollTop - v.clientHeight < 40;
	if (atBottom !== LOG.follow) { LOG.follow = atBottom; logFollowBtn.classList.toggle("on", atBottom); }
});
function logLevel(m) {
	const head = m.slice(0, 120);
	if (/\b(error|err|exception|fatal|fail(?:ed|ure|ing)?)\b/i.test(head)) return "error";
	if (/\bwarn(?:ing)?\b/i.test(head)) return "warn";
	if (/\binfo\b/i.test(head)) return "info";
	if (/\bdebug\b/i.test(head)) return "debug";
	return "";
}
function logParseJson(m) {
	const s = m.trim();
	const i = s.search(/[{[]/);
	if (i < 0) return null;
	try {
		const v = JSON.parse(s.slice(i));
		if (v && typeof v === "object") return v;
	} catch { }
	return null;
}
function logJsonSummary(v) {
	if (Array.isArray(v)) return `[array · ${v.length} item${v.length === 1 ? "" : "s"}]`;
	const keys = Object.keys(v);
	if (!keys.length) return "{}";
	const parts = keys.slice(0, 4).map((k) => {
		let val = v[k];
		if (val && typeof val === "object") val = Array.isArray(val) ? `[${val.length}]` : "{…}";
		else { val = JSON.stringify(val); if (typeof val === "string" && val.length > 48) val = val.slice(0, 47) + "…"; }
		return `${k}: ${val}`;
	});
	return `{ ${parts.join(", ")}${keys.length > 4 ? `, +${keys.length - 4} more` : ""} }`;
}
function buildLogRow(e) {
	const lvl = logLevel(e.m);
	const row = document.createElement("div");
	row.className = "lrow" + (lvl === "error" ? " err" : lvl === "warn" ? " warn" : lvl === "debug" ? " debug" : "");
	row._raw = e.m.toLowerCase();
	const caret = document.createElement("span");
	caret.className = "lcaret off";
	caret.textContent = "▸";
	const ts = document.createElement("span");
	ts.className = "lts";
	ts.textContent = new Date(e.t).toLocaleTimeString("en-GB");
	row.append(caret, ts);
	if (lvl) {
		const chip = document.createElement("span");
		chip.className = "lchip " + lvl;
		chip.textContent = lvl.toUpperCase();
		row.append(chip);
	}
	const msg = document.createElement("span");
	msg.className = "lmsg";
	const obj = logParseJson(e.m);
	let full = "";
	if (obj) { msg.textContent = logJsonSummary(obj); full = JSON.stringify(obj, null, 2); }
	else if (e.m.length > 240) { msg.textContent = e.m.slice(0, 240).trimEnd() + "…"; full = e.m; }
	else msg.textContent = e.m.trimEnd();
	row.append(msg);
	if (full) {
		caret.classList.remove("off");
		row.classList.add("x");
		const pre = document.createElement("pre");
		pre.className = "lfull";
		pre.textContent = full;
		row.append(pre);
		const key = e.t + "§" + e.m.slice(0, 80);
		if (LOG_OPEN.has(key)) row.classList.add("open");
		row.addEventListener("click", (ev) => {
			if (ev.target.closest(".lfull") || String(getSelection())) return;
			row.classList.toggle("open");
			if (row.classList.contains("open")) LOG_OPEN.add(key); else LOG_OPEN.delete(key);
		});
	}
	return row;
}
function applyLogFilter() {
	const q = LOG.q.trim().toLowerCase();
	const rows = $$(".lrow", $("#logView"));
	let shown = 0;
	rows.forEach((r) => {
		const hit = !q || r._raw.includes(q);
		r.classList.toggle("hid", !hit);
		if (hit) { r.classList.toggle("alt", shown % 2 === 1); shown++; }
	});
	logCountEl.textContent = rows.length ? `${shown.toLocaleString("en-US")} / ${rows.length.toLocaleString("en-US")}` : "";
}
function renderLogView() {
	const v = $("#logView");
	const prevTop = v.scrollTop;
	v.textContent = "";
	if (!LOG.lastEvents.length) {
		const d = document.createElement("div");
		d.className = "logline meta";
		d.textContent = `no events in the last ${LOG.minutes} min${LOG.filter ? " matching filter" : ""}`;
		v.append(d);
		logCountEl.textContent = "";
		return;
	}
	const frag = document.createDocumentFragment();
	LOG.lastEvents.forEach((e) => frag.append(buildLogRow(e)));
	v.append(frag);
	applyLogFilter();
	v.scrollTop = LOG.follow ? v.scrollHeight : prevTop;
}
function applyDefaultLogGroup() {
	if (LOG.group) return;
	const g = envCfg().logGroup;
	if (!g) return;
	LOG.group = g;
	const sel = $("#logGroup");
	if (sel) sel.value = g;
	refreshLogs();
}
async function refreshLogs() {
	syncLogConsoleLink();
	if (!LOG.group) return;
	$("#logMeta").textContent = "loading…";
	try {
		const r = await fetch(`/api/logs?env=${encodeURIComponent(ENV)}&group=${encodeURIComponent(LOG.group)}&minutes=${LOG.minutes}&filter=${encodeURIComponent(LOG.filter)}`);
		const d = await r.json();
		if (!d.ok) {
			const v = $("#logView");
			v.textContent = "";
			const err = document.createElement("div");
			err.className = "logline meta";
			err.textContent = d.detail || d.reason;
			v.append(err);
			logCountEl.textContent = "";
			return;
		}
		LOG.lastEvents = d.events;
		renderLogView();
		$("#logMeta").textContent = `${d.events.length} events · ${agoTxt(d.fetchedAt)}`;
	} catch { $("#logMeta").textContent = "offline"; }
}
function logTimer() {
	clearInterval(LOG.timer);
	if (LOG.auto) LOG.timer = setInterval(() => { if ($("#viewLive").classList.contains("active")) refreshLogs(); }, SETTINGS.logMs);
}
$("#logGroup").onchange = (e) => { LOG.group = e.target.value; LOG_OPEN.clear(); refreshLogs(); };
$("#logMinutes").onchange = (e) => { LOG.minutes = +e.target.value; refreshLogs(); };
$("#logFilter").addEventListener("keydown", (e) => { if (e.key === "Enter") { LOG.filter = e.target.value; refreshLogs(); } });
$("#logRefresh").onclick = refreshLogs;
$("#logAuto").onclick = () => { LOG.auto = !LOG.auto; $("#logAuto").classList.toggle("on", LOG.auto); logTimer(); };
$("#logDownload").onclick = () => {
	const txt = LOG.lastEvents.map((e) => new Date(e.t).toISOString() + " " + e.m).join("\n");
	const a = document.createElement("a");
	a.href = URL.createObjectURL(new Blob([txt], { type: "text/plain" }));
	a.download = (LOG.group.split("/").pop() || "logs") + ".txt";
	a.click();
};
const CHAT = [];
$("#logChatBtn").onclick = () => {
	const c = $("#logChat");
	c.style.display = c.style.display === "none" ? "flex" : "none";
	if (c.style.display === "flex") $("#lcQ").focus();
};
$("#lcClose").onclick = () => { $("#logChat").style.display = "none"; };
function chatAdd(cls, html) {
	const d = document.createElement("div");
	d.className = cls === "q" ? "lc-q" : "lc-a ai-box";
	d.innerHTML = html;
	$("#lcMsgs").appendChild(d);
	$("#lcMsgs").scrollTop = $("#lcMsgs").scrollHeight;
	return d;
}
async function chatSend() {
	const q = $("#lcQ").value.trim();
	if (!q) return;
	$("#lcQ").value = "";
	chatAdd("q", q.replace(/&/g, "&amp;").replace(/</g, "&lt;"));
	const wait = chatAdd("a", "<i>analyzing…</i>");
	try {
		const excerpt = LOG.lastEvents.slice(-80).map((e) => new Date(e.t).toISOString().slice(11, 19) + " " + e.m).join("\n");
		const r = await fetch(`/api/ai/chat?env=${ENV}`, { method: "POST", body: JSON.stringify({ question: q, excerpt, history: CHAT.slice(-3) }) });
		const d = await r.json();
		if (!d.ok) { wait.textContent = d.detail || "unavailable"; return; }
		await typewrite(wait, d.answer);
		CHAT.push({ q, a: d.answer.slice(0, 600) });
	} catch { wait.textContent = "unavailable — is the sync server running?"; }
}
$("#lcSend").onclick = chatSend;
$("#lcQ").addEventListener("keydown", (e) => { if (e.key === "Enter") chatSend(); });
logTimer();

