/**
 * Isomorphic base64url (RFC 4648 §5, no padding) over bytes and UTF-8 strings.
 * Hand-rolled (no Buffer / no atob) so both the Node relay (P1) and the browser
 * bundle (P6) import it unchanged and agree byte-for-byte on the §4.3 token subprotocol.
 */
import { ContractDecodeError } from './errors.js'

const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
const DECODE_MAP: Readonly<Record<string, number>> = Object.fromEntries(
  [...ALPHABET].map((ch, i) => [ch, i]),
)

const textEncoder = new TextEncoder()
const textDecoder = new TextDecoder('utf-8', { fatal: true })

/** Encode raw bytes to a base64url string (no `=` padding). */
export function encodeBase64UrlBytes(bytes: Uint8Array): string {
  let out = ''
  for (let i = 0; i < bytes.length; i += 3) {
    const b0 = bytes[i]!
    const b1 = i + 1 < bytes.length ? bytes[i + 1]! : 0
    const b2 = i + 2 < bytes.length ? bytes[i + 2]! : 0
    const triple = (b0 << 16) | (b1 << 8) | b2
    const remaining = bytes.length - i
    out += ALPHABET[(triple >> 18) & 0x3f]
    out += ALPHABET[(triple >> 12) & 0x3f]
    if (remaining > 1) out += ALPHABET[(triple >> 6) & 0x3f]
    if (remaining > 2) out += ALPHABET[triple & 0x3f]
  }
  return out
}

/** Decode a base64url string (padding tolerated) back to raw bytes. */
export function decodeBase64UrlBytes(value: string): Uint8Array {
  const clean = value.replace(/=+$/, '')
  if (/[^A-Za-z0-9\-_]/.test(clean)) {
    throw new ContractDecodeError('invalid base64url: contains non-alphabet characters')
  }
  const bytes: number[] = []
  for (let i = 0; i < clean.length; i += 4) {
    const c0 = DECODE_MAP[clean[i]!]!
    const c1 = i + 1 < clean.length ? DECODE_MAP[clean[i + 1]!]! : 0
    const c2 = i + 2 < clean.length ? DECODE_MAP[clean[i + 2]!] : undefined
    const c3 = i + 3 < clean.length ? DECODE_MAP[clean[i + 3]!] : undefined
    const triple = (c0 << 18) | (c1 << 12) | ((c2 ?? 0) << 6) | (c3 ?? 0)
    bytes.push((triple >> 16) & 0xff)
    if (c2 !== undefined) bytes.push((triple >> 8) & 0xff)
    if (c3 !== undefined) bytes.push(triple & 0xff)
  }
  return Uint8Array.from(bytes)
}

/** Encode a UTF-8 string to base64url. */
export function encodeBase64UrlString(value: string): string {
  return encodeBase64UrlBytes(textEncoder.encode(value))
}

/** Decode a base64url string to a UTF-8 string. */
export function decodeBase64UrlString(value: string): string {
  try {
    return textDecoder.decode(decodeBase64UrlBytes(value))
  } catch (err) {
    if (err instanceof ContractDecodeError) throw err
    throw new ContractDecodeError('base64url payload is not valid UTF-8')
  }
}
