import { ErrorCode, ValueTag } from '@bilig/protocol';
import { compileCriteriaMatcher, matchesCompiledCriteria, normalizeExactLookupNumber, } from '@bilig/formula';
import { sliceAbsoluteRowsToRangeView } from './criterion-rowset-view.js';
import { getOrBuildIndexedPredicateAggregateFromColumnViews } from './criterion-range-indexed-predicate-aggregate.js';
const equalityRowIndexes = new WeakMap();
const COMPOUND_EXACT_AGGREGATE_BUCKET_LIMIT = 16_384;
function errorValue(code) {
    return { tag: ValueTag.Error, code };
}
function numberValue(value) {
    return { tag: ValueTag.Number, value };
}
function criteriaCacheKey(value) {
    switch (value.tag) {
        case ValueTag.Empty:
            return 'e:';
        case ValueTag.Number:
            return `n:${normalizeExactLookupNumber(value.value)}`;
        case ValueTag.Boolean:
            return value.value ? 'b:1' : 'b:0';
        case ValueTag.String:
            return `s:${value.value}`;
        case ValueTag.Error:
            return `r:${value.code}`;
    }
}
function decodeValueTag(rawTag) {
    if (rawTag === undefined) {
        return ValueTag.Empty;
    }
    switch (rawTag) {
        case 1:
            return ValueTag.Number;
        case 2:
            return ValueTag.Boolean;
        case 3:
            return ValueTag.String;
        case 4:
            return ValueTag.Error;
        case 0:
        default:
            return ValueTag.Empty;
    }
}
function normalizeSliceString(runtimeColumnStore, stringId) {
    return stringId === 0 ? '' : runtimeColumnStore.normalizeStringId(stringId);
}
function materializeSliceValue(view, offset, runtimeColumnStore) {
    const tag = decodeValueTag(view.readTagAt(offset));
    switch (tag) {
        case ValueTag.Empty:
            return { tag: ValueTag.Empty };
        case ValueTag.Number:
            return { tag: ValueTag.Number, value: view.readNumberAt(offset) };
        case ValueTag.Boolean:
            return { tag: ValueTag.Boolean, value: view.readNumberAt(offset) !== 0 };
        case ValueTag.String: {
            const stringId = view.readStringIdAt(offset);
            return {
                tag: ValueTag.String,
                value: stringId === 0 ? '' : runtimeColumnStore.normalizeStringId(stringId),
                stringId,
            };
        }
        case ValueTag.Error:
            return { tag: ValueTag.Error, code: view.readErrorAt(offset) ?? ErrorCode.None };
    }
}
function buildSlicePredicate(compiled) {
    const { operator, operand, wildcardPattern } = compiled;
    if (wildcardPattern) {
        return { kind: 'generic', compiled };
    }
    if (operator === '=' || operator === '<>') {
        const negate = operator === '<>';
        switch (operand.tag) {
            case ValueTag.Empty:
                return { kind: 'eq-empty', negate };
            case ValueTag.Boolean:
                return { kind: 'eq-bool', negate, value: operand.value };
            case ValueTag.Number:
                return {
                    kind: 'eq-number',
                    negate,
                    value: normalizeExactLookupNumber(operand.value),
                };
            case ValueTag.String:
                return { kind: 'eq-string', negate, value: operand.value.toUpperCase() };
            case ValueTag.Error:
                return { kind: 'generic', compiled };
        }
    }
    if (operand.tag === ValueTag.Number) {
        return {
            kind: 'cmp-number',
            operator,
            value: normalizeExactLookupNumber(operand.value),
        };
    }
    return { kind: 'generic', compiled };
}
function slicePredicateMatches(predicate, view, offset, runtimeColumnStore) {
    switch (predicate.kind) {
        case 'eq-empty': {
            const tag = decodeValueTag(view.readTagAt(offset));
            const matches = tag === ValueTag.Empty || (tag === ValueTag.String && normalizeSliceString(runtimeColumnStore, view.readStringIdAt(offset)) === '');
            return predicate.negate ? !matches : matches;
        }
        case 'eq-bool': {
            const tag = decodeValueTag(view.readTagAt(offset));
            const numeric = tag === ValueTag.Number || tag === ValueTag.Boolean || tag === ValueTag.Empty ? view.readNumberAt(offset) : undefined;
            const matches = numeric !== undefined && (Object.is(numeric, -0) ? 0 : numeric) === (predicate.value ? 1 : 0);
            return predicate.negate ? !matches : matches;
        }
        case 'eq-number': {
            const tag = decodeValueTag(view.readTagAt(offset));
            const numeric = normalizeExactLookupNumber(view.readNumberAt(offset));
            const matches = (tag === ValueTag.Number || tag === ValueTag.Boolean || tag === ValueTag.Empty) && numeric === predicate.value;
            return predicate.negate ? !matches : matches;
        }
        case 'eq-string': {
            const tag = decodeValueTag(view.readTagAt(offset));
            if (predicate.negate && predicate.value === '') {
                return tag !== ValueTag.Empty && tag !== ValueTag.Error;
            }
            const matches = (tag === ValueTag.String || tag === ValueTag.Empty) &&
                (tag === ValueTag.Empty ? '' : normalizeSliceString(runtimeColumnStore, view.readStringIdAt(offset))) === predicate.value;
            return predicate.negate ? !matches : matches;
        }
        case 'cmp-number': {
            const tag = decodeValueTag(view.readTagAt(offset));
            if (tag !== ValueTag.Number && tag !== ValueTag.Boolean) {
                return false;
            }
            const numeric = normalizeExactLookupNumber(view.readNumberAt(offset));
            switch (predicate.operator) {
                case '>':
                    return numeric > predicate.value;
                case '>=':
                    return numeric >= predicate.value;
                case '<':
                    return numeric < predicate.value;
                case '<=':
                    return numeric <= predicate.value;
                default:
                    return false;
            }
        }
        case 'generic':
            return matchesCompiledCriteria(materializeSliceValue(view, offset, runtimeColumnStore), predicate.compiled);
    }
}
function slicePredicateMatchesEmpty(predicate) {
    switch (predicate.kind) {
        case 'eq-empty':
            return !predicate.negate;
        case 'eq-bool':
            return predicate.negate ? predicate.value : !predicate.value;
        case 'eq-number':
            return predicate.negate ? predicate.value !== 0 : predicate.value === 0;
        case 'eq-string':
            return predicate.negate ? predicate.value !== '' : predicate.value === '';
        case 'cmp-number':
            return false;
        case 'generic':
            return matchesCompiledCriteria({ tag: ValueTag.Empty }, predicate.compiled);
    }
}
function equalityIndexKeyForPredicate(predicate) {
    if (predicate.kind === 'eq-number' && !predicate.negate && predicate.value !== 0) {
        return { kind: 'number', value: predicate.value };
    }
    if (predicate.kind === 'eq-bool' && !predicate.negate && predicate.value) {
        return { kind: 'number', value: 1 };
    }
    if (predicate.kind === 'eq-string' && !predicate.negate && predicate.value !== '') {
        return { kind: 'string', value: predicate.value };
    }
    return undefined;
}
function equalityIndexKeyForStoredValue(tag, number, stringId, runtimeColumnStore) {
    if (tag === ValueTag.Number) {
        const value = normalizeExactLookupNumber(number);
        return value === 0 ? undefined : { kind: 'number', value };
    }
    if (tag === ValueTag.Boolean) {
        return number === 0 ? undefined : { kind: 'number', value: 1 };
    }
    if (tag === ValueTag.String) {
        const value = normalizeSliceString(runtimeColumnStore, stringId);
        return value === '' ? undefined : { kind: 'string', value };
    }
    return undefined;
}
function sortedUint32Rows(rows) {
    if (rows.length === 0) {
        return new Uint32Array(0);
    }
    rows.sort((left, right) => left - right);
    return Uint32Array.from(rows);
}
function getOrBuildEqualityRowIndex(view, runtimeColumnStore) {
    const cached = equalityRowIndexes.get(view.owner);
    if (cached !== undefined) {
        return cached;
    }
    const numbers = new Map();
    const strings = new Map();
    view.owner.pages.forEach((page) => {
        if (page.nonEmptyCount === 0) {
            return;
        }
        const rowEnd = page.rowStart + page.tags.length - 1;
        for (let row = page.rowStart; row <= rowEnd; row += 1) {
            const localRow = row - page.rowStart;
            const tag = decodeValueTag(page.tags[localRow]);
            const key = equalityIndexKeyForStoredValue(tag, page.numbers[localRow] ?? 0, page.stringIds[localRow] ?? 0, runtimeColumnStore);
            if (key?.kind === 'number') {
                let rows = numbers.get(key.value);
                if (rows === undefined) {
                    rows = [];
                    numbers.set(key.value, rows);
                }
                rows.push(row);
                continue;
            }
            if (key?.kind === 'string') {
                let rows = strings.get(key.value);
                if (rows === undefined) {
                    rows = [];
                    strings.set(key.value, rows);
                }
                rows.push(row);
            }
        }
    });
    const index = {
        numbers: new Map([...numbers].map(([value, rows]) => [value, sortedUint32Rows(rows)])),
        strings: new Map([...strings].map(([value, rows]) => [value, sortedUint32Rows(rows)])),
    };
    equalityRowIndexes.set(view.owner, index);
    return index;
}
function readIndexedEqualityRows(view, predicate, runtimeColumnStore) {
    const key = equalityIndexKeyForPredicate(predicate);
    if (key === undefined) {
        return undefined;
    }
    const index = getOrBuildEqualityRowIndex(view, runtimeColumnStore);
    const rows = key.kind === 'number' ? index.numbers.get(key.value) : index.strings.get(key.value);
    return sliceAbsoluteRowsToRangeView(rows ?? new Uint32Array(0), view.rowStart, view.rowEnd);
}
function createExactAggregateBucket() {
    return {
        count: 0,
        sum: 0,
        numericCount: 0,
        minimum: Number.POSITIVE_INFINITY,
        maximum: Number.NEGATIVE_INFINITY,
    };
}
function addExactAggregateMatch(bucket, aggregateView, offset) {
    bucket.count += 1;
    if (aggregateView === undefined) {
        return;
    }
    const tag = decodeValueTag(aggregateView.readTagAt(offset));
    if (tag === ValueTag.Error) {
        bucket.firstError ??= { tag: ValueTag.Error, code: aggregateView.readErrorAt(offset) ?? ErrorCode.None };
        return;
    }
    if (tag === ValueTag.Number) {
        const numeric = aggregateView.readNumberAt(offset);
        bucket.sum += numeric;
        bucket.numericCount += 1;
        bucket.minimum = Math.min(bucket.minimum, numeric);
        bucket.maximum = Math.max(bucket.maximum, numeric);
        return;
    }
    if (tag === ValueTag.Boolean) {
        bucket.sum += aggregateView.readNumberAt(offset) !== 0 ? 1 : 0;
        bucket.numericCount += 1;
        return;
    }
    if (tag === ValueTag.Empty) {
        bucket.numericCount += 1;
    }
}
function addExactAggregateBucket(index, key, aggregateView, offset) {
    let bucket = key.kind === 'number' ? index.numbers.get(key.value) : index.strings.get(key.value);
    if (bucket === undefined) {
        bucket = createExactAggregateBucket();
        if (key.kind === 'number') {
            index.numbers.set(key.value, bucket);
        }
        else {
            index.strings.set(key.value, bucket);
        }
    }
    addExactAggregateMatch(bucket, aggregateView, offset);
}
function exactAggregateValueFromBucket(bucket, aggregateKind) {
    if (bucket === undefined) {
        return aggregateKind === 'average' ? errorValue(ErrorCode.Div0) : numberValue(0);
    }
    if (aggregateKind === 'count') {
        return numberValue(bucket.count);
    }
    if (bucket.firstError) {
        return bucket.firstError;
    }
    if (aggregateKind === 'sum') {
        return numberValue(bucket.sum);
    }
    if (aggregateKind === 'average') {
        return bucket.numericCount === 0 ? errorValue(ErrorCode.Div0) : numberValue(bucket.sum / bucket.numericCount);
    }
    if (aggregateKind === 'min') {
        return numberValue(bucket.minimum === Number.POSITIVE_INFINITY ? 0 : bucket.minimum);
    }
    return numberValue(bucket.maximum === Number.NEGATIVE_INFINITY ? 0 : bucket.maximum);
}
function exactAggregateBucketValue(index, key, aggregateKind) {
    return exactAggregateValueFromBucket(key.kind === 'number' ? index.numbers.get(key.value) : index.strings.get(key.value), aggregateKind);
}
function exactAggregateIndexCacheKey(request) {
    return [
        request.criteriaRegionId,
        request.criteriaView.columnVersion,
        request.criteriaView.structureVersion,
        request.aggregateRegionId ?? -1,
        request.aggregateView?.columnVersion ?? -1,
        request.aggregateView?.structureVersion ?? -1,
    ].join('\u0001');
}
function exactKeyCachePart(key) {
    return key.kind === 'number' ? `n:${key.value}` : `s:${key.value}`;
}
function compoundExactTupleKey(keys) {
    return keys.map(exactKeyCachePart).join('\u0002');
}
function compoundExactAggregateCacheKey(request) {
    return [
        request.aggregateKind,
        request.criteriaPairs
            .map(({ regionId, view, key }) => `${regionId}:${view.columnVersion}:${view.structureVersion}:${exactKeyCachePart(key)}`)
            .join('\u0002'),
        request.aggregateRegionId ?? -1,
        request.aggregateView?.columnVersion ?? -1,
        request.aggregateView?.structureVersion ?? -1,
    ].join('\u0001');
}
function compoundExactAggregateIndexCacheKey(request) {
    return [
        request.criteriaPairs.map(({ regionId, view }) => `${regionId}:${view.columnVersion}:${view.structureVersion}`).join('\u0002'),
        request.aggregateRegionId ?? -1,
        request.aggregateView?.columnVersion ?? -1,
        request.aggregateView?.structureVersion ?? -1,
    ].join('\u0001');
}
function countNonEmptyRowsInView(view) {
    let count = 0;
    view.owner.pages.forEach((page) => {
        const rowStart = Math.max(view.rowStart, page.rowStart);
        const rowEnd = Math.min(view.rowEnd, page.rowStart + page.tags.length - 1);
        if (rowStart > rowEnd || page.nonEmptyCount === 0) {
            return;
        }
        if (rowStart === page.rowStart && rowEnd === page.rowStart + page.tags.length - 1) {
            count += page.nonEmptyCount;
            return;
        }
        for (let row = rowStart; row <= rowEnd; row += 1) {
            if (decodeValueTag(page.tags[row - page.rowStart]) !== ValueTag.Empty) {
                count += 1;
            }
        }
    });
    return count;
}
function forEachNonEmptyRowOffsetInView(view, fn) {
    view.owner.pages.forEach((page) => {
        const rowStart = Math.max(view.rowStart, page.rowStart);
        const rowEnd = Math.min(view.rowEnd, page.rowStart + page.tags.length - 1);
        if (rowStart > rowEnd || page.nonEmptyCount === 0) {
            return;
        }
        if (rowStart === page.rowStart && rowEnd === page.rowStart + page.tags.length - 1 && page.nonEmptyCount === page.tags.length) {
            for (let row = rowStart; row <= rowEnd; row += 1) {
                fn(row - view.rowStart);
            }
            return;
        }
        for (let row = rowStart; row <= rowEnd; row += 1) {
            if (decodeValueTag(page.tags[row - page.rowStart]) !== ValueTag.Empty) {
                fn(row - view.rowStart);
            }
        }
    });
}
export function createCriterionRangeCacheService(args) {
    const exactAggregateIndexes = new Map();
    const compoundExactAggregateResults = new Map();
    const compoundExactAggregateIndexes = new Map();
    const getColumnView = (request) => {
        const direct = Reflect.get(args.runtimeColumnStore, 'getColumnView');
        if (typeof direct === 'function') {
            return direct.call(args.runtimeColumnStore, request);
        }
        const slice = args.runtimeColumnStore.getColumnSlice(request);
        return {
            owner: {
                sheetName: slice.sheetName,
                col: slice.col,
                columnVersion: slice.columnVersion,
                structureVersion: slice.structureVersion,
                sheetColumnVersions: slice.sheetColumnVersions,
                pages: new Map(),
            },
            sheetName: slice.sheetName,
            rowStart: slice.rowStart,
            rowEnd: slice.rowEnd,
            col: slice.col,
            length: slice.length,
            columnVersion: slice.columnVersion,
            structureVersion: slice.structureVersion,
            sheetColumnVersions: slice.sheetColumnVersions,
            readTagAt(offset) {
                return slice.tags[offset] ?? ValueTag.Empty;
            },
            readNumberAt(offset) {
                return slice.numbers[offset] ?? 0;
            },
            readStringIdAt(offset) {
                return slice.stringIds[offset] ?? 0;
            },
            readErrorAt(offset) {
                return slice.errors[offset] ?? ErrorCode.None;
            },
            readCellValueAt(offset) {
                return materializeSliceValue(this, offset, args.runtimeColumnStore);
            },
        };
    };
    const rangeRegionId = (range) => args.regionGraph.internSingleColumnRegion({
        sheetName: range.sheetName,
        rowStart: range.rowStart,
        rowEnd: range.rowEnd,
        col: range.col,
    });
    const buildExactAggregateIndex = (criteriaView, aggregateView) => {
        const buckets = {
            numbers: new Map(),
            strings: new Map(),
        };
        criteriaView.owner.pages.forEach((page) => {
            const rowStart = Math.max(criteriaView.rowStart, page.rowStart);
            const rowEnd = Math.min(criteriaView.rowEnd, page.rowStart + page.tags.length - 1);
            if (rowStart > rowEnd || page.nonEmptyCount === 0) {
                return;
            }
            for (let row = rowStart; row <= rowEnd; row += 1) {
                const localRow = row - page.rowStart;
                const key = equalityIndexKeyForStoredValue(decodeValueTag(page.tags[localRow]), page.numbers[localRow] ?? 0, page.stringIds[localRow] ?? 0, args.runtimeColumnStore);
                if (key === undefined) {
                    continue;
                }
                addExactAggregateBucket(buckets, key, aggregateView, row - criteriaView.rowStart);
            }
        });
        return buckets;
    };
    const equalityKeyAtOffset = (view, offset) => equalityIndexKeyForStoredValue(decodeValueTag(view.readTagAt(offset)), view.readNumberAt(offset), view.readStringIdAt(offset), args.runtimeColumnStore);
    const buildCompoundExactAggregateIndex = (resolvedPairs, aggregateView, length) => {
        const buckets = new Map();
        for (let rowOffset = 0; rowOffset < length; rowOffset += 1) {
            const rowKeys = [];
            for (const pair of resolvedPairs) {
                const key = equalityKeyAtOffset(pair.view, rowOffset);
                if (key === undefined) {
                    rowKeys.length = 0;
                    break;
                }
                rowKeys.push(key);
            }
            if (rowKeys.length === 0) {
                continue;
            }
            const tupleKey = compoundExactTupleKey(rowKeys);
            let bucket = buckets.get(tupleKey);
            if (bucket === undefined) {
                if (buckets.size >= COMPOUND_EXACT_AGGREGATE_BUCKET_LIMIT) {
                    return undefined;
                }
                bucket = createExactAggregateBucket();
                buckets.set(tupleKey, bucket);
            }
            addExactAggregateMatch(bucket, aggregateView, rowOffset);
        }
        return { buckets };
    };
    const getOrBuildExactAggregate = (request) => {
        const criteriaPredicate = buildSlicePredicate(compileCriteriaMatcher(request.criteriaPair.criteria));
        const criteriaKey = equalityIndexKeyForPredicate(criteriaPredicate);
        if (criteriaKey === undefined) {
            return undefined;
        }
        if (request.aggregateKind !== 'count' && request.aggregateRange === undefined) {
            return undefined;
        }
        if (request.aggregateRange !== undefined && request.aggregateRange.length !== request.criteriaPair.range.length) {
            return undefined;
        }
        const criteriaRegionId = rangeRegionId(request.criteriaPair.range);
        const criteriaView = getColumnView(request.criteriaPair.range);
        const aggregateRegionId = request.aggregateRange === undefined ? undefined : rangeRegionId(request.aggregateRange);
        const aggregateView = request.aggregateRange === undefined ? undefined : getColumnView(request.aggregateRange);
        const cacheKey = exactAggregateIndexCacheKey({
            criteriaRegionId,
            criteriaView,
            ...(aggregateRegionId === undefined ? {} : { aggregateRegionId }),
            ...(aggregateView === undefined ? {} : { aggregateView }),
        });
        let index = exactAggregateIndexes.get(cacheKey);
        if (index === undefined) {
            index = buildExactAggregateIndex(criteriaView, aggregateView);
            exactAggregateIndexes.set(cacheKey, index);
        }
        return exactAggregateBucketValue(index, criteriaKey, request.aggregateKind);
    };
    const getOrBuildCompoundExactAggregate = (request) => {
        const { criteriaPairs } = request;
        if (criteriaPairs.length < 2) {
            return undefined;
        }
        const expectedLength = criteriaPairs[0].range.length;
        if (criteriaPairs.some((pair) => pair.range.length !== expectedLength)) {
            return undefined;
        }
        if (request.aggregateKind !== 'count' && request.aggregateRange === undefined) {
            return undefined;
        }
        if (request.aggregateRange !== undefined && request.aggregateRange.length !== expectedLength) {
            return undefined;
        }
        const predicates = criteriaPairs.map((pair) => buildSlicePredicate(compileCriteriaMatcher(pair.criteria)));
        const keys = [];
        for (const predicate of predicates) {
            const key = equalityIndexKeyForPredicate(predicate);
            if (key === undefined) {
                return undefined;
            }
            keys.push(key);
        }
        const resolvedPairs = criteriaPairs.map((pair, index) => ({
            regionId: rangeRegionId(pair.range),
            view: getColumnView(pair.range),
            predicate: predicates[index],
            key: keys[index],
        }));
        const aggregateRegionId = request.aggregateRange === undefined ? undefined : rangeRegionId(request.aggregateRange);
        const aggregateView = request.aggregateRange === undefined ? undefined : getColumnView(request.aggregateRange);
        const cacheKey = compoundExactAggregateCacheKey({
            aggregateKind: request.aggregateKind,
            criteriaPairs: resolvedPairs,
            ...(aggregateRegionId === undefined ? {} : { aggregateRegionId }),
            ...(aggregateView === undefined ? {} : { aggregateView }),
        });
        const cached = compoundExactAggregateResults.get(cacheKey);
        if (cached !== undefined) {
            return cached;
        }
        if (request.useCompoundBucketIndex) {
            const indexCacheKey = compoundExactAggregateIndexCacheKey({
                criteriaPairs: resolvedPairs,
                ...(aggregateRegionId === undefined ? {} : { aggregateRegionId }),
                ...(aggregateView === undefined ? {} : { aggregateView }),
            });
            let index = compoundExactAggregateIndexes.get(indexCacheKey);
            if (index === undefined) {
                index = buildCompoundExactAggregateIndex(resolvedPairs, aggregateView, expectedLength);
                if (index !== undefined) {
                    compoundExactAggregateIndexes.set(indexCacheKey, index);
                }
            }
            if (index !== undefined) {
                const result = exactAggregateValueFromBucket(index.buckets.get(compoundExactTupleKey(keys)), request.aggregateKind);
                compoundExactAggregateResults.set(cacheKey, result);
                return result;
            }
        }
        const rowsets = resolvedPairs.map(({ view, predicate }) => readIndexedEqualityRows(view, predicate, args.runtimeColumnStore));
        if (rowsets.some((rowset) => rowset === undefined)) {
            return undefined;
        }
        let limitingPairIndex = 0;
        let limitingRows = rowsets[0];
        for (let index = 1; index < rowsets.length; index += 1) {
            const rowset = rowsets[index];
            if (rowset.cardinality < limitingRows.cardinality) {
                limitingPairIndex = index;
                limitingRows = rowset;
            }
        }
        const bucket = createExactAggregateBucket();
        limitingRows.forEachOffset((rowOffset) => {
            for (let pairIndex = 0; pairIndex < resolvedPairs.length; pairIndex += 1) {
                if (pairIndex === limitingPairIndex) {
                    continue;
                }
                const pair = resolvedPairs[pairIndex];
                if (!slicePredicateMatches(pair.predicate, pair.view, rowOffset, args.runtimeColumnStore)) {
                    return;
                }
            }
            addExactAggregateMatch(bucket, aggregateView, rowOffset);
        });
        const result = exactAggregateValueFromBucket(bucket.count === 0 ? undefined : bucket, request.aggregateKind);
        compoundExactAggregateResults.set(cacheKey, result);
        return result;
    };
    const getOrBuildExactCriteriaAggregate = (request) => {
        return request.criteriaPairs.length === 1
            ? getOrBuildExactAggregate({
                criteriaPair: request.criteriaPairs[0],
                ...(request.aggregateRange === undefined ? {} : { aggregateRange: request.aggregateRange }),
                aggregateKind: request.aggregateKind,
            })
            : getOrBuildCompoundExactAggregate(request);
    };
    const getOrBuildIndexedPredicateAggregate = (request) => {
        return getOrBuildIndexedPredicateAggregateFromColumnViews({
            request,
            runtimeColumnStore: args.runtimeColumnStore,
            getColumnView,
            buildSlicePredicate,
            readIndexedEqualityRows,
            slicePredicateMatches,
            createExactAggregateBucket,
            addExactAggregateMatch,
            exactAggregateValueFromBucket,
        });
    };
    const getOrBuildMatchingRows = (request) => {
        const { criteriaPairs } = request;
        if (criteriaPairs.length === 0) {
            return errorValue(ErrorCode.Value);
        }
        const expectedLength = criteriaPairs[0].range.length;
        if (criteriaPairs.some((pair) => pair.range.length !== expectedLength)) {
            return errorValue(ErrorCode.Value);
        }
        const resolvedPairs = criteriaPairs.map((pair) => ({
            regionId: args.regionGraph.internSingleColumnRegion({
                sheetName: pair.range.sheetName,
                rowStart: pair.range.rowStart,
                rowEnd: pair.range.rowEnd,
                col: pair.range.col,
            }),
            view: getColumnView({
                sheetName: pair.range.sheetName,
                rowStart: pair.range.rowStart,
                rowEnd: pair.range.rowEnd,
                col: pair.range.col,
            }),
            criteria: pair.criteria,
        }));
        const versionStamp = resolvedPairs
            .map(({ regionId, view }) => `${regionId}:${view.columnVersion}:${view.structureVersion}`)
            .join('\u0001');
        const existing = args.depPatternStore.getCriteriaPattern({
            regionIds: resolvedPairs.map(({ regionId }) => regionId),
            criteriaKeys: resolvedPairs.map(({ criteria }) => criteriaCacheKey(criteria)),
            versionStamp,
        });
        if (existing) {
            return existing;
        }
        const predicates = criteriaPairs.map((pair) => buildSlicePredicate(compileCriteriaMatcher(pair.criteria)));
        const matchingRows = [];
        let limitingPairIndex;
        let limitingIndexedRows;
        let limitingPairNonEmptyRows = Number.POSITIVE_INFINITY;
        for (let pairIndex = 0; pairIndex < predicates.length; pairIndex += 1) {
            const indexedRows = readIndexedEqualityRows(resolvedPairs[pairIndex].view, predicates[pairIndex], args.runtimeColumnStore);
            if (indexedRows !== undefined) {
                if (indexedRows.cardinality < limitingPairNonEmptyRows) {
                    limitingPairIndex = undefined;
                    limitingIndexedRows = indexedRows;
                    limitingPairNonEmptyRows = indexedRows.cardinality;
                }
                continue;
            }
            if (slicePredicateMatchesEmpty(predicates[pairIndex])) {
                continue;
            }
            const view = resolvedPairs[pairIndex].view;
            if (view.owner.pages.size === 0) {
                continue;
            }
            const nonEmptyRows = countNonEmptyRowsInView(view);
            if (nonEmptyRows < limitingPairNonEmptyRows) {
                limitingPairIndex = pairIndex;
                limitingIndexedRows = undefined;
                limitingPairNonEmptyRows = nonEmptyRows;
            }
        }
        const visitCandidate = (rowOffset) => {
            let matches = true;
            for (let pairIndex = 0; pairIndex < predicates.length; pairIndex += 1) {
                if (!slicePredicateMatches(predicates[pairIndex], resolvedPairs[pairIndex].view, rowOffset, args.runtimeColumnStore)) {
                    matches = false;
                    break;
                }
            }
            if (matches) {
                matchingRows.push(rowOffset);
            }
        };
        if (limitingIndexedRows !== undefined) {
            limitingIndexedRows.forEachOffset(visitCandidate);
        }
        else if (limitingPairIndex === undefined) {
            for (let rowOffset = 0; rowOffset < expectedLength; rowOffset += 1) {
                visitCandidate(rowOffset);
            }
        }
        else {
            forEachNonEmptyRowOffsetInView(resolvedPairs[limitingPairIndex].view, visitCandidate);
        }
        const entry = {
            rows: Uint32Array.from(matchingRows),
            length: matchingRows.length,
            pairVersions: resolvedPairs.map(({ view }) => ({
                columnVersion: view.columnVersion,
                structureVersion: view.structureVersion,
            })),
        };
        return args.depPatternStore.setCriteriaPattern({
            regionIds: resolvedPairs.map(({ regionId }) => regionId),
            criteriaKeys: resolvedPairs.map(({ criteria }) => criteriaCacheKey(criteria)),
            versionStamp,
            rows: entry.rows,
            length: entry.length,
        });
    };
    return {
        getOrBuildCompoundExactAggregate,
        getOrBuildExactCriteriaAggregate,
        getOrBuildExactAggregate,
        getOrBuildIndexedPredicateAggregate,
        getOrBuildMatchingRows,
    };
}
//# sourceMappingURL=criterion-range-cache-service.js.map