<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 SettingsEmpty from "../SettingsEmpty.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 { createAction, createLoad } from "$lib/action.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);

	type KeysData = {
		keys: ListedRecord<PublicKeyRecord>[];
		fingerprints: Record<string, string>;
	};
	// 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 loaded = createLoad(async (): Promise<KeysData> => {
		const serviceUrl = auth.bobbinUrl;
		const did = user?.did;
		if (!did) return { keys: [], fingerprints: {} };
		const ctx = createBobbinClient({ serviceUrl });
		const keys = await listPubKeys(ctx, did);
		const entries = await Promise.all(
			keys.map(async (k) => [k.rkey, await sshFingerprint(k.value.key)] as const)
		);
		const fingerprints = Object.fromEntries(
			entries.filter(([, fp]) => fp !== null) as [string, string][]
		);
		return { keys, fingerprints };
	});

	const removeKey = createAction(async (key: ListedRecord<PublicKeyRecord>) => {
		const agent = auth.agent;
		if (!agent) return;
		await deletePubKey(agent, key.rkey);
		// drop just this key from local state instead of refetching everything.
		loaded.update((data) => ({
			...data,
			keys: data.keys.filter((candidate) => candidate.rkey !== key.rkey)
		}));
	});

	const remove = (key: ListedRecord<PublicKeyRecord>) => {
		if (!auth.agent || removeKey.loading) return;
		if (!confirm(`Are you sure you want to delete the key ${key.value.name}?`)) return;
		void removeKey.run(key);
	};
</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>

	<ErrorAlert label={loaded.error} />
	<ErrorAlert label={removeKey.error} />

	{#if loaded.loading}
		<div
			class="flex items-center justify-center gap-2 p-4 typography-paragraph-regular text-foreground-subtle"
		>
			<Spinner /> Loading…
		</div>
	{:else if loaded.data?.keys.length === 0}
		<SettingsEmpty message="No keys added yet" />
	{:else if loaded.data}
		{@const data = loaded.data}
		<SettingsList>
			{#each data.keys as k (k.rkey)}
				<SettingsEntry icon={Key} title={k.value.name}>
					{#snippet meta()}
						{#if data.fingerprints[k.rkey]}
							<span class="typography-monospace-regular font-mono break-all text-foreground-muted">
								{data.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={removeKey.loading && removeKey.args?.[0].rkey === k.rkey}
							onclick={() => remove(k)}
						>
							Delete
						</Button>
					{/snippet}
				</SettingsEntry>
			{/each}
		</SettingsList>
	{/if}
</div>
