/**
 * ISAAC-64: A fast cryptographic PRNG
 * Re-implemented in TypeScript using BigInt for 64-bit support.
 * Used for WeChat Channels/SNS video decryption.
 */

export class Isaac64 {
    private mm = new BigUint64Array(256);
    private aa = 0n;
    private bb = 0n;
    private cc = 0n;
    private randrsl = new BigUint64Array(256);
    private randcnt = 0;
    private static readonly MASK = 0xFFFFFFFFFFFFFFFFn;

    constructor(seed: number | string | bigint) {
        const seedBig = BigInt(seed);
        // 通常单密钥初始化是将密钥放在第一个槽位，其余清零（或者按某种规律填充）
        // 这里我们尝试仅设置第一个槽位，这在很多 WASM 移植版本中更为常见
        this.randrsl.fill(0n);
        this.randrsl[0] = seedBig;
        this.init(true);
    }

    private init(flag: boolean) {
        let a: bigint, b: bigint, c: bigint, d: bigint, e: bigint, f: bigint, g: bigint, h: bigint;
        a = b = c = d = e = f = g = h = 0x9e3779b97f4a7c15n;

        const mix = () => {
            a = (a - e) & Isaac64.MASK; f ^= (h >> 9n); h = (h + a) & Isaac64.MASK;
            b = (b - f) & Isaac64.MASK; g ^= (a << 9n) & Isaac64.MASK; a = (a + b) & Isaac64.MASK;
            c = (c - g) & Isaac64.MASK; h ^= (b >> 23n); b = (b + c) & Isaac64.MASK;
            d = (d - h) & Isaac64.MASK; a ^= (c << 15n) & Isaac64.MASK; c = (c + d) & Isaac64.MASK;
            e = (e - a) & Isaac64.MASK; b ^= (d >> 14n); d = (d + e) & Isaac64.MASK;
            f = (f - b) & Isaac64.MASK; c ^= (e << 20n) & Isaac64.MASK; e = (e + f) & Isaac64.MASK;
            g = (g - c) & Isaac64.MASK; d ^= (f >> 17n); f = (f + g) & Isaac64.MASK;
            h = (h - d) & Isaac64.MASK; e ^= (g << 14n) & Isaac64.MASK; g = (g + h) & Isaac64.MASK;
        };

        for (let i = 0; i < 4; i++) mix();

        for (let i = 0; i < 256; i += 8) {
            if (flag) {
                a = (a + this.randrsl[i]) & Isaac64.MASK;
                b = (b + this.randrsl[i + 1]) & Isaac64.MASK;
                c = (c + this.randrsl[i + 2]) & Isaac64.MASK;
                d = (d + this.randrsl[i + 3]) & Isaac64.MASK;
                e = (e + this.randrsl[i + 4]) & Isaac64.MASK;
                f = (f + this.randrsl[i + 5]) & Isaac64.MASK;
                g = (g + this.randrsl[i + 6]) & Isaac64.MASK;
                h = (h + this.randrsl[i + 7]) & Isaac64.MASK;
            }
            mix();
            this.mm[i] = a; this.mm[i + 1] = b; this.mm[i + 2] = c; this.mm[i + 3] = d;
            this.mm[i + 4] = e; this.mm[i + 5] = f; this.mm[i + 6] = g; this.mm[i + 7] = h;
        }

        if (flag) {
            for (let i = 0; i < 256; i += 8) {
                a = (a + this.mm[i]) & Isaac64.MASK;
                b = (b + this.mm[i + 1]) & Isaac64.MASK;
                c = (c + this.mm[i + 2]) & Isaac64.MASK;
                d = (d + this.mm[i + 3]) & Isaac64.MASK;
                e = (e + this.mm[i + 4]) & Isaac64.MASK;
                f = (f + this.mm[i + 5]) & Isaac64.MASK;
                g = (g + this.mm[i + 6]) & Isaac64.MASK;
                h = (h + this.mm[i + 7]) & Isaac64.MASK;
                mix();
                this.mm[i] = a; this.mm[i + 1] = b; this.mm[i + 2] = c; this.mm[i + 3] = d;
                this.mm[i + 4] = e; this.mm[i + 5] = f; this.mm[i + 6] = g; this.mm[i + 7] = h;
            }
        }

        this.isaac64();
        this.randcnt = 256;
    }

    private isaac64() {
        this.cc = (this.cc + 1n) & Isaac64.MASK;
        this.bb = (this.bb + this.cc) & Isaac64.MASK;
        for (let i = 0; i < 256; i++) {
            let x = this.mm[i];
            switch (i & 3) {
                case 0: this.aa = (this.aa ^ (((this.aa << 21n) & Isaac64.MASK) ^ Isaac64.MASK)) & Isaac64.MASK; break;
                case 1: this.aa = (this.aa ^ (this.aa >> 5n)) & Isaac64.MASK; break;
                case 2: this.aa = (this.aa ^ ((this.aa << 12n) & Isaac64.MASK)) & Isaac64.MASK; break;
                case 3: this.aa = (this.aa ^ (this.aa >> 33n)) & Isaac64.MASK; break;
            }
            this.aa = (this.mm[(i + 128) & 255] + this.aa) & Isaac64.MASK;
            const y = (this.mm[Number(x >> 3n) & 255] + this.aa + this.bb) & Isaac64.MASK;
            this.mm[i] = y;
            this.bb = (this.mm[Number(y >> 11n) & 255] + x) & Isaac64.MASK;
            this.randrsl[i] = this.bb;
        }
    }

    public getNext(): bigint {
        if (this.randcnt === 0) {
            this.isaac64();
            this.randcnt = 256;
        }
        return this.randrsl[--this.randcnt];
    }

    /**
     * Generates a keystream where each 64-bit block is Big-Endian.
     * This matches WeChat's behavior (Reverse index order + byte reversal).
     */
    public generateKeystreamBE(size: number): Buffer {
        const buffer = Buffer.allocUnsafe(size);
        const fullBlocks = Math.floor(size / 8);

        for (let i = 0; i < fullBlocks; i++) {
            buffer.writeBigUInt64BE(this.getNext(), i * 8);
        }

        const remaining = size % 8;
        if (remaining > 0) {
            const lastK = this.getNext();
            const temp = Buffer.allocUnsafe(8);
            temp.writeBigUInt64BE(lastK, 0);
            temp.copy(buffer, fullBlocks * 8, 0, remaining);
        }

        return buffer;
    }
}
