<script lang="ts">
	import { getAuth } from "$lib/auth.svelte";
	import { createBobbinClient } from "$lib/api/client";
	import {
		listPubKeys,
		deletePubKey,
		type ListedRecord,
		type PublicKeyRecord
	} from "$lib/api/settings";
	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 EmptyState from "$lib/components/ui/EmptyState.svelte";
	import TimeAgo from "$lib/components/ui/TimeAgo.svelte";
	import SettingsHeader from "../SettingsHeader.svelte";
	import SettingsToolbar from "../SettingsToolbar.svelte";
	import SettingsList from "../SettingsList.svelte";
	import SettingsEntry from "../SettingsEntry.svelte";
	import Plus from "$icon/plus";
	import Trash from "$icon/trash-2";
	import Key from "$icon/key";

	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 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 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>

<SettingsHeader
	title="Keys"
	description="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."
/>

<div class="flex w-full flex-col gap-4">
	<SettingsToolbar>
		{#snippet end()}
			<Button href="/settings/keys/new" icon={Plus}>Add key</Button>
		{/snippet}
	</SettingsToolbar>

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

	{#if loading}
		<div
			class="flex items-center justify-center gap-2 p-4 typography-paragraph-regular text-foreground-subtle"
		>
			<Spinner /> Loading…
		</div>
	{:else if keys.length === 0}
		<EmptyState message="No keys added yet" />
	{:else}
		<SettingsList>
			{#each keys as k (k.rkey)}
				<SettingsEntry icon={Key} title={k.value.name}>
					{#snippet meta()}
						{#if fingerprints[k.rkey]}
							<span class="font-mono typography-monospace-regular break-all text-foreground-muted">
								{fingerprints[k.rkey]}
							</span>
						{/if}
						<span class="typography-paragraph-regular text-foreground-muted">
							added <TimeAgo value={k.value.createdAt} variant="full" />
						</span>
					{/snippet}
					{#snippet actions()}
						<Button
							variant="danger"
							icon={Trash}
							loading={deleting === k.rkey}
							onclick={() => remove(k)}
						>
							Delete
						</Button>
					{/snippet}
				</SettingsEntry>
			{/each}
		</SettingsList>
	{/if}
</div>
