/**
 * Base64 encoding/decoding utilities.
 *
 * Uses native Uint8Array.prototype.toBase64 / Uint8Array.fromBase64 when
 * available (workerd, Node 26+, modern browsers), falls back to btoa/atob.
 *
 * All base64url encoding uses the { alphabet: "base64url" } option natively
 * or manual character replacement as fallback.
 *
 * Delete the fallback paths when the minimum Node version supports these
 * methods natively.
 */

const hasNative =
	typeof Uint8Array.prototype.toBase64 === "function" &&
	typeof Uint8Array.fromBase64 === "function";

// Regex patterns for base64url character replacement
const BASE64_PLUS_PATTERN = /\+/g;
const BASE64_SLASH_PATTERN = /\//g;
const BASE64_PADDING_PATTERN = /=+$/;
const BASE64URL_DASH_PATTERN = /-/g;
const BASE64URL_UNDERSCORE_PATTERN = /_/g;

// ---------------------------------------------------------------------------
// Standard base64 (for opaque tokens, cursors, Basic Auth, etc.)
// ---------------------------------------------------------------------------

/** Encode a UTF-8 string as standard base64. */
export function encodeBase64(str: string): string {
	const bytes = new TextEncoder().encode(str);
	if (hasNative) return bytes.toBase64();
	let binary = "";
	for (const b of bytes) binary += String.fromCharCode(b);
	return btoa(binary);
}

/** Decode a standard base64 string to a UTF-8 string. */
export function decodeBase64(base64: string): string {
	if (hasNative) return new TextDecoder().decode(Uint8Array.fromBase64(base64));
	const binary = atob(base64);
	const bytes = new Uint8Array(binary.length);
	for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
	return new TextDecoder().decode(bytes);
}

// ---------------------------------------------------------------------------
// Base64url (for tokens, HMAC signatures, PKCE, etc.)
// ---------------------------------------------------------------------------

/** Encode bytes as base64url without padding. */
export function encodeBase64url(bytes: Uint8Array): string {
	if (hasNative) return bytes.toBase64({ alphabet: "base64url", omitPadding: true });
	let binary = "";
	for (const b of bytes) binary += String.fromCharCode(b);
	return btoa(binary)
		.replace(BASE64_PLUS_PATTERN, "-")
		.replace(BASE64_SLASH_PATTERN, "_")
		.replace(BASE64_PADDING_PATTERN, "");
}

/** Decode a base64url string (with or without padding) to bytes. */
export function decodeBase64url(encoded: string): Uint8Array {
	if (hasNative) return Uint8Array.fromBase64(encoded, { alphabet: "base64url" });
	const base64 = encoded
		.replace(BASE64URL_DASH_PATTERN, "+")
		.replace(BASE64URL_UNDERSCORE_PATTERN, "/");
	const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4);
	const binary = atob(padded);
	const bytes = new Uint8Array(binary.length);
	for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
	return bytes;
}
