import { api, protectedFetch } from "./api.js?v=__ASSET_VERSION__";
import { applyAvatar, initSideNav, loadNavUser } from "./nav.js?v=__ASSET_VERSION__";
import { showToast } from "./toast.js?v=__ASSET_VERSION__";

function fmtDate(value) {
	return value ? new Date(value).toLocaleString() : "—";
}

function friendlyDevice(userAgent) {
	if (!userAgent) return "Unknown device";
	const browser = /Edg\//.test(userAgent)
		? "Edge"
		: /OPR\//.test(userAgent)
			? "Opera"
			: /Chrome\//.test(userAgent)
				? "Chrome"
				: /CriOS\//.test(userAgent)
					? "Chrome"
					: /Firefox\//.test(userAgent)
						? "Firefox"
						: /Safari\//.test(userAgent)
							? "Safari"
							: "Browser";
	const os = /Windows/.test(userAgent)
		? "Windows"
		: /iPhone|iPad/.test(userAgent)
			? "iOS"
			: /Mac OS X/.test(userAgent)
				? "macOS"
				: /Android/.test(userAgent)
					? "Android"
					: /Linux/.test(userAgent)
						? "Linux"
						: "";
	return os ? `${browser} on ${os}` : browser;
}

async function loadProfile(user) {
	document.getElementById("profile-email").textContent = user.email;
	document
		.getElementById("profile-role-admin")
		.classList.toggle("hidden", user.role !== "admin");
	document.getElementById("email-form").email.value = user.email;
}

async function loadSessions() {
	const body = document.getElementById("sessions-body");
	try {
		const { sessions } = await api("/api/account/sessions");
		if (!sessions.length) {
			body.innerHTML = `<tr><td colspan="4" class="empty-state">No active sessions.</td></tr>`;
			return;
		}
		body.replaceChildren(
			...sessions.map((session) => {
				const tr = document.createElement("tr");
				const device = document.createElement("td");
				const label = document.createElement("span");
				label.className = "session-device-label";
				label.textContent = friendlyDevice(session.userAgent);
				if (session.userAgent) label.title = session.userAgent;
				device.append(label);
				if (session.current) {
					const badge = document.createElement("span");
					badge.className = "badge badge-current";
					badge.textContent = "This device";
					badge.style.marginLeft = "8px";
					device.append(badge);
				}
				const lastSeen = document.createElement("td");
				lastSeen.textContent = fmtDate(session.lastSeenAt || session.createdAt);
				const expires = document.createElement("td");
				expires.textContent = fmtDate(session.expiresAt);
				const actions = document.createElement("td");
				const revoke = document.createElement("button");
				revoke.className = "ghost-btn small";
				revoke.type = "button";
				revoke.textContent = session.current ? "Sign out" : "Revoke";
				revoke.addEventListener("click", async () => {
					try {
						await api(`/api/account/sessions/${session.id}`, {
							method: "DELETE",
						});
						if (session.current) return location.assign("/login");
						showToast("Session revoked.");
						loadSessions();
					} catch (error) {
						showToast(error.message, "fail");
					}
				});
				actions.append(revoke);
				tr.append(device, lastSeen, expires, actions);
				return tr;
			}),
		);
	} catch (error) {
		body.innerHTML = `<tr><td colspan="4" class="empty-state">Could not load sessions.</td></tr>`;
	}
}

async function loadPlans() {
	const body = document.getElementById("plans-body");
	try {
		const response = await fetch("/api/plans");
		if (!response.ok) throw new Error("could not load plans");
		const { plans } = await response.json();
		if (!plans.length) {
			body.innerHTML = `<tr><td colspan="3" class="empty-state">No saved plans yet.</td></tr>`;
			return;
		}
		body.replaceChildren(
			...plans.map((plan) => {
				const tr = document.createElement("tr");
				const title = document.createElement("td");
				title.textContent = plan.plan?.fields?.["0.1"] || "Untitled plan";
				const updated = document.createElement("td");
				updated.textContent = fmtDate(plan.updated_at);
				const actions = document.createElement("td");
				actions.className = "data-table-actions";

				const open = document.createElement("a");
				open.className = "ghost-btn small";
				open.href = `/app?plan=${encodeURIComponent(plan.id)}`;
				open.textContent = "Open";

				const duplicate = document.createElement("button");
				duplicate.className = "ghost-btn small";
				duplicate.type = "button";
				duplicate.textContent = "Duplicate";
				duplicate.addEventListener("click", async () => {
					try {
						await api("/api/plans", {
							method: "POST",
							body: JSON.stringify({ plan: plan.plan }),
						});
						showToast("Plan duplicated.");
						loadPlans();
					} catch (error) {
						showToast(error.message, "fail");
					}
				});

				const download = document.createElement("button");
				download.className = "ghost-btn small";
				download.type = "button";
				download.textContent = "Download";
				download.addEventListener("click", () => {
					const blob = new Blob([JSON.stringify(plan.plan, null, 2)], {
						type: "application/json",
					});
					const a = document.createElement("a");
					a.href = URL.createObjectURL(blob);
					a.download = `${(plan.plan?.fields?.["0.1"] || "roast-plan").replace(/[^\w-]+/g, "_")}.json`;
					a.click();
					URL.revokeObjectURL(a.href);
				});

				const cup = document.createElement("button");
				cup.className = "ghost-btn small";
				cup.type = "button";
				cup.textContent = "Cup";
				cup.addEventListener("click", async () => {
					cup.disabled = true;
					try {
						const existing = await api(`/api/cupping?plan=${encodeURIComponent(plan.id)}`);
						if (existing.sessions?.length) {
							location.assign(`/cupping?session=${existing.sessions[0].id}`);
							return;
						}
						const created = await api("/api/cupping", {
							method: "POST",
							body: JSON.stringify({ roastPlanId: plan.id }),
						});
						location.assign(`/cupping?session=${created.session.id}`);
					} catch (error) {
						showToast(error.message, "fail");
						cup.disabled = false;
					}
				});

				const del = document.createElement("button");
				del.className = "ghost-btn small";
				del.type = "button";
				del.textContent = "Delete";
				del.addEventListener("click", async () => {
					if (!confirm("Delete this plan? This cannot be undone.")) return;
					try {
						await api(`/api/plans/${plan.id}`, { method: "DELETE" });
						showToast("Plan deleted.");
						loadPlans();
					} catch (error) {
						showToast(error.message, "fail");
					}
				});

				actions.append(open, duplicate, download, cup, del);
				tr.append(title, updated, actions);
				return tr;
			}),
		);
	} catch {
		body.innerHTML = `<tr><td colspan="3" class="empty-state">Could not load plans.</td></tr>`;
	}
}

/** Disables `button` for the duration of `run()` so a slow request can't be double-submitted. */
async function guarded(button, run) {
	if (button.disabled) return;
	button.disabled = true;
	try {
		await run();
	} finally {
		button.disabled = false;
	}
}

function wireForms(user) {
	document.getElementById("email-form").addEventListener("submit", (event) => {
		event.preventDefault();
		const data = Object.fromEntries(new FormData(event.target));
		guarded(event.submitter || event.target.querySelector("button[type=submit]"), async () => {
			try {
				await api("/api/account/email", { method: "PUT", body: JSON.stringify(data) });
				showToast("Email updated.");
				document.getElementById("profile-email").textContent = data.email;
				event.target.password.value = "";
			} catch (error) {
				showToast(
					error.code === "email_exists"
						? "That email is already in use."
						: error.message,
					"fail",
				);
			}
		});
	});

	document
		.getElementById("password-form")
		.addEventListener("submit", (event) => {
			event.preventDefault();
			const data = Object.fromEntries(new FormData(event.target));
			if (data.newPassword !== data.confirmPassword) {
				showToast("New passwords do not match.", "fail");
				return;
			}
			guarded(event.submitter || event.target.querySelector("button[type=submit]"), async () => {
				try {
					await api("/api/account/password", {
						method: "PUT",
						body: JSON.stringify(data),
					});
					showToast("Password updated. Other sessions were signed out.");
					event.target.reset();
					loadSessions();
				} catch (error) {
					showToast(error.message, "fail");
				}
			});
		});

	document
		.getElementById("btn-revoke-others")
		.addEventListener("click", (event) => {
			guarded(event.currentTarget, async () => {
				try {
					await api("/api/account/sessions/revoke-others", { method: "POST" });
					showToast("Other sessions signed out.");
					loadSessions();
				} catch (error) {
					showToast(error.message, "fail");
				}
			});
		});

	document.getElementById("delete-form").addEventListener("submit", (event) => {
		event.preventDefault();
		const data = Object.fromEntries(new FormData(event.target));
		if (data.confirmEmail.trim().toLowerCase() !== user.email.toLowerCase()) {
			showToast("Type your email exactly to confirm.", "fail");
			return;
		}
		if (!confirm("This permanently deletes your account and plans. Continue?"))
			return;
		guarded(event.submitter || event.target.querySelector("button[type=submit]"), async () => {
			try {
				await api("/api/account", {
					method: "DELETE",
					body: JSON.stringify({ password: data.password }),
				});
				location.assign("/");
			} catch (error) {
				showToast(
					error.code === "last_admin"
						? "You are the only administrator — promote another admin first."
						: error.message,
					"fail",
				);
			}
		});
	});
}

function wirePwaButtons() {
	let deferredInstallPrompt = null;
	window.addEventListener("beforeinstallprompt", (event) => {
		event.preventDefault();
		deferredInstallPrompt = event;
		document.getElementById("btn-install").classList.remove("hidden");
	});
	document.getElementById("btn-install").addEventListener("click", async () => {
		if (!deferredInstallPrompt) return;
		deferredInstallPrompt.prompt();
		await deferredInstallPrompt.userChoice;
		deferredInstallPrompt = null;
		document.getElementById("btn-install").classList.add("hidden");
	});
	if ("serviceWorker" in navigator) {
		const hadController = !!navigator.serviceWorker.controller;
		navigator.serviceWorker.getRegistration().then((registration) => {
			if (registration?.waiting)
				document.getElementById("btn-refresh").classList.remove("hidden");
		});
		navigator.serviceWorker.addEventListener("controllerchange", () => {
			if (hadController) location.reload();
		});
	}
	document.getElementById("btn-refresh").addEventListener("click", async () => {
		const registration = await navigator.serviceWorker.getRegistration();
		registration?.waiting?.postMessage("SKIP_WAITING");
	});
}

// Profile picture: resized client-side to a small square JPEG data URL before upload, so the
// server only ever stores a few tens of KB per user.
function wireAvatar(user) {
	const refresh = (hasAvatar) => {
		const shownUser = { ...user, hasAvatar };
		applyAvatar(document.getElementById("profile-avatar"), shownUser, true);
		applyAvatar(document.getElementById("nav-user-avatar"), shownUser, true);
		document.getElementById("avatar-remove").classList.toggle("hidden", !hasAvatar);
	};
	refresh(user.hasAvatar);
	document.getElementById("avatar-file").addEventListener("change", async (event) => {
		const file = event.target.files?.[0];
		event.target.value = "";
		if (!file) return;
		try {
			const bitmap = await createImageBitmap(file);
			const size = 128;
			const canvas = document.createElement("canvas");
			canvas.width = size;
			canvas.height = size;
			const context = canvas.getContext("2d");
			// Cover-crop the centre square so faces aren't squished.
			const side = Math.min(bitmap.width, bitmap.height);
			context.drawImage(
				bitmap,
				(bitmap.width - side) / 2,
				(bitmap.height - side) / 2,
				side,
				side,
				0,
				0,
				size,
				size,
			);
			const dataUrl = canvas.toDataURL("image/jpeg", 0.85);
			await api("/api/account/avatar", {
				method: "PUT",
				body: JSON.stringify({ dataUrl }),
			});
			showToast("Profile picture updated.");
			refresh(true);
		} catch (error) {
			showToast(error.message || "Could not read that image.", "fail");
		}
	});
	document.getElementById("avatar-remove").addEventListener("click", async () => {
		try {
			await api("/api/account/avatar", { method: "DELETE" });
			showToast("Profile picture removed.");
			refresh(false);
		} catch (error) {
			showToast(error.message, "fail");
		}
	});
}

async function loadTokens() {
	const body = document.getElementById("tokens-body");
	try {
		const { tokens } = await api("/api/tokens");
		if (!tokens.length) {
			body.innerHTML = `<tr><td colspan="4" class="empty-state">No API tokens yet.</td></tr>`;
			return;
		}
		body.replaceChildren(
			...tokens.map((token) => {
				const tr = document.createElement("tr");
				const name = document.createElement("td");
				name.textContent = token.name || "(unnamed)";
				const created = document.createElement("td");
				created.textContent = fmtDate(token.createdAt);
				const used = document.createElement("td");
				used.textContent = token.lastUsedAt ? fmtDate(token.lastUsedAt) : "Never";
				const actions = document.createElement("td");
				actions.className = "data-table-actions";
				const revoke = document.createElement("button");
				revoke.className = "ghost-btn small";
				revoke.type = "button";
				revoke.textContent = "Revoke";
				revoke.addEventListener("click", async () => {
					if (!confirm(`Revoke "${token.name || "this token"}"? Anything using it stops working immediately.`))
						return;
					try {
						await api(`/api/tokens/${token.id}`, { method: "DELETE" });
						showToast("Token revoked.");
						await loadTokens();
					} catch (error) {
						showToast(error.message, "fail");
					}
				});
				actions.append(revoke);
				tr.append(name, created, used, actions);
				return tr;
			}),
		);
	} catch {
		body.innerHTML = `<tr><td colspan="4" class="empty-state">Could not load tokens.</td></tr>`;
	}
}

function wireTokenForm() {
	document.getElementById("token-form").addEventListener("submit", async (event) => {
		event.preventDefault();
		const button = document.getElementById("token-create");
		button.disabled = true;
		try {
			const created = await api("/api/tokens", {
				method: "POST",
				body: JSON.stringify({ name: event.target.name.value.trim() }),
			});
			// Shown exactly once — the server only stores a hash.
			document.getElementById("token-reveal").textContent = created.token;
			document.getElementById("token-reveal-wrap").classList.remove("hidden");
			event.target.reset();
			await loadTokens();
		} catch (error) {
			showToast(error.message, "fail");
		} finally {
			button.disabled = false;
		}
	});
}

document.getElementById("btn-logout").addEventListener("click", async () => {
	await protectedFetch("/api/auth/logout", { method: "POST" });
	location.assign("/");
});

async function init() {
	initSideNav();
	const user = await loadNavUser();
	if (!user) return;
	document.getElementById("profile-since").textContent =
		`Member since ${fmtDate(user.createdAt)}`;
	await loadProfile(user);
	wireForms(user);
	wirePwaButtons();
	wireTokenForm();
	wireAvatar(user);
	await Promise.all([loadSessions(), loadPlans(), loadTokens()]);
}

init();
