//#region src/crypto.ts
/**
* `@platform-modules/util/crypto` — AES-GCM secrets, SHA-256 hex, portable timing-safe compare.
*
* `timingSafeEqual` is safe only for equal-expected-length comparands (hashes, tokens).
* The length guard returns early — do not use for variable-length secrets.
*/
/** Coerce Uint8Array to BufferSource when DOM + @types/node widen ArrayBufferLike. */
function asBufferSource(bytes) {
	return bytes;
}
/**
* Portable constant-time string comparison (NOT `crypto.subtle.timingSafeEqual` — CF-only).
* Safe only when `a` and `b` are expected to be the same length (e.g. hex hashes).
*/
function timingSafeEqual(a, b) {
	const encoder = new TextEncoder();
	const bufA = encoder.encode(a);
	const bufB = encoder.encode(b);
	if (bufA.byteLength !== bufB.byteLength) return false;
	let diff = 0;
	for (let i = 0; i < bufA.byteLength; i++) diff |= bufA[i] ^ bufB[i];
	return diff === 0;
}
const GCM_IV_BYTES = 12;
function b64ToBytes(b64) {
	const binary = atob(b64);
	const out = new Uint8Array(binary.length);
	for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
	return out;
}
function bytesToB64(bytes) {
	let binary = "";
	for (const b of bytes) binary += String.fromCharCode(b);
	return btoa(binary);
}
async function importAesKey(keyB64) {
	const raw = b64ToBytes(keyB64);
	if (raw.byteLength !== 32) throw new Error("keyB64 must decode to exactly 32 bytes (AES-256)");
	return crypto.subtle.importKey("raw", asBufferSource(raw), { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
}
/** AES-256-GCM — returns base64( iv(12B) ‖ ciphertext+tag ). IV is random per call. */
async function encryptSecret(plaintext, keyB64) {
	const key = await importAesKey(keyB64);
	const iv = crypto.getRandomValues(new Uint8Array(GCM_IV_BYTES));
	const ct = await crypto.subtle.encrypt({
		name: "AES-GCM",
		iv: asBufferSource(iv)
	}, key, new TextEncoder().encode(plaintext));
	const ctBytes = new Uint8Array(ct);
	const combined = new Uint8Array(iv.byteLength + ctBytes.byteLength);
	combined.set(iv, 0);
	combined.set(ctBytes, iv.byteLength);
	return bytesToB64(combined);
}
/** Decrypts base64( iv(12B) ‖ ciphertext+tag ). Throws on tamper, wrong key, or malformed input. */
async function decryptSecret(blob, keyB64) {
	const key = await importAesKey(keyB64);
	const combined = b64ToBytes(blob);
	if (combined.byteLength <= GCM_IV_BYTES) throw new Error("ciphertext too short");
	const iv = combined.subarray(0, GCM_IV_BYTES);
	const ct = combined.subarray(GCM_IV_BYTES);
	const pt = await crypto.subtle.decrypt({
		name: "AES-GCM",
		iv: asBufferSource(iv)
	}, key, asBufferSource(ct));
	return new TextDecoder().decode(pt);
}
/** SHA-256 hex digest of `input`. */
async function sha256(input) {
	const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
	return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
}
/** URL-safe base64 without padding (`+`→`-`, `/`→`_`). */
function base64url(bytes) {
	const arr = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
	let binary = "";
	for (const b of arr) binary += String.fromCharCode(b);
	return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
async function importHmacKey(secret, usages) {
	return crypto.subtle.importKey("raw", asBufferSource(new TextEncoder().encode(secret)), {
		name: "HMAC",
		hash: "SHA-256"
	}, false, usages);
}
/**
* HMAC-SHA-256 of `message` under `secret`, returned as URL-safe base64 (no padding).
* Fail-closed on minting: throws on an empty `secret` rather than signing under a weak key.
*/
async function hmacSign(message, secret) {
	if (!secret) throw new Error("hmacSign: secret must be non-empty");
	const key = await importHmacKey(secret, ["sign"]);
	return base64url(await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(message)));
}
/**
* Constant-time verify of an `hmacSign` signature over `message` under `secret`.
* Fail-closed: returns false on an empty `secret` or any mismatch — never throws.
* Comparison is timing-safe (recompute-and-compare via {@link timingSafeEqual}).
*/
async function hmacVerify(message, secret, signature) {
	if (!secret) return false;
	return timingSafeEqual(await hmacSign(message, secret), signature);
}
//#endregion
export { base64url, decryptSecret, encryptSecret, hmacSign, hmacVerify, sha256, timingSafeEqual };
