import { parseFormula, parseRangeAddress, } from '@bilig/formula';
import { ValueTag } from '@bilig/protocol';
import { mapStructuralAxisIndex, mapStructuralAxisInterval } from '../../engine-structural-utils.js';
import { resolveRuntimeDirectLookupBinding } from '../direct-vector-lookup.js';
import { appendDirectCriteriaResultTransform, callName, flattenCriteriaProduct, resolveDirectCriteriaRange, staticCellValue, } from './formula-binding-direct-criteria-ast.js';
function internPrimaryDirectAggregateRegion(args) {
    return args.regionGraph.internSingleColumnRegion({
        sheetName: args.sheetName,
        rowStart: args.rowStart,
        rowEnd: args.rowEnd,
        col: args.col,
    });
}
export function renameDirectAggregateDescriptorSheet(args) {
    if (args.descriptor.sheetName !== args.oldSheetName) {
        return args.descriptor;
    }
    return {
        ...args.descriptor,
        sheetName: args.newSheetName,
    };
}
export function rewriteDirectAggregateDescriptorForStructuralTransform(args) {
    if (args.descriptor.sheetName !== args.targetSheetName) {
        return undefined;
    }
    if (args.transform.kind === 'insert' && args.transform.axis === 'row') {
        return rewriteDirectAggregateDescriptorForRowInsert(args);
    }
    const nextRows = args.transform.axis === 'row'
        ? mapDirectAggregateReferenceInterval(args.descriptor.rowStart, args.descriptor.rowEnd, args.transform)
        : { start: args.descriptor.rowStart, end: args.descriptor.rowEnd };
    const descriptorColEnd = args.descriptor.colEnd ?? args.descriptor.col;
    const nextCols = args.transform.axis === 'column'
        ? mapDirectAggregateReferenceInterval(args.descriptor.col, descriptorColEnd, args.transform)
        : { start: args.descriptor.col, end: descriptorColEnd };
    if (!nextRows || !nextCols) {
        return undefined;
    }
    const regionId = internPrimaryDirectAggregateRegion({
        regionGraph: args.regionGraph,
        sheetName: args.descriptor.sheetName,
        rowStart: nextRows.start,
        rowEnd: nextRows.end,
        col: nextCols.start,
    });
    return {
        regionId,
        aggregateKind: args.descriptor.aggregateKind,
        sheetName: args.descriptor.sheetName,
        rowStart: nextRows.start,
        rowEnd: nextRows.end,
        col: nextCols.start,
        colEnd: nextCols.end,
        length: (nextRows.end - nextRows.start + 1) * (nextCols.end - nextCols.start + 1),
        ...(args.descriptor.resultOffset !== undefined ? { resultOffset: args.descriptor.resultOffset } : {}),
    };
}
function rewriteDirectAggregateDescriptorForRowInsert(args) {
    const rowStart = args.descriptor.rowStart >= args.transform.start ? args.descriptor.rowStart + args.transform.count : args.descriptor.rowStart;
    const rowEnd = args.descriptor.rowEnd >= args.transform.start ? args.descriptor.rowEnd + args.transform.count : args.descriptor.rowEnd;
    const colEnd = args.descriptor.colEnd ?? args.descriptor.col;
    const regionId = internPrimaryDirectAggregateRegion({
        regionGraph: args.regionGraph,
        sheetName: args.descriptor.sheetName,
        rowStart,
        rowEnd,
        col: args.descriptor.col,
    });
    return {
        regionId,
        aggregateKind: args.descriptor.aggregateKind,
        sheetName: args.descriptor.sheetName,
        rowStart,
        rowEnd,
        col: args.descriptor.col,
        colEnd,
        length: (rowEnd - rowStart + 1) * (colEnd - args.descriptor.col + 1),
        ...(args.descriptor.resultOffset !== undefined ? { resultOffset: args.descriptor.resultOffset } : {}),
    };
}
function mapDirectAggregateReferenceInterval(start, end, transform) {
    if (transform.kind !== 'move') {
        return mapStructuralAxisInterval(start, end, transform);
    }
    if (transform.target <= transform.start) {
        return mapStructuralAxisInterval(start, end, transform);
    }
    const movedStart = transform.start;
    const movedEnd = transform.start + transform.count - 1;
    if (end < movedStart || start > movedEnd || (start >= movedStart && end <= movedEnd)) {
        return mapStructuralAxisInterval(start, end, transform);
    }
    const survivorSegments = [];
    if (start < movedStart) {
        survivorSegments.push({ start, end: Math.min(end, movedStart - 1) });
    }
    if (end > movedEnd) {
        survivorSegments.push({ start: Math.max(start, movedEnd + 1), end });
    }
    let nextStart;
    let nextEnd;
    survivorSegments.forEach((segment) => {
        const mappedStart = mapStructuralAxisIndex(segment.start, transform);
        const mappedEnd = mapStructuralAxisIndex(segment.end, transform);
        if (mappedStart === undefined || mappedEnd === undefined) {
            return;
        }
        nextStart = nextStart === undefined ? Math.min(mappedStart, mappedEnd) : Math.min(nextStart, mappedStart, mappedEnd);
        nextEnd = nextEnd === undefined ? Math.max(mappedStart, mappedEnd) : Math.max(nextEnd, mappedStart, mappedEnd);
    });
    return nextStart === undefined || nextEnd === undefined ? undefined : { start: nextStart, end: nextEnd };
}
const DIRECT_CRITERIA_PLAN_CALLEES = new Set([
    'COUNTIF',
    'COUNTIFS',
    'SUMIF',
    'SUMIFS',
    'AVERAGEIF',
    'AVERAGEIFS',
    'MINIFS',
    'MAXIFS',
    'INDEX',
]);
function mayContainDirectCriteriaPlanCall(compiled) {
    return compiled.jsPlan.some((instruction) => {
        const opcode = Reflect.get(instruction, 'opcode');
        const callee = Reflect.get(instruction, 'callee');
        return opcode === 'call' && typeof callee === 'string' && DIRECT_CRITERIA_PLAN_CALLEES.has(callee.trim().toUpperCase());
    });
}
export function buildDirectCriteriaDescriptor(args) {
    const sameCellRef = (left, right) => {
        return (left.ref.trim().toUpperCase() === right.ref.trim().toUpperCase() &&
            (left.sheetName ?? args.ownerSheetName).trim().toUpperCase() === (right.sheetName ?? args.ownerSheetName).trim().toUpperCase());
    };
    const resolveDatePartCellRef = (node, expectedCallee) => {
        if (callName(node) !== expectedCallee || node?.kind !== 'CallExpr' || node.args.length !== 1) {
            return undefined;
        }
        const cellRef = node.args[0];
        return cellRef?.kind === 'CellRef' ? cellRef : undefined;
    };
    const resolveMonthStartCellRef = (node) => {
        if (callName(node) !== 'DATE' || node?.kind !== 'CallExpr' || node.args.length !== 3) {
            return undefined;
        }
        const yearCell = resolveDatePartCellRef(node.args[0], 'YEAR');
        const monthCell = resolveDatePartCellRef(node.args[1], 'MONTH');
        const day = staticCellValue(node.args[2]);
        if (!yearCell || !monthCell || day?.tag !== ValueTag.Number || !Object.is(day.value, 1) || !sameCellRef(yearCell, monthCell)) {
            return undefined;
        }
        return yearCell;
    };
    const resolveMonthBoundaryCellRef = (node) => {
        const monthStartCell = resolveMonthStartCellRef(node);
        if (monthStartCell) {
            return { cellRef: monthStartCell, offsetMonths: 0 };
        }
        if (callName(node) !== 'EDATE' || node?.kind !== 'CallExpr' || node.args.length !== 2) {
            return undefined;
        }
        const startCell = resolveMonthStartCellRef(node.args[0]);
        const offset = staticCellValue(node.args[1]);
        if (!startCell || offset?.tag !== ValueTag.Number || !Number.isFinite(offset.value)) {
            return undefined;
        }
        return { cellRef: startCell, offsetMonths: offset.value };
    };
    const buildMonthBoundaryCriterion = (boundary, prefix, suffix) => {
        const sheetName = boundary.cellRef.sheetName ?? args.ownerSheetName;
        if (!args.workbook.getSheet(sheetName)) {
            return undefined;
        }
        return {
            kind: 'cell-month-boundary-string-concat',
            cellIndex: args.ensureCellTracked(sheetName, boundary.cellRef.ref),
            prefix,
            suffix,
            offsetMonths: boundary.offsetMonths,
        };
    };
    const resolveEmptyStringCellGuard = (node) => {
        if (!node || node.kind !== 'BinaryExpr' || (node.operator !== '=' && node.operator !== '<>')) {
            return undefined;
        }
        const leftLiteral = staticCellValue(node.left);
        const rightLiteral = staticCellValue(node.right);
        const leftCell = node.left.kind === 'CellRef' ? node.left : undefined;
        const rightCell = node.right.kind === 'CellRef' ? node.right : undefined;
        if (leftCell && rightLiteral?.tag === ValueTag.String && rightLiteral.value === '') {
            return { cellRef: leftCell, operator: node.operator };
        }
        if (rightCell && leftLiteral?.tag === ValueTag.String && leftLiteral.value === '') {
            return { cellRef: rightCell, operator: node.operator };
        }
        return undefined;
    };
    const appendIfEmptyCellTransform = (descriptor, cellRef, fallback) => {
        if (!descriptor || !fallback) {
            return undefined;
        }
        const sheetName = cellRef.sheetName ?? args.ownerSheetName;
        if (!args.workbook.getSheet(sheetName)) {
            return undefined;
        }
        return appendDirectCriteriaResultTransform(descriptor, {
            kind: 'if-empty-cell',
            cellIndex: args.ensureCellTracked(sheetName, cellRef.ref),
            fallback,
        });
    };
    const resolveCriterionOperand = (criterionNode) => {
        if (!criterionNode) {
            return undefined;
        }
        if (criterionNode.kind === 'CellRef') {
            const sheetName = criterionNode.sheetName ?? args.ownerSheetName;
            if (!args.workbook.getSheet(sheetName)) {
                return undefined;
            }
            return {
                kind: 'cell',
                cellIndex: args.ensureCellTracked(sheetName, criterionNode.ref),
            };
        }
        if (criterionNode.kind === 'BinaryExpr' && criterionNode.operator === '&') {
            const leftLiteral = staticCellValue(criterionNode.left);
            const rightLiteral = staticCellValue(criterionNode.right);
            const leftCell = criterionNode.left.kind === 'CellRef' ? criterionNode.left : undefined;
            const rightCell = criterionNode.right.kind === 'CellRef' ? criterionNode.right : undefined;
            const leftMonthBoundary = resolveMonthBoundaryCellRef(criterionNode.left);
            const rightMonthBoundary = resolveMonthBoundaryCellRef(criterionNode.right);
            if (leftLiteral?.tag === ValueTag.String && rightMonthBoundary) {
                return buildMonthBoundaryCriterion(rightMonthBoundary, leftLiteral.value, '');
            }
            if (rightLiteral?.tag === ValueTag.String && leftMonthBoundary) {
                return buildMonthBoundaryCriterion(leftMonthBoundary, '', rightLiteral.value);
            }
            if (leftLiteral?.tag === ValueTag.String && rightCell) {
                const sheetName = rightCell.sheetName ?? args.ownerSheetName;
                if (!args.workbook.getSheet(sheetName)) {
                    return undefined;
                }
                return {
                    kind: 'cell-string-concat',
                    cellIndex: args.ensureCellTracked(sheetName, rightCell.ref),
                    prefix: leftLiteral.value,
                    suffix: '',
                };
            }
            if (rightLiteral?.tag === ValueTag.String && leftCell) {
                const sheetName = leftCell.sheetName ?? args.ownerSheetName;
                if (!args.workbook.getSheet(sheetName)) {
                    return undefined;
                }
                return {
                    kind: 'cell-string-concat',
                    cellIndex: args.ensureCellTracked(sheetName, leftCell.ref),
                    prefix: '',
                    suffix: rightLiteral.value,
                };
            }
        }
        const literal = staticCellValue(criterionNode);
        return literal ? { kind: 'literal', value: literal } : undefined;
    };
    const withRangeRegion = (range) => ({
        ...range,
        regionId: args.regionGraph.internSingleColumnRegion({
            sheetName: range.sheetName,
            rowStart: range.rowStart,
            rowEnd: range.rowEnd,
            col: range.col,
        }),
    });
    const resolveIndexOffsetOperand = (node) => {
        if (!node) {
            return undefined;
        }
        const literal = staticCellValue(node);
        if (literal?.tag === ValueTag.Number) {
            return { kind: 'literal-number', value: literal.value };
        }
        if (node.kind !== 'CellRef') {
            return undefined;
        }
        const sheetName = node.sheetName ?? args.ownerSheetName;
        if (!args.workbook.getSheet(sheetName)) {
            return undefined;
        }
        return {
            kind: 'cell',
            cellIndex: args.ensureCellTracked(sheetName, node.ref),
        };
    };
    const pair = (rangeNode, criterionNode) => {
        const range = resolveDirectCriteriaRange(rangeNode, args.ownerSheetName);
        const criterion = resolveCriterionOperand(criterionNode);
        if (!range || !criterion) {
            return undefined;
        }
        return {
            range: withRangeRegion(range),
            criterion,
        };
    };
    const directCriteriaPairFromEquality = (node) => {
        if (node.kind !== 'BinaryExpr' || node.operator !== '=') {
            return undefined;
        }
        return pair(node.left, node.right) ?? pair(node.right, node.left);
    };
    const buildCriteriaPairsFromProduct = (node) => {
        const pairs = [];
        for (const factor of flattenCriteriaProduct(node)) {
            const criteriaPair = directCriteriaPairFromEquality(factor);
            if (!criteriaPair) {
                return undefined;
            }
            pairs.push(criteriaPair);
        }
        if (pairs.length === 0) {
            return undefined;
        }
        const expectedLength = pairs[0].range.length;
        return pairs.every((current) => current.range.length === expectedLength) ? pairs : undefined;
    };
    const isStaticNumber = (node, expected) => {
        const value = staticCellValue(node);
        return value?.tag === ValueTag.Number && Object.is(value.value, expected);
    };
    const buildIndexMatchCriteriaDescriptor = (node) => {
        const aggregateRange = resolveDirectCriteriaRange(node.args[0], args.ownerSheetName);
        const rowLookup = node.args[1];
        const columnLookup = node.args[2];
        if (!aggregateRange ||
            node.args.length < 2 ||
            node.args.length > 3 ||
            (columnLookup !== undefined && !isStaticNumber(columnLookup, 1)) ||
            rowLookup?.kind !== 'CallExpr' ||
            rowLookup.callee.trim().toUpperCase() !== 'MATCH' ||
            rowLookup.args.length !== 3 ||
            !isStaticNumber(rowLookup.args[0], 1) ||
            !isStaticNumber(rowLookup.args[2], 0)) {
            return undefined;
        }
        const criteriaPairs = buildCriteriaPairsFromProduct(rowLookup.args[1]);
        if (!criteriaPairs || criteriaPairs.some((criteriaPair) => criteriaPair.range.length !== aggregateRange.length)) {
            return undefined;
        }
        return {
            aggregateKind: 'first',
            aggregateRange: withRangeRegion(aggregateRange),
            criteriaPairs,
        };
    };
    const buildSimpleIndexMatchCriteriaDescriptor = (node) => {
        const aggregateRange = resolveDirectCriteriaRange(node.args[0], args.ownerSheetName);
        const rowLookup = node.args[1];
        const columnLookup = node.args[2];
        if (!aggregateRange ||
            node.args.length < 2 ||
            node.args.length > 3 ||
            (columnLookup !== undefined && !isStaticNumber(columnLookup, 1)) ||
            rowLookup?.kind !== 'CallExpr' ||
            rowLookup.callee.trim().toUpperCase() !== 'MATCH' ||
            rowLookup.args.length !== 3 ||
            !isStaticNumber(rowLookup.args[2], 0)) {
            return undefined;
        }
        const criteriaPair = pair(rowLookup.args[1], rowLookup.args[0]);
        if (!criteriaPair || criteriaPair.range.length !== aggregateRange.length) {
            return undefined;
        }
        return {
            aggregateKind: 'first',
            aggregateRange: withRangeRegion(aggregateRange),
            firstMatchMode: 'exact-lookup',
            criteriaPairs: [criteriaPair],
        };
    };
    const buildIndexReferenceCriteriaDescriptor = (node) => {
        if (node.args.length < 2 || node.args.length > 3) {
            return undefined;
        }
        const rangeNode = node.args[0];
        if (!rangeNode || rangeNode.kind !== 'RangeRef' || rangeNode.refKind !== 'cells' || rangeNode.sheetEndName !== undefined) {
            return undefined;
        }
        const parsedRange = parseRangeAddress(`${rangeNode.start}:${rangeNode.end}`, rangeNode.sheetName ?? args.ownerSheetName);
        if (parsedRange.kind !== 'cells') {
            return undefined;
        }
        const sheetName = parsedRange.sheetName ?? rangeNode.sheetName ?? args.ownerSheetName;
        if (!args.workbook.getSheet(sheetName)) {
            return undefined;
        }
        const rowStart = parsedRange.start.row;
        const rowEnd = parsedRange.end.row;
        const colStart = parsedRange.start.col;
        const colEnd = parsedRange.end.col;
        const colCount = colEnd - colStart + 1;
        const columnLookup = node.args[2];
        const columnValue = staticCellValue(columnLookup);
        const columnOffset = columnLookup === undefined
            ? colCount === 1
                ? 1
                : undefined
            : columnValue?.tag === ValueTag.Number
                ? Math.trunc(columnValue.value)
                : undefined;
        if (columnOffset === undefined || columnOffset < 1 || columnOffset > colCount) {
            return undefined;
        }
        const offsetOperand = resolveIndexOffsetOperand(node.args[1]);
        if (!offsetOperand) {
            return undefined;
        }
        return {
            aggregateKind: 'first',
            aggregateRange: withRangeRegion({
                sheetName,
                rowStart,
                rowEnd,
                col: colStart + columnOffset - 1,
                length: rowEnd - rowStart + 1,
            }),
            offsetOperand,
            criteriaPairs: [],
        };
    };
    const buildDescriptorForNode = (node) => {
        if (node.kind !== 'CallExpr') {
            return undefined;
        }
        const callee = node.callee.trim().toUpperCase();
        if (callee === 'IF') {
            const guard = resolveEmptyStringCellGuard(node.args[0]);
            if (!guard || node.args.length < 3) {
                return undefined;
            }
            const trueValue = staticCellValue(node.args[1]);
            const falseValue = staticCellValue(node.args[2]);
            if (guard.operator === '=' && trueValue) {
                return appendIfEmptyCellTransform(buildDescriptorForNode(node.args[2]), guard.cellRef, trueValue);
            }
            if (guard.operator === '<>' && falseValue) {
                return appendIfEmptyCellTransform(buildDescriptorForNode(node.args[1]), guard.cellRef, falseValue);
            }
            return undefined;
        }
        if (callee === 'ROUND') {
            const digits = staticCellValue(node.args[1]);
            if (node.args.length !== 2 || !digits) {
                return undefined;
            }
            const inner = buildDescriptorForNode(node.args[0]);
            return inner ? appendDirectCriteriaResultTransform(inner, { kind: 'round', digits }) : undefined;
        }
        if (callee === 'IFERROR') {
            const fallback = staticCellValue(node.args[1]);
            if (node.args.length !== 2 || !fallback) {
                return undefined;
            }
            const inner = buildDescriptorForNode(node.args[0]);
            return inner ? appendDirectCriteriaResultTransform(inner, { kind: 'if-error', fallback }) : undefined;
        }
        if (callee === 'INDEX') {
            return (buildIndexReferenceCriteriaDescriptor(node) ??
                buildSimpleIndexMatchCriteriaDescriptor(node) ??
                buildIndexMatchCriteriaDescriptor(node));
        }
        if (callee === 'COUNTIF') {
            const criteriaPair = pair(node.args[0], node.args[1]);
            if (!criteriaPair) {
                return undefined;
            }
            return {
                aggregateKind: 'count',
                aggregateRange: undefined,
                criteriaPairs: [criteriaPair],
            };
        }
        if (callee === 'COUNTIFS') {
            if (node.args.length === 0 || node.args.length % 2 !== 0) {
                return undefined;
            }
            const criteriaPairs = [];
            for (let index = 0; index < node.args.length; index += 2) {
                const criteriaPair = pair(node.args[index], node.args[index + 1]);
                if (!criteriaPair) {
                    return undefined;
                }
                criteriaPairs.push(criteriaPair);
            }
            const expectedLength = criteriaPairs[0].range.length;
            if (criteriaPairs.some((current) => current.range.length !== expectedLength)) {
                return undefined;
            }
            return {
                aggregateKind: 'count',
                aggregateRange: undefined,
                criteriaPairs,
            };
        }
        if (callee === 'SUMIF' || callee === 'AVERAGEIF') {
            const criteriaPair = pair(node.args[0], node.args[1]);
            if (!criteriaPair) {
                return undefined;
            }
            const aggregateRange = resolveDirectCriteriaRange(node.args[2] ?? node.args[0], args.ownerSheetName);
            if (!aggregateRange || aggregateRange.length !== criteriaPair.range.length) {
                return undefined;
            }
            return {
                aggregateKind: callee === 'SUMIF' ? 'sum' : 'average',
                aggregateRange: withRangeRegion(aggregateRange),
                criteriaPairs: [criteriaPair],
            };
        }
        if (callee !== 'SUMIFS' && callee !== 'AVERAGEIFS' && callee !== 'MINIFS' && callee !== 'MAXIFS') {
            return undefined;
        }
        const aggregateRange = resolveDirectCriteriaRange(node.args[0], args.ownerSheetName);
        if (!aggregateRange || node.args.length < 3 || node.args.length % 2 === 0) {
            return undefined;
        }
        const criteriaPairs = [];
        for (let index = 1; index < node.args.length; index += 2) {
            const criteriaPair = pair(node.args[index], node.args[index + 1]);
            if (!criteriaPair || criteriaPair.range.length !== aggregateRange.length) {
                return undefined;
            }
            criteriaPairs.push(criteriaPair);
        }
        return {
            aggregateKind: callee === 'SUMIFS' ? 'sum' : callee === 'AVERAGEIFS' ? 'average' : callee === 'MINIFS' ? 'min' : 'max',
            aggregateRange: withRangeRegion(aggregateRange),
            criteriaPairs,
        };
    };
    if (args.compiled.astMatchesSource === false && mayContainDirectCriteriaPlanCall(args.compiled)) {
        return buildDescriptorForNode(parseFormula(args.source));
    }
    return buildDescriptorForNode(args.compiled.optimizedAst);
}
export function buildDirectAggregateDescriptor(args) {
    const buildDescriptor = (descriptor) => {
        const regionId = internPrimaryDirectAggregateRegion({
            regionGraph: args.regionGraph,
            sheetName: descriptor.sheetName,
            rowStart: descriptor.rowStart,
            rowEnd: descriptor.rowEnd,
            col: descriptor.colStart,
        });
        return {
            regionId,
            aggregateKind: descriptor.aggregateKind,
            sheetName: descriptor.sheetName,
            rowStart: descriptor.rowStart,
            rowEnd: descriptor.rowEnd,
            col: descriptor.colStart,
            colEnd: descriptor.colEnd,
            length: (descriptor.rowEnd - descriptor.rowStart + 1) * (descriptor.colEnd - descriptor.colStart + 1),
            ...(descriptor.resultOffset !== undefined ? { resultOffset: descriptor.resultOffset } : {}),
        };
    };
    const directAggregateCandidate = args.compiled.directAggregateCandidate;
    if (directAggregateCandidate) {
        const rangeInfo = args.compiled.parsedSymbolicRanges?.[directAggregateCandidate.symbolicRangeIndex];
        if (rangeInfo && rangeInfo.refKind === 'cells') {
            const sheetName = rangeInfo.sheetName ?? args.ownerSheetName;
            if (!args.workbook.getSheet(sheetName)) {
                return undefined;
            }
            return buildDescriptor({
                sheetName,
                rowStart: rangeInfo.startRow,
                rowEnd: rangeInfo.endRow,
                colStart: rangeInfo.startCol,
                colEnd: rangeInfo.endCol,
                aggregateKind: directAggregateCandidate.aggregateKind,
                ...(directAggregateCandidate.resultOffset !== undefined ? { resultOffset: directAggregateCandidate.resultOffset } : {}),
            });
        }
    }
    const node = directAggregateDescriptorNode(args.compiled);
    if (node.kind !== 'CallExpr' || node.args.length !== 1) {
        return undefined;
    }
    if (args.compiled.symbolicNames.length > 0 || args.compiled.symbolicTables.length > 0 || args.compiled.symbolicSpills.length > 0) {
        return undefined;
    }
    const callee = node.callee.trim().toUpperCase();
    if (callee !== 'SUM' && callee !== 'AVERAGE' && callee !== 'AVG' && callee !== 'COUNT' && callee !== 'MIN' && callee !== 'MAX') {
        return undefined;
    }
    const rangeNode = node.args[0];
    if (!rangeNode || rangeNode.kind !== 'RangeRef' || rangeNode.refKind !== 'cells' || rangeNode.sheetEndName !== undefined) {
        return undefined;
    }
    const parsedRange = parseRangeAddress(`${rangeNode.start}:${rangeNode.end}`, rangeNode.sheetName ?? args.ownerSheetName);
    if (parsedRange.kind !== 'cells') {
        return undefined;
    }
    const sheetName = rangeNode.sheetName ?? args.ownerSheetName;
    if (!args.workbook.getSheet(sheetName)) {
        return undefined;
    }
    return buildDescriptor({
        sheetName,
        rowStart: parsedRange.start.row,
        rowEnd: parsedRange.end.row,
        colStart: parsedRange.start.col,
        colEnd: parsedRange.end.col,
        aggregateKind: callee === 'SUM' ? 'sum' : callee === 'COUNT' ? 'count' : callee === 'MIN' ? 'min' : callee === 'MAX' ? 'max' : 'average',
    });
}
function directAggregateDescriptorNode(compiled) {
    if (compiled.astMatchesSource === false) {
        try {
            return parseFormula(compiled.source);
        }
        catch {
            return compiled.optimizedAst;
        }
    }
    return compiled.optimizedAst;
}
export function buildDirectLookupDescriptor(args) {
    const binding = resolveRuntimeDirectLookupBinding(args.compiled.jsPlan, args.ownerSheetName);
    if (!binding) {
        return undefined;
    }
    const lookupSheet = args.workbook.getSheet(binding.lookupSheetName);
    if (!lookupSheet || !args.workbook.getSheet(binding.operandSheetName)) {
        return undefined;
    }
    const operandCellIndex = args.ensureCellTracked(binding.operandSheetName, binding.operandAddress);
    if (binding.kind === 'exact') {
        if (!args.preferColumnIndex) {
            const uniform = args.exactLookup.prepareUniformNumericVectorLookup({
                sheetName: binding.lookupSheetName,
                rowStart: binding.rowStart,
                rowEnd: binding.rowEnd,
                col: binding.col,
            });
            if (uniform) {
                return {
                    kind: 'exact-uniform-numeric',
                    operandCellIndex,
                    sheetName: binding.lookupSheetName,
                    sheetId: lookupSheet.id,
                    rowStart: binding.rowStart,
                    rowEnd: binding.rowEnd,
                    col: binding.col,
                    length: uniform.length,
                    columnVersion: uniform.columnVersion,
                    structureVersion: uniform.structureVersion,
                    sheetColumnVersions: uniform.sheetColumnVersions,
                    start: uniform.uniformStart,
                    step: uniform.uniformStep,
                    searchMode: binding.searchMode,
                };
            }
        }
        const prepared = args.exactLookup.prepareVectorLookup({
            sheetName: binding.lookupSheetName,
            rowStart: binding.rowStart,
            rowEnd: binding.rowEnd,
            col: binding.col,
        });
        if (prepared.comparableKind === 'numeric' && prepared.uniformStart !== undefined && prepared.uniformStep !== undefined) {
            return {
                kind: 'exact-uniform-numeric',
                operandCellIndex,
                sheetName: binding.lookupSheetName,
                sheetId: lookupSheet.id,
                rowStart: binding.rowStart,
                rowEnd: binding.rowEnd,
                col: binding.col,
                length: prepared.length,
                columnVersion: prepared.columnVersion,
                structureVersion: prepared.structureVersion,
                sheetColumnVersions: prepared.sheetColumnVersions,
                start: prepared.uniformStart,
                step: prepared.uniformStep,
                searchMode: binding.searchMode,
            };
        }
        return {
            kind: 'exact',
            operandCellIndex,
            prepared,
            searchMode: binding.searchMode,
        };
    }
    const uniform = args.exactLookup.prepareUniformNumericVectorLookup({
        sheetName: binding.lookupSheetName,
        rowStart: binding.rowStart,
        rowEnd: binding.rowEnd,
        col: binding.col,
    });
    if (uniform) {
        return {
            kind: 'approximate-uniform-numeric',
            operandCellIndex,
            sheetName: binding.lookupSheetName,
            sheetId: lookupSheet.id,
            rowStart: binding.rowStart,
            rowEnd: binding.rowEnd,
            col: binding.col,
            length: uniform.length,
            columnVersion: uniform.columnVersion,
            structureVersion: uniform.structureVersion,
            sheetColumnVersions: uniform.sheetColumnVersions,
            start: uniform.uniformStart,
            step: uniform.uniformStep,
            matchMode: binding.matchMode,
        };
    }
    const prepared = args.sortedLookup.prepareVectorLookup({
        sheetName: binding.lookupSheetName,
        rowStart: binding.rowStart,
        rowEnd: binding.rowEnd,
        col: binding.col,
    });
    if (prepared.comparableKind === 'numeric' && prepared.uniformStart !== undefined && prepared.uniformStep !== undefined) {
        return {
            kind: 'approximate-uniform-numeric',
            operandCellIndex,
            sheetName: binding.lookupSheetName,
            sheetId: lookupSheet.id,
            rowStart: binding.rowStart,
            rowEnd: binding.rowEnd,
            col: binding.col,
            length: prepared.length,
            columnVersion: prepared.columnVersion,
            structureVersion: prepared.structureVersion,
            sheetColumnVersions: prepared.sheetColumnVersions,
            start: prepared.uniformStart,
            step: prepared.uniformStep,
            matchMode: binding.matchMode,
        };
    }
    if (prepared.comparableKind === 'numeric' &&
        prepared.repeatedUniformStart !== undefined &&
        prepared.repeatedUniformStep !== undefined &&
        prepared.repeatedUniformRunLength !== undefined) {
        return {
            kind: 'approximate-uniform-numeric',
            operandCellIndex,
            sheetName: binding.lookupSheetName,
            sheetId: lookupSheet.id,
            rowStart: binding.rowStart,
            rowEnd: binding.rowEnd,
            col: binding.col,
            length: prepared.length,
            columnVersion: prepared.columnVersion,
            structureVersion: prepared.structureVersion,
            sheetColumnVersions: prepared.sheetColumnVersions,
            start: prepared.repeatedUniformStart,
            step: prepared.repeatedUniformStep,
            repeatedRunLength: prepared.repeatedUniformRunLength,
            matchMode: binding.matchMode,
        };
    }
    return {
        kind: 'approximate',
        operandCellIndex,
        prepared,
        matchMode: binding.matchMode,
    };
}
export function hasLookupPlanInstruction(plan) {
    for (let index = 0; index < plan.length; index += 1) {
        const opcode = plan[index]?.opcode;
        if (opcode === 'lookup-exact-match' || opcode === 'lookup-approximate-match') {
            return true;
        }
    }
    return false;
}
//# sourceMappingURL=formula-binding-direct-descriptors.js.map