/**
 * encoding.ts — shared encode/decode helpers.
 *
 * CF Workers runtime: no Buffer, no Node crypto.
 * Use atob/btoa + Uint8Array only.
 */

/**
 * Convert a Uint8Array or ArrayBuffer to lowercase hex string.
 * Deterministic — same as inline .map(b => b.toString(16).padStart(2,'0')).join('').
 * Safe to blanket-replace all 29 inline occurrences.
 */
export function bytesToHex(bytes: Uint8Array | ArrayBuffer): string {
  const arr = ArrayBuffer.isView(bytes) ? bytes : new Uint8Array(bytes as ArrayBufferLike);
  return Array.from(arr)
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('');
}

/**
 * Decode a base64url string as UTF-8 text.
 */
export function base64UrlDecodeStr(str: string): string {
  return new TextDecoder().decode(base64UrlDecodeBytes(str));
}

/**
 * Decode a base64url string to raw bytes.
 * Use this for binary/crypto payloads (HMAC keys, nonces).
 * CF-safe: uses atob + charCodeAt, not Buffer.from.
 */
export function base64UrlDecodeBytes(str: string): Uint8Array {
  if (!/^[A-Za-z0-9_-]*={0,2}$/.test(str) || (str.indexOf('=') >= 0 && !/=+$/.test(str))) {
    throw new Error('Invalid base64url input');
  }
  const unpadded = str.replace(/=+$/, '');
  if (unpadded.length % 4 === 1 || str.length % 4 === 1) {
    throw new Error('Invalid base64url input');
  }
  const pad = '='.repeat((4 - (unpadded.length % 4)) % 4);
  let decoded: string;
  try {
    decoded = atob((unpadded + pad).replace(/-/g, '+').replace(/_/g, '/'));
  } catch (error) {
    throw new Error('Invalid base64url input', { cause: error });
  }
  return Uint8Array.from(decoded, (c) => c.charCodeAt(0));
}
