import { MASKS, DEFAULT_PAGE_SIZE, BLOCK_SIZE, greatestMultiple, roundUpToMultipleOf32, normalizePageSize, } from "./fastPforShared";
import { fastUnpack32_2, fastUnpack32_3, fastUnpack32_4, fastUnpack32_5, fastUnpack32_6, fastUnpack32_7, fastUnpack32_8, fastUnpack32_9, fastUnpack32_10, fastUnpack32_11, fastUnpack32_12, fastUnpack32_16, fastUnpack256_1, fastUnpack256_2, fastUnpack256_3, fastUnpack256_4, fastUnpack256_5, fastUnpack256_6, fastUnpack256_7, fastUnpack256_8, fastUnpack256_16, fastUnpack256_Generic, } from "./fastPforUnpack";
const MAX_BIT_WIDTH = 32;
const BIT_WIDTH_SLOTS = MAX_BIT_WIDTH + 1;
const PAGE_SIZE = normalizePageSize(DEFAULT_PAGE_SIZE);
const BYTE_CONTAINER_SIZE = ((3 * PAGE_SIZE) / BLOCK_SIZE + PAGE_SIZE) | 0;
/**
 * Creates an isolated workspace for decoding.
 * Reusing a workspace across calls avoids repeated allocations.
 */
export function createDecoderWorkspace() {
    const byteContainer = new Uint8Array(BYTE_CONTAINER_SIZE);
    return {
        dataToBePacked: new Array(BIT_WIDTH_SLOTS),
        dataPointers: new Int32Array(BIT_WIDTH_SLOTS),
        byteContainer,
        byteContainerI32: new Int32Array(byteContainer.buffer, byteContainer.byteOffset, byteContainer.byteLength >>> 2),
        exceptionSizes: new Int32Array(BIT_WIDTH_SLOTS),
    };
}
export function createFastPforWireDecodeWorkspace(initialEncodedWordCapacity = 16) {
    if (initialEncodedWordCapacity < 0) {
        throw new RangeError(`initialEncodedWordCapacity must be >= 0, got ${initialEncodedWordCapacity}`);
    }
    const capacity = Math.max(16, initialEncodedWordCapacity | 0);
    return {
        encodedWords: new Uint32Array(capacity),
        decoderWorkspace: createDecoderWorkspace(),
    };
}
export function ensureFastPforWireEncodedWordsCapacity(workspace, requiredWordCount) {
    if (requiredWordCount <= workspace.encodedWords.length)
        return workspace.encodedWords;
    const next = new Uint32Array(Math.max(16, requiredWordCount * 2));
    workspace.encodedWords = next;
    return next;
}
function materializeByteContainer(inValues, byteContainerStart, byteSize, workspace) {
    if (workspace.byteContainer.length < byteSize) {
        workspace.byteContainer = new Uint8Array(byteSize * 2);
        workspace.byteContainerI32 = undefined;
    }
    const byteContainer = workspace.byteContainer;
    const numFullInts = byteSize >>> 2;
    if ((byteContainer.byteOffset & 3) === 0) {
        let intView = workspace.byteContainerI32;
        if (!intView ||
            intView.buffer !== byteContainer.buffer ||
            intView.byteOffset !== byteContainer.byteOffset ||
            intView.length < numFullInts) {
            intView = workspace.byteContainerI32 = new Int32Array(byteContainer.buffer, byteContainer.byteOffset, byteContainer.byteLength >>> 2);
        }
        intView.set(inValues.subarray(byteContainerStart, byteContainerStart + numFullInts));
    }
    else {
        for (let i = 0; i < numFullInts; i = (i + 1) | 0) {
            const val = inValues[(byteContainerStart + i) | 0] | 0;
            const base = i << 2;
            byteContainer[base] = val & 0xff;
            byteContainer[(base + 1) | 0] = (val >>> 8) & 0xff;
            byteContainer[(base + 2) | 0] = (val >>> 16) & 0xff;
            byteContainer[(base + 3) | 0] = (val >>> 24) & 0xff;
        }
    }
    const remainder = byteSize & 3;
    if (remainder > 0) {
        const lastIntIdx = (byteContainerStart + numFullInts) | 0;
        const lastVal = inValues[lastIntIdx] | 0;
        const base = numFullInts << 2;
        for (let r = 0; r < remainder; r = (r + 1) | 0) {
            byteContainer[(base + r) | 0] = (lastVal >>> (r << 3)) & 0xff;
        }
    }
    return byteContainer;
}
/**
 * Unpacks the per-bitWidth "exception streams" described by the page's bitmap.
 *
 * @remarks
 * For each bit-width present in the bitmap, a stream header gives the count of outlier values for that
 * bit-width, followed by packed bits representing those values.
 *
 * @param inValues - Packed input (32-bit words).
 * @param inExcept - Offset (32-bit word index) where the exception bitmap starts.
 * @param workspace - Decoder workspace used to store the unpacked exception streams.
 * @returns The new input offset (32-bit word index) after consuming all exception streams.
 */
function unpackExceptionStreams(inValues, inExcept, workspace) {
    const bitmap = inValues[inExcept++] | 0;
    const dataToBePacked = workspace.dataToBePacked;
    for (let bitWidth = 2; bitWidth <= MAX_BIT_WIDTH; bitWidth = (bitWidth + 1) | 0) {
        if (((bitmap >>> (bitWidth - 1)) & 1) === 0)
            continue;
        if (inExcept >= inValues.length) {
            throw new Error(`FastPFOR decode: truncated exception stream header (bitWidth=${bitWidth}, streamWordIndex=${inExcept}, needWords=1, availableWords=${inValues.length - inExcept}, encodedWords=${inValues.length})`);
        }
        const size = inValues[inExcept++] >>> 0;
        const roundedUp = roundUpToMultipleOf32(size);
        const wordsNeeded = (size * bitWidth + 31) >>> 5;
        if (inExcept + wordsNeeded > inValues.length) {
            throw new Error(`FastPFOR decode: truncated exception stream (bitWidth=${bitWidth}, size=${size}, streamWordIndex=${inExcept}, needWords=${wordsNeeded}, availableWords=${inValues.length - inExcept}, encodedWords=${inValues.length})`);
        }
        let exceptionStream = dataToBePacked[bitWidth];
        if (!exceptionStream || exceptionStream.length < roundedUp) {
            exceptionStream = dataToBePacked[bitWidth] = new Uint32Array(roundedUp);
        }
        let j = 0;
        for (; j < size; j = (j + 32) | 0) {
            fastUnpack32(inValues, inExcept, exceptionStream, j, bitWidth);
            inExcept = (inExcept + bitWidth) | 0;
        }
        const overflow = (j - size) | 0;
        inExcept = (inExcept - ((overflow * bitWidth) >>> 5)) | 0;
        workspace.exceptionSizes[bitWidth] = size;
    }
    return inExcept;
}
/**
 * Unpacks one 256-value block from the packed bitstream using a specialized implementation for common widths.
 *
 * @param inValues - Packed input (32-bit words).
 * @param inPos - Input offset (32-bit word index) where the packed block starts.
 * @param out - Output buffer.
 * @param outPos - Output offset where the 256 values will be written.
 * @param bitWidth - Base bit-width used for this block.
 * @returns The new input offset (32-bit word index) right after the packed block data.
 */
function unpackBlock256(inValues, inPos, out, outPos, bitWidth) {
    switch (bitWidth) {
        case 1:
            fastUnpack256_1(inValues, inPos, out, outPos);
            break;
        case 2:
            fastUnpack256_2(inValues, inPos, out, outPos);
            break;
        case 3:
            fastUnpack256_3(inValues, inPos, out, outPos);
            break;
        case 4:
            fastUnpack256_4(inValues, inPos, out, outPos);
            break;
        case 5:
            fastUnpack256_5(inValues, inPos, out, outPos);
            break;
        case 6:
            fastUnpack256_6(inValues, inPos, out, outPos);
            break;
        case 7:
            fastUnpack256_7(inValues, inPos, out, outPos);
            break;
        case 8:
            fastUnpack256_8(inValues, inPos, out, outPos);
            break;
        case 16:
            fastUnpack256_16(inValues, inPos, out, outPos);
            break;
        default:
            fastUnpack256_Generic(inValues, inPos, out, outPos, bitWidth);
            break;
    }
    return (inPos + (bitWidth << 3)) | 0;
}
/**
 * Reads and validates the 2-byte block header from the byteContainer.
 *
 * @remarks
 * The header is `[bitWidth, exceptionCount]`, both stored as single bytes.
 *
 * @param byteContainer - Byte metadata buffer for the page.
 * @param byteContainerLen - The valid byte length in `byteContainer` for this page.
 * @param bytePosIn - Current offset in `byteContainer`.
 * @param block - Block index within the page (for error messages).
 * @returns The parsed header and the updated `bytePosIn`.
 */
function readBlockHeader(byteContainer, byteContainerLen, bytePosIn, block) {
    if (bytePosIn + 2 > byteContainerLen) {
        throw new Error(`FastPFOR decode: byteContainer underflow at block=${block} (need 2 bytes for [bitWidth, exceptionCount], bytePos=${bytePosIn}, byteSize=${byteContainerLen})`);
    }
    const bitWidth = byteContainer[bytePosIn++];
    const exceptionCount = byteContainer[bytePosIn++];
    if (bitWidth > MAX_BIT_WIDTH) {
        throw new Error(`FastPFOR decode: invalid bitWidth=${bitWidth} at block=${block} (expected 0..${MAX_BIT_WIDTH}). This likely indicates corrupted or truncated input.`);
    }
    return { bitWidth, exceptionCount, bytePosIn };
}
/**
 * Reads and validates the exception header for a block.
 *
 * @remarks
 * The header contains `maxBits` (1 byte), which defines the width of the outlier values as
 * `exceptionBitWidth = maxBits - bitWidth`.
 *
 * @param byteContainer - Byte metadata buffer for the page.
 * @param byteContainerLen - The valid byte length in `byteContainer` for this page.
 * @param bytePosIn - Current offset in `byteContainer`.
 * @param bitWidth - Base bit-width for the block.
 * @param exceptionCount - Number of exceptions/outliers in this block.
 * @param block - Block index within the page (for error messages).
 * @returns Parsed `maxBits`, `exceptionBitWidth`, and the updated `bytePosIn`.
 */
function readBlockExceptionHeader(byteContainer, byteContainerLen, bytePosIn, bitWidth, exceptionCount, block) {
    if (bytePosIn + 1 > byteContainerLen) {
        throw new Error(`FastPFOR decode: exception header underflow at block=${block} (need 1 byte for maxBits, bytePos=${bytePosIn}, byteSize=${byteContainerLen})`);
    }
    const maxBits = byteContainer[bytePosIn++];
    if (maxBits < bitWidth || maxBits > MAX_BIT_WIDTH) {
        throw new Error(`FastPFOR decode: invalid maxBits=${maxBits} at block=${block} (bitWidth=${bitWidth}, expected ${bitWidth}..${MAX_BIT_WIDTH})`);
    }
    const exceptionBitWidth = (maxBits - bitWidth) | 0;
    if (exceptionBitWidth < 1 || exceptionBitWidth > MAX_BIT_WIDTH) {
        throw new Error(`FastPFOR decode: invalid exceptionBitWidth=${exceptionBitWidth} at block=${block} (bitWidth=${bitWidth}, maxBits=${maxBits})`);
    }
    if (bytePosIn + exceptionCount > byteContainerLen) {
        throw new Error(`FastPFOR decode: exception positions underflow at block=${block} (need=${exceptionCount}, have=${byteContainerLen - bytePosIn})`);
    }
    return { maxBits, exceptionBitWidth, bytePosIn };
}
/**
 * Applies (block-local) FastPFOR "exceptions" (outliers) to an already-unpacked base 256-value block.
 *
 * @param out - Output buffer containing the base unpacked values for the block.
 * @param blockOutPos - Offset in `out` where the 256-value block starts.
 * @param bitWidth - Base bit-width for the block.
 * @param exceptionCount - Number of exceptions/outliers in this block.
 * @param byteContainer - Byte metadata buffer for the page.
 * @param byteContainerLen - The valid byte length in `byteContainer` for this page.
 * @param bytePosIn - Current offset in `byteContainer` (right after `[bitWidth, exceptionCount]`).
 * @param workspace - Decoder workspace holding the unpacked exception streams.
 * @param block - Block index within the page (for error messages).
 * @returns The updated `bytePosIn` after consuming the exception metadata bytes.
 *
 * The exception metadata is stored in `byteContainer`:
 * - `maxBits` (1 byte): the maximum bit-width of any value in the block
 * - `exceptionCount` exception positions (1 byte each, 0..255)
 *
 * The exception values themselves are read from the pre-unpacked exception streams stored in `workspace`.
 * Returns the new position in the byteContainer after consuming the exception metadata bytes.
 */
function applyBlockExceptions(out, blockOutPos, bitWidth, exceptionCount, byteContainer, byteContainerLen, bytePosIn, workspace, block) {
    const { maxBits, exceptionBitWidth, bytePosIn: afterHeaderPos, } = readBlockExceptionHeader(byteContainer, byteContainerLen, bytePosIn, bitWidth, exceptionCount, block);
    bytePosIn = afterHeaderPos;
    if (exceptionBitWidth === 1) {
        const shift = 1 << bitWidth;
        for (let k = 0; k < exceptionCount; k = (k + 1) | 0) {
            const pos = byteContainer[bytePosIn++];
            out[(pos + blockOutPos) | 0] |= shift;
        }
        return bytePosIn;
    }
    const exceptionValues = workspace.dataToBePacked[exceptionBitWidth];
    if (!exceptionValues) {
        throw new Error(`FastPFOR decode: missing exception stream for exceptionBitWidth=${exceptionBitWidth} (bitWidth=${bitWidth}, maxBits=${maxBits}) at block ${block}`);
    }
    const exceptionPointers = workspace.dataPointers;
    let exPtr = exceptionPointers[exceptionBitWidth] | 0;
    const exSize = workspace.exceptionSizes[exceptionBitWidth] | 0;
    if (exPtr + exceptionCount > exSize) {
        throw new Error(`FastPFOR decode: exception stream overflow for exceptionBitWidth=${exceptionBitWidth} (ptr=${exPtr}, need ${exceptionCount}, size=${exSize}) at block ${block}`);
    }
    for (let k = 0; k < exceptionCount; k = (k + 1) | 0) {
        const pos = byteContainer[bytePosIn++];
        const val = exceptionValues[exPtr++] | 0;
        out[(pos + blockOutPos) | 0] |= val << bitWidth;
    }
    exceptionPointers[exceptionBitWidth] = exPtr;
    return bytePosIn;
}
function decodePageBlocks(inValues, pageStart, inPos, packedEnd, out, outPos, blocks, byteContainer, byteContainerLen, workspace) {
    let tmpInPos = inPos | 0;
    let bytePosIn = 0;
    for (let run = 0; run < blocks; run = (run + 1) | 0) {
        const header = readBlockHeader(byteContainer, byteContainerLen, bytePosIn, run);
        bytePosIn = header.bytePosIn;
        const bitWidth = header.bitWidth;
        const exceptionCount = header.exceptionCount;
        const blockOutPos = (outPos + run * BLOCK_SIZE) | 0;
        switch (bitWidth) {
            case 0:
                out.fill(0, blockOutPos, blockOutPos + BLOCK_SIZE);
                break;
            case 32:
                for (let i = 0; i < BLOCK_SIZE; i = (i + 1) | 0) {
                    out[(blockOutPos + i) | 0] = inValues[(tmpInPos + i) | 0] | 0;
                }
                tmpInPos = (tmpInPos + BLOCK_SIZE) | 0;
                break;
            default:
                tmpInPos = unpackBlock256(inValues, tmpInPos, out, blockOutPos, bitWidth);
                break;
        }
        if (exceptionCount > 0) {
            bytePosIn = applyBlockExceptions(out, blockOutPos, bitWidth, exceptionCount, byteContainer, byteContainerLen, bytePosIn, workspace, run);
        }
    }
    if (tmpInPos !== packedEnd) {
        throw new Error(`FastPFOR decode: packed region mismatch (pageStart=${pageStart}, packedStart=${inPos}, consumedPackedEnd=${tmpInPos}, expectedPackedEnd=${packedEnd}, packedWords=${packedEnd - inPos}, encoded.length=${inValues.length})`);
    }
    return;
}
/**
 * Decodes one FastPFOR page (aligned to 256-value blocks).
 */
function decodePage(inValues, out, inPos, outPos, thisSize, workspace) {
    const pageStart = inPos | 0;
    const whereMeta = inValues[pageStart] | 0;
    if (whereMeta <= 0 || pageStart + whereMeta > inValues.length - 1) {
        throw new Error(`FastPFOR decode: invalid whereMeta=${whereMeta} at pageStart=${pageStart} (expected > 0 and pageStart+whereMeta < encoded.length=${inValues.length})`);
    }
    const packedStart = (pageStart + 1) | 0;
    const packedEnd = (pageStart + whereMeta) | 0;
    const byteSize = inValues[packedEnd] >>> 0;
    const metaInts = (byteSize + 3) >>> 2;
    const byteContainerStart = packedEnd + 1;
    const bitmapPos = byteContainerStart + metaInts;
    if (bitmapPos >= inValues.length) {
        throw new Error(`FastPFOR decode: invalid byteSize=${byteSize} (metaInts=${metaInts}, pageStart=${pageStart}, packedEnd=${packedEnd}, byteContainerStart=${byteContainerStart}) causes bitmapPos=${bitmapPos} out of bounds (encoded.length=${inValues.length})`);
    }
    const byteContainer = materializeByteContainer(inValues, byteContainerStart, byteSize, workspace);
    const byteContainerLen = byteSize;
    const inExcept = unpackExceptionStreams(inValues, bitmapPos, workspace);
    const exceptionPointers = workspace.dataPointers;
    exceptionPointers.fill(0);
    const startOutPos = outPos | 0;
    const blocks = (thisSize / BLOCK_SIZE) | 0;
    decodePageBlocks(inValues, pageStart, packedStart, packedEnd, out, startOutPos, blocks, byteContainer, byteContainerLen, workspace);
    return inExcept;
}
function decodeAlignedPages(inValues, out, inPos, outPos, outLength, workspace) {
    const alignedOutLength = greatestMultiple(outLength, BLOCK_SIZE);
    const finalOut = outPos + alignedOutLength;
    let tmpOutPos = outPos;
    let tmpInPos = inPos;
    while (tmpOutPos !== finalOut) {
        const thisSize = Math.min(PAGE_SIZE, finalOut - tmpOutPos);
        tmpInPos = decodePage(inValues, out, tmpInPos, tmpOutPos, thisSize, workspace);
        tmpOutPos = (tmpOutPos + thisSize) | 0;
    }
    return tmpInPos;
}
/**
 * Decodes the VariableByte tail (MSB=1 terminator, opposite of Protobuf Varint).
 */
function decodeVByte(inValues, inPos, inLength, out, outPos, expectedCount) {
    if (expectedCount === 0)
        return inPos;
    let bitOffset = 0;
    let wordIndex = inPos;
    const finalWordIndex = inPos + inLength;
    const outPos0 = outPos;
    let tmpOutPos = outPos;
    const targetOut = outPos + expectedCount;
    let accumulator = 0;
    let accumulatorShift = 0;
    while (wordIndex < finalWordIndex && tmpOutPos < targetOut) {
        const word = inValues[wordIndex];
        const byte = (word >>> bitOffset) & 0xff;
        bitOffset += 8;
        wordIndex += bitOffset >>> 5;
        bitOffset &= 31;
        accumulator |= (byte & 0x7f) << accumulatorShift;
        if ((byte & 0x80) !== 0) {
            out[tmpOutPos++] = accumulator | 0;
            accumulator = 0;
            accumulatorShift = 0;
        }
        else {
            accumulatorShift += 7;
            if (accumulatorShift > 28) {
                throw new Error(`FastPFOR VByte: unterminated value (expected MSB=1 terminator within 5 bytes; shift=${accumulatorShift}, partial=${accumulator}, decoded=${tmpOutPos - outPos0}/${expectedCount}, inPos=${wordIndex}, inEnd=${finalWordIndex})`);
            }
        }
    }
    if (tmpOutPos !== targetOut) {
        throw new Error(`FastPFOR VByte: truncated stream (decoded=${tmpOutPos - outPos0}, expected=${expectedCount}, consumedWords=${wordIndex - inPos}/${inLength}, vbyteStart=${inPos}, vbyteEnd=${finalWordIndex})`);
    }
    return wordIndex;
}
/**
 * Decodes a sequence of FastPFOR-encoded integers.
 *
 * @param encoded The input buffer containing FastPFOR encoded data.
 * @param numValues The number of integers expected to be decoded.
 * @param workspace Optional workspace for reuse across calls. If omitted, a new workspace is created per call.
 */
export function decodeFastPforInt32(encoded, numValues, workspace) {
    let inPos = 0;
    let outPos = 0;
    const decoded = new Uint32Array(numValues);
    const decoderWorkspace = workspace ?? createDecoderWorkspace();
    if (encoded.length > 0) {
        const alignedLength = encoded[inPos] | 0;
        inPos = (inPos + 1) | 0;
        if ((alignedLength & (BLOCK_SIZE - 1)) !== 0) {
            throw new Error(`FastPFOR decode: invalid alignedLength=${alignedLength} (expected multiple of ${BLOCK_SIZE})`);
        }
        if (outPos + alignedLength > decoded.length) {
            throw new Error(`FastPFOR decode: output buffer too small (outPos=${outPos}, alignedLength=${alignedLength}, out.length=${decoded.length})`);
        }
        inPos = decodeAlignedPages(encoded, decoded, inPos, outPos, alignedLength, decoderWorkspace);
        outPos = (outPos + alignedLength) | 0;
    }
    const remainingLength = (encoded.length - inPos) | 0;
    const expectedTail = (numValues - outPos) | 0;
    decodeVByte(encoded, inPos, remainingLength, decoded, outPos, expectedTail);
    return decoded;
}
function fastUnpack32(inValues, inPos, out, outPos, bitWidth) {
    switch (bitWidth) {
        case 2:
            fastUnpack32_2(inValues, inPos, out, outPos);
            return;
        case 3:
            fastUnpack32_3(inValues, inPos, out, outPos);
            return;
        case 4:
            fastUnpack32_4(inValues, inPos, out, outPos);
            return;
        case 5:
            fastUnpack32_5(inValues, inPos, out, outPos);
            return;
        case 6:
            fastUnpack32_6(inValues, inPos, out, outPos);
            return;
        case 7:
            fastUnpack32_7(inValues, inPos, out, outPos);
            return;
        case 8:
            fastUnpack32_8(inValues, inPos, out, outPos);
            return;
        case 9:
            fastUnpack32_9(inValues, inPos, out, outPos);
            return;
        case 10:
            fastUnpack32_10(inValues, inPos, out, outPos);
            return;
        case 11:
            fastUnpack32_11(inValues, inPos, out, outPos);
            return;
        case 12:
            fastUnpack32_12(inValues, inPos, out, outPos);
            return;
        case 16:
            fastUnpack32_16(inValues, inPos, out, outPos);
            return;
        case 32:
            for (let i = 0; i < 32; i = (i + 1) | 0) {
                out[(outPos + i) | 0] = inValues[(inPos + i) | 0] | 0;
            }
            return;
        default:
            break;
    }
    const valueMask = MASKS[bitWidth] >>> 0;
    let inputWordIndex = inPos;
    let bitOffset = 0;
    let currentWord = inValues[inputWordIndex] >>> 0;
    for (let i = 0; i < 32; i++) {
        if (bitOffset + bitWidth <= 32) {
            const value = (currentWord >>> bitOffset) & valueMask;
            out[outPos + i] = value | 0;
            bitOffset += bitWidth;
            if (bitOffset === 32) {
                bitOffset = 0;
                inputWordIndex++;
                if (i !== 31)
                    currentWord = inValues[inputWordIndex] >>> 0;
            }
        }
        else {
            const lowBits = 32 - bitOffset;
            const low = currentWord >>> bitOffset;
            inputWordIndex++;
            currentWord = inValues[inputWordIndex] >>> 0;
            const highMask = MASKS[bitWidth - lowBits] >>> 0;
            const high = currentWord & highMask;
            const value = (low | (high << lowBits)) & valueMask;
            out[outPos + i] = value | 0;
            bitOffset = bitWidth - lowBits;
        }
    }
}
//# sourceMappingURL=fastPforDecoder.js.map