import { addCodecSizePrefix, getBase16Codec, getBooleanCodec, getBytesCodec, getU128Codec, getU32Codec, getU64Codec, getU8Codec, getUtf8Codec, } from '@solana/kit';
import { assert, isNullish } from '@hyperlane-xyz/utils';
import { MAX_ACCOUNT_DATA_SIZE } from '../constants.js';
const U8_CODEC = getU8Codec();
const BOOL_CODEC = getBooleanCodec();
const U32_CODEC = getU32Codec();
const U64_CODEC = getU64Codec();
const U128_CODEC = getU128Codec();
const UTF8_CODEC = getUtf8Codec();
const BASE16_CODEC = getBase16Codec();
const VEC_BYTES_CODEC = addCodecSizePrefix(getBytesCodec(), U32_CODEC);
export class ByteCursor {
    data;
    offset = 0;
    constructor(data) {
        this.data = data;
    }
    remaining() {
        return this.data.length - this.offset;
    }
    readU8() {
        this.ensure(1);
        const value = U8_CODEC.decode(this.data.slice(this.offset, this.offset + 1));
        this.offset += 1;
        return value;
    }
    readBool() {
        this.ensure(1);
        const value = BOOL_CODEC.decode(this.data.slice(this.offset, this.offset + 1));
        this.offset += 1;
        return value;
    }
    readU32LE() {
        this.ensure(4);
        const value = U32_CODEC.decode(this.data.slice(this.offset, this.offset + 4));
        this.offset += 4;
        return value;
    }
    readU64LE() {
        this.ensure(8);
        const value = U64_CODEC.decode(this.data.slice(this.offset, this.offset + 8));
        this.offset += 8;
        return value;
    }
    readU128LE() {
        this.ensure(16);
        const value = U128_CODEC.decode(this.data.slice(this.offset, this.offset + 16));
        this.offset += 16;
        return value;
    }
    readU256LE() {
        return readBigIntLE(this.readBytes(32));
    }
    readBytes(length) {
        this.ensure(length);
        const start = this.offset;
        this.offset += length;
        return this.data.slice(start, this.offset);
    }
    readVecBytes() {
        const length = this.readU32LE();
        assert(length <= MAX_ACCOUNT_DATA_SIZE, `Vec length ${length} exceeds Solana max account size (${MAX_ACCOUNT_DATA_SIZE})`);
        return this.readBytes(length);
    }
    readString() {
        return UTF8_CODEC.decode(this.readVecBytes());
    }
    readWithDecoder(decoder) {
        try {
            const [value, nextOffset] = decoder.read(this.data, this.offset);
            this.offset = nextOffset;
            return value;
        }
        catch (e) {
            throw new Error(`Decoder failed at offset ${this.offset}`, { cause: e });
        }
    }
    ensure(length) {
        if (this.remaining() < length) {
            throw new Error(`Buffer underflow: need ${length}, remaining ${this.remaining()}`);
        }
    }
}
export function concatBytes(...parts) {
    const total = parts.reduce((acc, p) => acc + p.length, 0);
    const out = new Uint8Array(total);
    let offset = 0;
    for (const part of parts) {
        out.set(part, offset);
        offset += part.length;
    }
    return out;
}
export function u8(value) {
    return U8_CODEC.encode(value);
}
export function bool(value) {
    return BOOL_CODEC.encode(value);
}
export function u32le(value) {
    return U32_CODEC.encode(value);
}
export function u64le(value) {
    return U64_CODEC.encode(value);
}
export function u128le(value) {
    return U128_CODEC.encode(value);
}
export function u256le(value) {
    return writeBigIntLE(value, 32);
}
export function vecBytes(bytes) {
    return VEC_BYTES_CODEC.encode(bytes);
}
export function vec(items, encodeItem) {
    return concatBytes(u32le(items.length), ...items.map(encodeItem));
}
export function option(item, encodeItem) {
    if (item == null)
        return u8(0);
    return concatBytes(u8(1), encodeItem(item));
}
export function mapU32(entries, encodeValue) {
    const materialized = Array.from(entries);
    return concatBytes(u32le(materialized.length), ...materialized.map(([k, v]) => concatBytes(u32le(k), encodeValue(v))));
}
export function addressBytes(input) {
    if (input instanceof Uint8Array)
        return input;
    if (typeof input === 'string' && input.startsWith('0x')) {
        return BASE16_CODEC.encode(input.slice(2).toLowerCase());
    }
    throw new Error(`Expected raw bytes or 0x-prefixed hex string, got: ${input.slice(0, 12)}`);
}
export function ensureLength(bytes, length, label) {
    if (bytes.length !== length) {
        throw new Error(`${label} must be ${length} bytes, got ${bytes.length}`);
    }
    return bytes;
}
export function readBigIntLE(bytes) {
    let result = 0n;
    for (let i = 0; i < bytes.length; i += 1) {
        const byte = bytes[i];
        if (isNullish(byte))
            continue;
        result |= BigInt(byte) << (8n * BigInt(i));
    }
    return result;
}
export function writeBigIntLE(value, length) {
    if (value < 0n)
        throw new Error('Negative bigint not supported');
    if (value >= 1n << BigInt(length * 8)) {
        throw new Error(`Integer does not fit in ${length} bytes`);
    }
    const out = new Uint8Array(length);
    let remaining = value;
    for (let i = 0; i < length; i += 1) {
        out[i] = Number(remaining & 0xffn);
        remaining >>= 8n;
    }
    return out;
}
//# sourceMappingURL=binary.js.map