<script lang="ts">
	import { getAuth } from "$lib/auth.svelte";
	import { createBobbinClient } from "$lib/api/client";
	import {
		listPubKeys,
		createPubKey,
		deletePubKey,
		type ListedRecord,
		type PublicKeyRecord
	} from "$lib/api/settings";
	import Section from "$lib/components/ui/Section.svelte";
	import Button from "$lib/components/ui/Button.svelte";
	import Spinner from "$lib/components/ui/Spinner.svelte";
	import ErrorAlert from "$lib/components/ui/Error.svelte";
	import KeyCard from "../KeyCard.svelte";
	import AddKeyForm from "../AddKeyForm.svelte";
	import Plus from "$icon/plus";

	const auth = getAuth();
	const user = $derived(auth.currentUser);

	let keys = $state<ListedRecord<PublicKeyRecord>[]>([]);
	let fingerprints = $state<Record<string, string>>({});
	let loading = $state(true);
	let loadError = $state<string | null>(null);

	let popover = $state<HTMLElement>();
	let name = $state("");
	let key = $state("");
	let submitting = $state(false);
	let formError = $state<string | null>(null);
	let deleting = $state<string | null>(null);

	// ssh SHA256 fingerprint of the base64 key blob, matching `ssh-keygen -lf`.
	const sshFingerprint = async (pubkey: string): Promise<string | null> => {
		const parts = pubkey.trim().split(/\s+/);
		const blob = parts.length >= 2 ? parts[1] : parts[0];
		try {
			const bytes = Uint8Array.from(atob(blob), (c) => c.charCodeAt(0));
			const digest = await crypto.subtle.digest("SHA-256", bytes);
			const b64 = btoa(String.fromCharCode(...new Uint8Array(digest))).replace(/=+$/, "");
			return `SHA256:${b64}`;
		} catch {
			return null;
		}
	};

	const load = async () => {
		const did = user?.did;
		if (!did) return;
		loading = true;
		loadError = null;
		try {
			const ctx = createBobbinClient({ serviceUrl: auth.bobbinUrl });
			keys = await listPubKeys(ctx, did);
			const entries = await Promise.all(
				keys.map(async (k) => [k.rkey, await sshFingerprint(k.value.key)] as const)
			);
			fingerprints = Object.fromEntries(
				entries.filter(([, fp]) => fp !== null) as [string, string][]
			);
		} catch (err) {
			loadError = err instanceof Error ? err.message : "Failed to load keys";
		} finally {
			loading = false;
		}
	};

	$effect(() => {
		if (user?.did) void load();
	});

	const resetForm = () => {
		name = "";
		key = "";
		formError = null;
	};

	const add = async () => {
		if (!auth.agent) return;
		submitting = true;
		formError = null;
		try {
			const trimmedName = name.trim();
			const trimmedKey = key.trim();
			const rkey = await createPubKey(auth.agent, trimmedName, trimmedKey);
			const createdAt = new Date().toISOString();
			// prepend to local state (newest first) instead of refetching everything.
			keys = [
				{
					uri: `at://${auth.agent.sub}/sh.tangled.publicKey/${rkey}`,
					rkey,
					value: { $type: "sh.tangled.publicKey", name: trimmedName, key: trimmedKey, createdAt }
				},
				...keys
			];
			const fp = await sshFingerprint(trimmedKey);
			if (fp) fingerprints = { ...fingerprints, [rkey]: fp };
			resetForm();
			popover?.hidePopover();
		} catch (err) {
			formError = err instanceof Error ? err.message : "Failed to add key";
		} finally {
			submitting = false;
		}
	};

	const remove = async (k: ListedRecord<PublicKeyRecord>) => {
		if (!auth.agent) return;
		if (!confirm(`Are you sure you want to delete the key ${k.value.name}?`)) return;
		deleting = k.rkey;
		try {
			await deletePubKey(auth.agent, k.rkey);
			// drop just this key from local state instead of refetching everything.
			keys = keys.filter((x) => x.rkey !== k.rkey);
		} catch (err) {
			loadError = err instanceof Error ? err.message : "Failed to delete key";
		} finally {
			deleting = null;
		}
	};
</script>

<div class="flex flex-col gap-4">
	<div>
		<div class="flex items-center justify-between gap-4">
			<h2 class="text-paragraph-large font-medium text-foreground-default">SSH keys</h2>
			<Button variant="primary" icon={Plus} popovertarget="add-key-popover">Add</Button>
		</div>
		<p class="pt-4 text-sm text-foreground-subtle">
			SSH public keys added here will be broadcasted to knots that you are a member of, allowing you
			to push to repositories there and verify commit signatures.
		</p>
	</div>

	<div
		bind:this={popover}
		id="add-key-popover"
		popover="auto"
		ontoggle={(e) => {
			if ((e as ToggleEvent).newState === "closed") resetForm();
		}}
		class="m-auto w-96 max-w-[90vw] rounded border border-border-default bg-background-default p-4 text-foreground-default shadow-lg backdrop:bg-black/40"
	>
		<AddKeyForm
			bind:name
			bind:key
			{submitting}
			error={formError}
			onSubmit={add}
			onCancel={() => popover?.hidePopover()}
		/>
	</div>

	{#if loadError}
		<ErrorAlert label={loadError} />
	{/if}

	{#if loading}
		<div class="flex items-center justify-center gap-2 p-4 text-sm text-foreground-subtle">
			<Spinner /> Loading…
		</div>
	{:else}
		<Section
			empty={keys.length === 0}
			emptyMessage="No keys added yet."
			listClass="flex flex-col gap-4"
		>
			{#each keys as k (k.rkey)}
				<KeyCard
					name={k.value.name}
					fingerprint={fingerprints[k.rkey]}
					createdAt={k.value.createdAt}
					deleting={deleting === k.rkey}
					onDelete={() => remove(k)}
				/>
			{/each}
		</Section>
	{/if}
</div>
