// `WebCryptoKeyStore` persists exactly one Ed25519 device keypair in
// IndexedDB using the WebCrypto subtle API. The private key is
// generated with `extractable: false`, which means even the page that
// owns the handle cannot read out the raw bytes — only the platform's
// signing primitive can use it. The matching public key is forced
// extractable by the WebCrypto spec for asymmetric pairs (you cannot
// publish a key you cannot read), and we cache its raw 32-byte
// export alongside the handle so consumers do not pay an exportKey
// round-trip on every page load.
//
// Both `CryptoKey` handles round-trip through IndexedDB's
// structured-clone path; the non-exportable property is preserved
// across page reloads. The browser baseline is documented in
// `ui/docs/storage.md` (Chrome ≥137, Firefox ≥130, Safari ≥17.4).

import type { IDBPDatabase } from "idb";
import type { DeviceKeypair, KeyStore } from "./index";
import {
	DEVICE_KEYPAIR_KEY,
	type GalaxyDB,
	type KeypairRow,
} from "./idb";

const ED25519_ALG = { name: "Ed25519" } as const;

export class WebCryptoKeyStore implements KeyStore {
	constructor(private readonly db: IDBPDatabase<GalaxyDB>) {}

	async load(): Promise<DeviceKeypair | null> {
		const row = await this.db.get("keypair", DEVICE_KEYPAIR_KEY);
		if (!row) {
			return null;
		}
		return makeDeviceKeypair(row);
	}

	async generate(): Promise<DeviceKeypair> {
		const pair = (await crypto.subtle.generateKey(
			ED25519_ALG,
			false,
			["sign", "verify"],
		)) as CryptoKeyPair;
		const publicKeyRaw = await crypto.subtle.exportKey("raw", pair.publicKey);
		const row: KeypairRow = {
			privateKey: pair.privateKey,
			publicKey: pair.publicKey,
			publicKeyRaw,
		};
		await this.db.put("keypair", row, DEVICE_KEYPAIR_KEY);
		return makeDeviceKeypair(row);
	}

	async clear(): Promise<void> {
		await this.db.delete("keypair", DEVICE_KEYPAIR_KEY);
	}
}

function makeDeviceKeypair(row: KeypairRow): DeviceKeypair {
	const publicKey = new Uint8Array(row.publicKeyRaw);
	return {
		publicKey,
		async sign(canonicalBytes: Uint8Array): Promise<Uint8Array> {
			// TS 5.7+ tightened BufferSource to require an ArrayBuffer-
			// backed view; an arbitrary Uint8Array could in principle be
			// SharedArrayBuffer-backed. The signature input is not, so
			// the cast is safe and keeps the public Signer type
			// (Uint8Array) un-narrowed.
			const signature = await crypto.subtle.sign(
				ED25519_ALG,
				row.privateKey,
				canonicalBytes as BufferSource,
			);
			return new Uint8Array(signature);
		},
	};
}
