// RFC 9285 Base45 encoding (strict)
const ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";

const idx = (() => {
    const m = new Map<string, number>();
    for (let i = 0; i < ALPHABET.length; i++) m.set(ALPHABET[i], i);
    return m;
})();

export function base45Encode(bytes: Uint8Array): string {
    let out = "";
    for (let i = 0; i < bytes.length; i += 2) {
        if (i + 1 < bytes.length) {
            const x = bytes[i] * 256 + bytes[i + 1];
            const c = x % 45;
            const d = Math.floor(x / 45) % 45;
            const e = Math.floor(x / (45 * 45));
            out += ALPHABET[c] + ALPHABET[d] + ALPHABET[e];
        } else {
            const x = bytes[i];
            const c = x % 45;
            const d = Math.floor(x / 45);
            out += ALPHABET[c] + ALPHABET[d];
        }
    }
    return out;
}

export function base45Decode(text: string): Uint8Array {
    if (!text.length) return new Uint8Array();

    for (const ch of text) {
        if (!idx.has(ch)) {
            throw new Error(`Base45 decode: invalid character '${ch}' (position ${text.indexOf(ch)})`);
        }
    }

    if (text.length % 3 === 1) {
        throw new Error("Base45 decode: invalid length (length mod 3 == 1)");
    }

    const out: number[] = [];
    for (let i = 0; i < text.length;) {
        const remaining = text.length - i;
        if (remaining >= 3) {
            const c = idx.get(text[i++])!;
            const d = idx.get(text[i++])!;
            const e = idx.get(text[i++])!;
            const x = c + d * 45 + e * 45 * 45;
            if (x > 0xffff) throw new Error("Base45 decode: value overflow in 3-tuple");
            out.push((x >> 8) & 0xff, x & 0xff);
        } else {
            const c = idx.get(text[i++])!;
            const d = idx.get(text[i++])!;
            const x = c + d * 45;
            if (x > 0xff) throw new Error("Base45 decode: value overflow in 2-tuple");
            out.push(x);
        }
    }

    return new Uint8Array(out);
}
