import { ErrorCode, ValueTag } from '@bilig/protocol';
import { normalizeExactLookupNumber } from '@bilig/formula';
import { addEngineCounter } from '../../perf/engine-counters.js';
import { directLookupVersionMatches } from './direct-lookup-helpers.js';
import { createWrittenColumnTracker, markWrittenColumn, materializeWrittenColumns, } from '../../written-column-tracker.js';
const LOOKUP_KIND_EXACT_UNIFORM_NUMERIC = 1;
const LOOKUP_KIND_APPROXIMATE_UNIFORM_NUMERIC = 2;
const MATCH_MODE_ASCENDING = 1;
const MATCH_MODE_DESCENDING = 2;
export const MIN_INITIAL_NATIVE_DIRECT_LOOKUP_BATCH_SIZE = 256;
export const MAX_INITIAL_NATIVE_DIRECT_LOOKUP_BATCH_SIZE = 65_536;
export const MIN_RECALC_NATIVE_DIRECT_LOOKUP_BATCH_SIZE = 64;
export const MAX_RECALC_NATIVE_DIRECT_LOOKUP_BATCH_SIZE = 4096;
export function createInitialNativeDirectLookupBatch(args) {
    return createNativeDirectLookupBatch({
        ...args,
        counterName: 'nativeDirectLookupInitialEvaluations',
        minBatchSize: 1,
    });
}
export function createRecalcNativeDirectLookupBatch(args) {
    return createNativeDirectLookupBatch({
        ...args,
        counterName: 'nativeDirectLookupRecalcEvaluations',
        minBatchSize: MIN_RECALC_NATIVE_DIRECT_LOOKUP_BATCH_SIZE,
    });
}
function createNativeDirectLookupBatch(args) {
    const targets = new Uint32Array(args.capacity);
    const kinds = new Uint8Array(args.capacity);
    const matchModes = new Uint8Array(args.capacity);
    const starts = new Float64Array(args.capacity);
    const steps = new Float64Array(args.capacity);
    const lengths = new Uint32Array(args.capacity);
    const repeatedRunLengths = new Uint32Array(args.capacity);
    const lookupTags = new Uint8Array(args.capacity);
    const lookupNumbers = new Float64Array(args.capacity);
    const outTags = new Uint8Array(args.capacity);
    const outNumbers = new Float64Array(args.capacity);
    const outErrors = new Uint16Array(args.capacity);
    const columnsBySheetId = new Map();
    let count = 0;
    const noteColumn = (sheetId, col) => {
        let tracker = columnsBySheetId.get(sheetId);
        if (!tracker) {
            tracker = createWrittenColumnTracker();
            columnsBySheetId.set(sheetId, tracker);
        }
        markWrittenColumn(tracker, col);
    };
    const writeLookupValue = (index, directLookup) => {
        const cellStore = args.state.workbook.cellStore;
        const tag = cellStore.tags[directLookup.operandCellIndex] ?? ValueTag.Empty;
        if (directLookup.kind === 'exact-uniform-numeric') {
            if (tag !== ValueTag.Number) {
                return false;
            }
            const normalized = normalizeExactLookupNumber(cellStore.numbers[directLookup.operandCellIndex] ?? 0);
            if (!Number.isFinite(normalized)) {
                return false;
            }
            lookupTags[index] = ValueTag.Number;
            lookupNumbers[index] = normalized;
            return true;
        }
        switch (tag) {
            case ValueTag.Empty:
                lookupTags[index] = ValueTag.Empty;
                lookupNumbers[index] = 0;
                return true;
            case ValueTag.Number:
                lookupTags[index] = ValueTag.Number;
                lookupNumbers[index] = Object.is(cellStore.numbers[directLookup.operandCellIndex] ?? 0, -0)
                    ? 0
                    : (cellStore.numbers[directLookup.operandCellIndex] ?? 0);
                return true;
            case ValueTag.Boolean:
                lookupTags[index] = ValueTag.Boolean;
                lookupNumbers[index] = (cellStore.numbers[directLookup.operandCellIndex] ?? 0) !== 0 ? 1 : 0;
                return true;
            case ValueTag.String:
            case ValueTag.Error:
                return false;
        }
    };
    const canUseNativeLookupShape = (directLookup) => {
        if (directLookup.tailPatch !== undefined || directLookup.length <= 0 || directLookup.step === 0) {
            return false;
        }
        const lookupSheet = args.state.workbook.getSheetById(directLookup.sheetId);
        if (!directLookupVersionMatches(lookupSheet, directLookup)) {
            return false;
        }
        if (directLookup.kind === 'exact-uniform-numeric') {
            const start = normalizeExactLookupNumber(directLookup.start);
            const step = normalizeExactLookupNumber(directLookup.step);
            return Number.isFinite(start) && Number.isFinite(step) && Number.isInteger(start) && Number.isInteger(step);
        }
        if (directLookup.matchMode === 1) {
            return directLookup.step > 0;
        }
        return directLookup.step < 0;
    };
    return {
        get count() {
            return count;
        },
        add(prepared, directLookup) {
            if (count >= args.capacity ||
                (directLookup.kind !== 'exact-uniform-numeric' && directLookup.kind !== 'approximate-uniform-numeric') ||
                !canUseNativeLookupShape(directLookup)) {
                return false;
            }
            const index = count;
            if (!writeLookupValue(index, directLookup)) {
                return false;
            }
            targets[index] = prepared.cellIndex;
            kinds[index] =
                directLookup.kind === 'exact-uniform-numeric' ? LOOKUP_KIND_EXACT_UNIFORM_NUMERIC : LOOKUP_KIND_APPROXIMATE_UNIFORM_NUMERIC;
            matchModes[index] =
                directLookup.kind === 'approximate-uniform-numeric' && directLookup.matchMode === -1 ? MATCH_MODE_DESCENDING : MATCH_MODE_ASCENDING;
            starts[index] = directLookup.kind === 'exact-uniform-numeric' ? normalizeExactLookupNumber(directLookup.start) : directLookup.start;
            steps[index] = directLookup.kind === 'exact-uniform-numeric' ? normalizeExactLookupNumber(directLookup.step) : directLookup.step;
            lengths[index] = directLookup.length;
            repeatedRunLengths[index] = directLookup.kind === 'approximate-uniform-numeric' ? (directLookup.repeatedRunLength ?? 0) : 0;
            noteColumn(prepared.sheetId, prepared.col);
            count += 1;
            return true;
        },
        evaluate() {
            if (count < args.minBatchSize || !args.state.wasm.initSyncIfPossible()) {
                return undefined;
            }
            const targetView = targets.subarray(0, count);
            if (!args.state.wasm.evalUniformNumericLookupBatch({
                kinds: kinds.subarray(0, count),
                matchModes: matchModes.subarray(0, count),
                starts: starts.subarray(0, count),
                steps: steps.subarray(0, count),
                lengths: lengths.subarray(0, count),
                repeatedRunLengths: repeatedRunLengths.subarray(0, count),
                lookupTags: lookupTags.subarray(0, count),
                lookupNumbers: lookupNumbers.subarray(0, count),
                outTags: outTags.subarray(0, count),
                outNumbers: outNumbers.subarray(0, count),
                outErrors: outErrors.subarray(0, count),
            })) {
                return undefined;
            }
            const cellStore = args.state.workbook.cellStore;
            const onCellValueChanged = args.onCellValueChanged;
            for (let index = 0; index < count; index += 1) {
                const cellIndex = targets[index];
                const tag = outTags[index] ?? ValueTag.Empty;
                const nextError = tag === ValueTag.Error ? (outErrors[index] ?? ErrorCode.None) : ErrorCode.None;
                const nextNumber = tag === ValueTag.Number || tag === ValueTag.Boolean ? (outNumbers[index] ?? 0) : 0;
                const beforeTag = onCellValueChanged === undefined ? tag : (cellStore.tags[cellIndex] ?? ValueTag.Empty);
                const changed = onCellValueChanged === undefined
                    ? false
                    : beforeTag !== tag ||
                        ((tag === ValueTag.Number || tag === ValueTag.Boolean) && !Object.is(cellStore.numbers[cellIndex] ?? 0, nextNumber)) ||
                        (tag === ValueTag.Error && (cellStore.errors[cellIndex] ?? ErrorCode.None) !== nextError);
                cellStore.flags[cellIndex] = (cellStore.flags[cellIndex] ?? 0) & ~(64 /* CellFlags.SpillChild */ | 128 /* CellFlags.PivotOutput */);
                cellStore.tags[cellIndex] = tag;
                cellStore.errors[cellIndex] = nextError;
                cellStore.stringIds[cellIndex] = 0;
                cellStore.numbers[cellIndex] = nextNumber;
                cellStore.versions[cellIndex] = (cellStore.versions[cellIndex] ?? 0) + 1;
                if (changed) {
                    onCellValueChanged?.(cellIndex);
                }
            }
            columnsBySheetId.forEach((tracker, sheetId) => {
                args.state.workbook.notifyColumnsWritten(sheetId, materializeWrittenColumns(tracker));
            });
            addEngineCounter(args.state.counters, args.counterName, count);
            return targetView;
        },
        reset() {
            count = 0;
            columnsBySheetId.clear();
        },
    };
}
//# sourceMappingURL=formula-initialization-native-direct-lookup.js.map