/* ---- tiny markdown ---- */
function md(txt) {
	const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;");
	let t = esc(txt);
	t = t.replace(/```[a-z]*\n?([\s\S]*?)```/g, (m, code) => `<div class="cmd">${code.trim()}<button class="copy">copy</button></div>`);
	t = t.replace(/^ {0,3}-{3,} *$/gm, "<hr class=\"mdhr\">");
	t = t.replace(/^\|? *:?-{2,}.*$/gm, "");
	t = t.replace(/^\|(.+)\|? *$/gm, (m, row) => {
		const cells = row.split("|").map((c) => c.trim()).filter(Boolean);
		if (cells.length < 2) return m;
		return `<div class="live-row"><span class="lk">${cells[0]}</span><span class="lv">${cells.slice(1).join(" · ")}</span></div>`;
	});
	return t
		.replace(/^#{1,3} (.+)$/gm, "<h4>$1</h4>")
		.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>")
		.replace(/`([^`\n]+)`/g, "<code>$1</code>")
		.replace(/^[-*] (.+)$/gm, "<li>$1</li>")
		.replace(/^(\d+)\. (.+)$/gm, "<li><b>$1.</b> $2</li>")
		.replace(/\n{2,}/g, "<br>");
}

/* ---- sparkline charts ---- */
function sparkChart(series, { threshold, unit = "", color = "var(--blue)", fmt = (v) => v, marks = [] } = {}) {
	const W = 320, H = 110, L = 34, R = 6, T = 8, B = 16;
	if (!series?.length) return `<svg viewBox="0 0 ${W} ${H}"><text x="${W / 2}" y="${H / 2}" class="axis-txt" text-anchor="middle">no data</text></svg>`;
	const vals = series.map((p) => p.v);
	const hi = Math.max(...vals, threshold ?? 0) * 1.1 || 1;
	const x = (i) => L + (i / (series.length - 1 || 1)) * (W - L - R);
	const y = (v) => T + (H - T - B) * (1 - v / hi);
	const pts = series.map((p, i) => `${x(i)},${y(p.v)}`).join(" ");
	let h = `<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="none">`;
	[0.5, 1].forEach((f) => h += `<line class="gridline" x1="${L}" y1="${y(hi * f / 1.1)}" x2="${W - R}" y2="${y(hi * f / 1.1)}"></line>`);
	h += `<text class="axis-txt" x="${L - 4}" y="${y(hi / 1.1) + 3}" text-anchor="end">${fmt(+(hi / 1.1).toFixed(hi > 10 ? 0 : 2))}</text>`;
	h += `<text class="axis-txt" x="${L - 4}" y="${H - B + 3}" text-anchor="end">0</text>`;
	if (threshold) {
		h += `<line class="thresh" stroke="var(--warning)" x1="${L}" y1="${y(threshold)}" x2="${W - R}" y2="${y(threshold)}"></line>`;
	}
	h += `<polygon points="${x(0)},${y(0)} ${pts} ${x(series.length - 1)},${y(0)}" fill="color-mix(in srgb, ${color} 14%, transparent)"></polygon>`;
	h += `<polyline points="${pts}" fill="none" stroke="${color}" stroke-width="2"></polyline>`;
	marks.forEach((f) => {
		if (f < 0 || f > 1) return;
		const mx = L + f * (W - L - R);
		h += `<line x1="${mx}" y1="${T}" x2="${mx}" y2="${H - B}" stroke="var(--violet)" stroke-width="1.4" stroke-dasharray="2 3"><title>deploy</title></line>`;
		h += `<text x="${mx}" y="${H - B + 11}" text-anchor="middle" font-size="8" fill="var(--violet)">▲</text>`;
	});
	h += `</svg>`;
	return h;
}
function deployMarks() {
	const now = Date.now(), span = 3 * 3600 * 1000;
	const branch = CFG?.envs?.find((e) => e.key === ENV)?.branch;
	return (GH?.runs || [])
		.filter((r) => /deploy/i.test(r.workflowName) && r.headBranch === branch && r.conclusion === "success")
		.map((r) => (new Date(r.updatedAt).getTime() - (now - span)) / span)
		.filter((f) => f >= 0 && f <= 1);
}
function renderCharts() {
	const el = $("#ovCharts");
	if (!LIVE?.ok) { el.innerHTML = ""; return; }
	const s = LIVE.sections;
	const marks = deployMarks();
	const card = (title, series, opts, lastFmt) => {
		const last = series?.at(-1);
		return `<div class="chart-card"><h5>${title}<output>${last != null ? lastFmt(last) : "–"}</output></h5>${sparkChart(series?.map((p) => ({ v: p.v })) || [], opts)}</div>`;
	};
	const mem = (s.memory || []).map((p) => ({ v: p.max }));
	const cpu = (s.cpu || []).map((p) => ({ v: p.max }));
	const req = (s.requests || []).map((p) => ({ v: p.sum ?? 0 }));
	const lat = (s.latency || []).map((p) => ({ v: +((p.avg ?? 0) * 1000).toFixed(0) }));
	const fx = (s.fivexx || []).map((p) => ({ v: p.sum ?? 0 }));
	el.innerHTML =
		`<div class="chart-card"><h5><span>Memory · max task % ${extLink(awsConsoleUrl("ecs"))}</span><output>${mem.at(-1)?.v ?? "–"}%</output></h5>${sparkChart(mem, { threshold: SETTINGS.memWarn, marks })}</div>` +
		`<div class="chart-card"><h5><span>CPU · max task % ${extLink(awsConsoleUrl("ecs"))}</span><output>${cpu.at(-1)?.v ?? "–"}%</output></h5>${sparkChart(cpu, { threshold: 70, color: "var(--aqua)", marks })}</div>` +
		`<div class="chart-card"><h5>ALB requests / 5 min<output>${req.at(-1)?.v ?? "–"}</output></h5>${sparkChart(req, { color: "var(--violet)", marks })}</div>` +
		`<div class="chart-card"><h5>Response time · avg ms<output>${lat.at(-1)?.v ?? "–"} ms</output></h5>${sparkChart(lat, { threshold: 3000, color: "var(--yellow)", marks })}</div>` +
		`<div class="chart-card"><h5>Target 5xx / 5 min<output>${fx.at(-1)?.v ?? 0}</output></h5>${sparkChart(fx, { color: "var(--red)", marks })}</div>`;
}

/* ---- cost + activity ---- */
async function fetchCost() {
	try {
		const r = await fetch(`/api/cost?env=${encodeURIComponent(ENV)}`);
		const d = await r.json();
		if (!d.ok) { $("#costTotal").textContent = ""; $("#costRow").innerHTML = `<div class="tile"><div class="num">–</div><div class="lbl">${d.reason === "auth" ? "login required" : "cost data unavailable"}</div></div>`; return; }
		$("#costTotal").innerHTML = `· $${d.totalAll} ${extLink(awsConsoleUrl("cost"), "Cost Explorer")}`;
		$("#costRow").innerHTML = d.byService.map((g) => `<a class="tile" href="${awsConsoleUrl("cost")}" target="_blank" rel="noopener"><div class="num">$${g.amount}</div><div class="lbl">${g.service.slice(0, 26)}</div></a>`).join("");
	} catch { /* offline */ }
}
async function fetchActivity() {
	try {
		const r = await fetch(`/api/activity?env=${encodeURIComponent(ENV)}`);
		const d = await r.json();
		if (!d.ok) { $("#actList").innerHTML = `<div class="act-row">unavailable</div>`; return; }
		$("#actList").innerHTML = d.events.slice(0, 12).map((e) =>
			`<div class="act-row"><span class="at">${agoTxt(e.at)}</span><span><b>${e.name}</b> · ${e.source}</span><span class="who">${e.user}</span>${extLink(awsConsoleUrl("trail", e.name))}</div>`).join("") || `<div class="act-row">no recent events</div>`;
	} catch { /* offline */ }
}

