// Encode bytes as base64. Uses Buffer when available (Node) for speed, and a
// chunked btoa() fallback in the browser that avoids building one giant
// intermediate string (the previous reduce()-based version was O(n^2) and could
// overflow the call stack / be very slow for large inputs such as the embedded
// .wasm binary).
export function bytesToBase64(bytes: Uint8Array): string {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const B = (globalThis as any).Buffer;
    if (typeof B !== 'undefined') {
        return B.from(bytes).toString('base64');
    }

    let binary = '';
    const chunkSize = 0x8000; // 32 KiB worth of chars per fromCharCode call
    for (let i = 0; i < bytes.length; i += chunkSize) {
        const chunk = bytes.subarray(i, i + chunkSize);
        binary += String.fromCharCode(...chunk);
    }
    return btoa(binary);
}

export function base64ToBytes(base64: string): Uint8Array {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const B = (globalThis as any).Buffer;
    if (typeof B !== 'undefined') {
        return new Uint8Array(B.from(base64, 'base64'));
    }
    return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
}

// Encode bytes as a hex string. By default a "0x" prefix is prepended (kept for
// backward compatibility); pass `prefix = false` to get a bare hex string.
export function bytesToHex(bytes: Uint8Array, prefix = true): string {
    let hex = '';
    for (const byte of bytes) {
        hex += byte.toString(16).padStart(2, '0');
    }
    return prefix ? '0x' + hex : hex;
}

export function hexToBytes(hexStr: string): Uint8Array {
    if (hexStr.startsWith('0x')) {
        hexStr = hexStr.slice(2);
    }
    if (hexStr.length % 2 !== 0) {
        hexStr = '0' + hexStr;
    }
    if (hexStr.length === 0) {
        return new Uint8Array(0);
    }
    if (!/^[0-9a-fA-F]+$/.test(hexStr)) {
        throw new Error('hexToBytes: invalid hex string');
    }
    const out = new Uint8Array(hexStr.length / 2);
    for (let i = 0; i < out.length; i++) {
        out[i] = parseInt(hexStr.substr(i * 2, 2), 16);
    }
    return out;
}
