import { base64url } from "@platform-modules/util/crypto";
//#region src/webpush.ts
const RECORD_SIZE = 4096;
function base64urlDecode(input) {
	const padded = input.replace(/-/g, "+").replace(/_/g, "/");
	const pad = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
	const binary = atob(padded + pad);
	const out = new Uint8Array(binary.length);
	for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
	return out;
}
function concat(...parts) {
	const total = parts.reduce((n, p) => n + p.length, 0);
	const out = new Uint8Array(total);
	let offset = 0;
	for (const part of parts) {
		out.set(part, offset);
		offset += part.length;
	}
	return out;
}
function toArrayBuffer(bytes) {
	return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
}
async function hmacSha256(key, data) {
	const cryptoKey = await crypto.subtle.importKey("raw", toArrayBuffer(key), {
		name: "HMAC",
		hash: "SHA-256"
	}, false, ["sign"]);
	const sig = await crypto.subtle.sign("HMAC", cryptoKey, toArrayBuffer(data));
	return new Uint8Array(sig);
}
async function hkdfExpand(prk, info, length) {
	return (await hmacSha256(prk, concat(info, new Uint8Array([1])))).slice(0, length);
}
function payloadBytes(payload) {
	return typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
}
async function importUncompressedP256PublicKey(b64) {
	const raw = base64urlDecode(b64.replace(/\s+/g, ""));
	return crypto.subtle.importKey("raw", toArrayBuffer(raw), {
		name: "ECDH",
		namedCurve: "P-256"
	}, true, []);
}
async function vapidSigningKey(vapid) {
	const d = base64urlDecode(vapid.privateKey);
	const pub = base64urlDecode(vapid.publicKey.replace(/\s+/g, ""));
	const jwk = {
		kty: "EC",
		crv: "P-256",
		d: base64url(d),
		x: base64url(pub.slice(1, 33)),
		y: base64url(pub.slice(33, 65))
	};
	return crypto.subtle.importKey("jwk", jwk, {
		name: "ECDSA",
		namedCurve: "P-256"
	}, false, ["sign"]);
}
async function vapidVerifyKey(vapidPublicKey) {
	const pub = base64urlDecode(vapidPublicKey.replace(/\s+/g, ""));
	const jwk = {
		kty: "EC",
		crv: "P-256",
		x: base64url(pub.slice(1, 33)),
		y: base64url(pub.slice(33, 65))
	};
	return crypto.subtle.importKey("jwk", jwk, {
		name: "ECDSA",
		namedCurve: "P-256"
	}, false, ["verify"]);
}
async function deriveEcdhSecret(privateKey, peerPublicKey) {
	const bits = await crypto.subtle.deriveBits({
		name: "ECDH",
		public: peerPublicKey
	}, privateKey, 256);
	return new Uint8Array(bits);
}
async function computeWebPushIntermediates(opts) {
	const keyInfo = concat(new TextEncoder().encode("WebPush: info"), new Uint8Array([0]), opts.uaPublic, opts.asPublic);
	const prkKey = await hmacSha256(opts.authSecret, opts.ecdhSecret);
	const ikm = await hkdfExpand(prkKey, keyInfo, 32);
	const prk = await hmacSha256(opts.salt, ikm);
	const cekInfo = concat(new TextEncoder().encode("Content-Encoding: aes128gcm"), new Uint8Array([0]));
	const nonceInfo = concat(new TextEncoder().encode("Content-Encoding: nonce"), new Uint8Array([0]));
	const cek = await hkdfExpand(prk, cekInfo, 16);
	const nonce = await hkdfExpand(prk, nonceInfo, 12);
	return {
		ecdhSecret: opts.ecdhSecret,
		prkKey,
		ikm,
		prk,
		cek,
		nonce
	};
}
function buildAes128GcmHeader(salt, serverPublicKey) {
	const rs = /* @__PURE__ */ new Uint8Array(4);
	new DataView(rs.buffer).setUint32(0, RECORD_SIZE, false);
	return concat(salt, rs, new Uint8Array([serverPublicKey.length]), serverPublicKey);
}
/**
* Deterministic RFC 8291 aes128gcm encryption core — inject fixed salt + AS key pair for KAT.
*/
async function encryptWebPushPayloadWithKeyMaterial(opts) {
	const clientPublic = await importUncompressedP256PublicKey(opts.clientP256dh);
	const authSecret = base64urlDecode(opts.clientAuth);
	const uaPublic = base64urlDecode(opts.clientP256dh.replace(/\s+/g, ""));
	const asPublicRaw = await crypto.subtle.exportKey("raw", opts.asKeyPair.publicKey);
	const asPublic = new Uint8Array(asPublicRaw);
	const { cek, nonce } = await computeWebPushIntermediates({
		ecdhSecret: await deriveEcdhSecret(opts.asKeyPair.privateKey, clientPublic),
		authSecret,
		salt: opts.salt,
		uaPublic,
		asPublic
	});
	const padded = concat(payloadBytes(opts.payload), new Uint8Array([2]));
	const aesKey = await crypto.subtle.importKey("raw", toArrayBuffer(cek), "AES-GCM", false, ["encrypt"]);
	const ciphertext = new Uint8Array(await crypto.subtle.encrypt({
		name: "AES-GCM",
		iv: toArrayBuffer(nonce)
	}, aesKey, toArrayBuffer(padded)));
	return {
		body: concat(buildAes128GcmHeader(opts.salt, asPublic), ciphertext),
		salt: opts.salt,
		serverPublicKey: asPublic
	};
}
/** Production path — random salt + ephemeral AS ECDH key pair. */
async function encryptWebPushPayload(opts) {
	const salt = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
	const asKeyPair = await crypto.subtle.generateKey({
		name: "ECDH",
		namedCurve: "P-256"
	}, true, ["deriveBits"]);
	return encryptWebPushPayloadWithKeyMaterial({
		...opts,
		salt,
		asKeyPair
	});
}
async function createVapidJwt(vapid, audience, expSeconds) {
	const signingInput = `${base64url(new TextEncoder().encode(JSON.stringify({
		typ: "JWT",
		alg: "ES256"
	})))}.${base64url(new TextEncoder().encode(JSON.stringify({
		aud: audience,
		exp: expSeconds,
		sub: vapid.subject
	})))}`;
	const signingKey = await vapidSigningKey(vapid);
	const sig = new Uint8Array(await crypto.subtle.sign({
		name: "ECDSA",
		hash: "SHA-256"
	}, signingKey, new TextEncoder().encode(signingInput)));
	return `${signingInput}.${base64url(sig)}`;
}
function rawUncompressedToJwk(raw, includePrivate, d) {
	const x = raw.slice(1, 33);
	const y = raw.slice(33, 65);
	const jwk = {
		kty: "EC",
		crv: "P-256",
		x: base64url(x),
		y: base64url(y)
	};
	if (includePrivate && d) jwk.d = base64url(d);
	return jwk;
}
/** Build RFC 8291 AS key pair from fixed scalar + uncompressed public (KAT / tests). */
async function importRfc8291AsKeyPair(privateScalarB64, publicUncompressedB64) {
	const d = base64urlDecode(privateScalarB64);
	const pub = base64urlDecode(publicUncompressedB64.replace(/\s+/g, ""));
	const jwk = rawUncompressedToJwk(pub, true, d);
	return {
		privateKey: await crypto.subtle.importKey("jwk", jwk, {
			name: "ECDH",
			namedCurve: "P-256"
		}, true, ["deriveBits"]),
		publicKey: await crypto.subtle.importKey("raw", toArrayBuffer(pub), {
			name: "ECDH",
			namedCurve: "P-256"
		}, true, [])
	};
}
function createWebPushChannel(opts) {
	const http = opts.fetch ?? fetch;
	const ttl = opts.ttl ?? 2419200;
	return {
		channel: "webpush",
		async send(rendered, recipient) {
			const sub = recipient;
			try {
				const encrypted = await encryptWebPushPayload({
					payload: rendered.body,
					clientP256dh: sub.keys.p256dh,
					clientAuth: sub.keys.auth
				});
				const aud = new URL(sub.endpoint).origin;
				const exp = Math.floor(Date.now() / 1e3) + 43200;
				const jwt = await createVapidJwt(opts.vapid, aud, exp);
				const vapidPublic = opts.vapid.publicKey.replace(/\s+/g, "");
				const res = await http(sub.endpoint, {
					method: "POST",
					headers: {
						Authorization: `vapid t=${jwt}, k=${vapidPublic}`,
						"Content-Encoding": "aes128gcm",
						TTL: String(ttl),
						"Content-Type": "application/octet-stream"
					},
					body: toArrayBuffer(encrypted.body)
				});
				if (res.status === 410 || res.status === 404) return {
					ok: false,
					error: {
						message: "push subscription expired",
						code: "expired_subscription",
						retryable: false
					}
				};
				if (!res.ok) return {
					ok: false,
					error: {
						message: `push endpoint returned ${res.status}`,
						retryable: res.status >= 500
					}
				};
				return { ok: true };
			} catch (err) {
				return {
					ok: false,
					error: {
						message: err instanceof Error ? err.message : String(err),
						retryable: true
					}
				};
			}
		}
	};
}
/** Verify a VAPID JWT offline (ES256). */
async function verifyVapidJwt(jwt, vapidPublicKey, expectedAud) {
	const [headerB64, payloadB64, sigB64] = jwt.split(".");
	if (!headerB64 || !payloadB64 || !sigB64) return false;
	const payload = JSON.parse(new TextDecoder().decode(base64urlDecode(payloadB64)));
	if (payload.aud !== expectedAud) return false;
	if (typeof payload.exp !== "number" || payload.exp < Math.floor(Date.now() / 1e3)) return false;
	const verifyKey = await vapidVerifyKey(vapidPublicKey);
	return crypto.subtle.verify({
		name: "ECDSA",
		hash: "SHA-256"
	}, verifyKey, toArrayBuffer(base64urlDecode(sigB64)), new TextEncoder().encode(`${headerB64}.${payloadB64}`));
}
//#endregion
export { computeWebPushIntermediates, createVapidJwt, createWebPushChannel, deriveEcdhSecret, encryptWebPushPayload, encryptWebPushPayloadWithKeyMaterial, importRfc8291AsKeyPair, importUncompressedP256PublicKey, verifyVapidJwt };
