import { FormulaMode } from '@bilig/protocol';
import { formatAddress, parseRangeAddress, } from '@bilig/formula';
import { parseA1RowNumber } from './a1-row-number.js';
const SIMPLE_DIRECT_AGGREGATE_RE = /^(?<callee>SUM|AVERAGE|AVG|COUNT|MIN|MAX)\s*\(\s*(?<range>[^(),]+:[^(),]+)\s*\)(?:\s*\+\s*(?<offset>[+-]?(?:\d+|\d*\.\d+)))?$/i;
const SIMPLE_COLUMN_RANGE_RE = /^(?:(?<sheet>[A-Za-z_][A-Za-z0-9_ .]*)!)?(?<startCol>[A-Za-z]+)(?<startRow>[1-9][0-9]*):(?:(?<endSheet>[A-Za-z_][A-Za-z0-9_ .]*)!)?(?<endCol>[A-Za-z]+)(?<endRow>[1-9][0-9]*)$/;
const EMPTY_STRINGS = [];
const EMPTY_PROGRAM = new Uint32Array();
const EMPTY_CONSTANTS = new Float64Array();
const DIRECT_AGGREGATE_KIND_BY_CALLEE = {
    SUM: 'sum',
    AVERAGE: 'average',
    AVG: 'average',
    COUNT: 'count',
    MIN: 'min',
    MAX: 'max',
};
function columnToIndex(column) {
    let value = 0;
    for (let index = 0; index < column.length; index += 1) {
        const code = column.charCodeAt(index);
        value = value * 26 + (code - 64);
    }
    return value - 1;
}
function tryParseSimpleColumnRange(rawRange) {
    const match = SIMPLE_COLUMN_RANGE_RE.exec(rawRange);
    const groups = match?.groups;
    if (!match || !groups) {
        return undefined;
    }
    const sheetName = groups['sheet'];
    const endSheetName = groups['endSheet'];
    if (sheetName !== undefined && endSheetName !== undefined && sheetName !== endSheetName) {
        return undefined;
    }
    const startColumn = groups['startCol'].toUpperCase();
    const endColumn = groups['endCol'].toUpperCase();
    const startCol = columnToIndex(startColumn);
    const endCol = columnToIndex(endColumn);
    if (endCol < startCol) {
        return undefined;
    }
    const startRowNumber = parseA1RowNumber(groups['startRow']);
    const endRowNumber = parseA1RowNumber(groups['endRow']);
    if (startRowNumber === undefined || endRowNumber === undefined) {
        return null;
    }
    if (endRowNumber < startRowNumber) {
        return undefined;
    }
    const startAddress = `${startColumn}${startRowNumber}`;
    const endAddress = `${endColumn}${endRowNumber}`;
    const address = sheetName === undefined ? `${startAddress}:${endAddress}` : `${sheetName}!${startAddress}:${endAddress}`;
    return {
        address,
        ...(sheetName !== undefined ? { sheetName, explicitSheet: true } : {}),
        startAddress,
        endAddress,
        startRow: startRowNumber - 1,
        endRow: endRowNumber - 1,
        startCol,
        endCol,
    };
}
export function tryCompileSimpleDirectAggregateFormula(source) {
    const trimmedSource = source.trim();
    const normalizedSource = trimmedSource.startsWith('=') ? trimmedSource.slice(1).trim() : trimmedSource;
    const match = SIMPLE_DIRECT_AGGREGATE_RE.exec(normalizedSource);
    if (!match?.groups) {
        return undefined;
    }
    const callee = match.groups['callee'].toUpperCase();
    const aggregateKind = DIRECT_AGGREGATE_KIND_BY_CALLEE[callee];
    const resultOffset = match.groups['offset'] === undefined ? 0 : Number(match.groups['offset']);
    if (!Number.isFinite(resultOffset)) {
        return undefined;
    }
    const rawRange = match.groups['range'].trim();
    const fastRange = tryParseSimpleColumnRange(rawRange);
    if (fastRange === null) {
        return undefined;
    }
    let rangeInfo;
    if (fastRange) {
        rangeInfo = fastRange;
    }
    else {
        let parsedRange;
        try {
            parsedRange = parseRangeAddress(rawRange);
        }
        catch {
            return undefined;
        }
        if (parsedRange.kind !== 'cells') {
            return undefined;
        }
        const start = parsedRange.start;
        const end = parsedRange.end;
        if (start.col !== end.col) {
            return undefined;
        }
        rangeInfo = {
            address: rawRange,
            ...(parsedRange.sheetName !== undefined ? { sheetName: parsedRange.sheetName, explicitSheet: true } : {}),
            startAddress: start.text,
            endAddress: end.text,
            startRow: start.row,
            endRow: end.row,
            startCol: start.col,
            endCol: end.col,
        };
    }
    const parsedRangeInfo = {
        ...rangeInfo,
        kind: 'range',
        refKind: 'cells',
    };
    const rangeNode = {
        kind: 'RangeRef',
        refKind: 'cells',
        ...(rangeInfo.sheetName !== undefined ? { sheetName: rangeInfo.sheetName } : {}),
        start: rangeInfo.startAddress,
        end: rangeInfo.endAddress,
    };
    const aggregateNode = {
        kind: 'CallExpr',
        callee,
        args: [rangeNode],
    };
    const ast = resultOffset === 0
        ? aggregateNode
        : {
            kind: 'BinaryExpr',
            operator: '+',
            left: aggregateNode,
            right: { kind: 'NumberLiteral', value: resultOffset },
        };
    const directAggregateCandidate = {
        callee,
        aggregateKind,
        symbolicRangeIndex: 0,
        ...(resultOffset !== 0 ? { resultOffset } : {}),
    };
    const baseRecord = {
        id: 0,
        source: normalizedSource,
        mode: aggregateKind === 'average' ? FormulaMode.JsOnly : FormulaMode.WasmFastPath,
        depsPtr: 0,
        depsLen: 1,
        programOffset: 0,
        programLength: 0,
        constNumberOffset: 0,
        constNumberLength: 0,
        rangeListOffset: 0,
        rangeListLength: 1,
        maxStackDepth: 0,
    };
    return {
        ...baseRecord,
        ast,
        optimizedAst: ast,
        astMatchesSource: true,
        directAggregateCandidate,
        deps: [rangeInfo.address],
        parsedDeps: [parsedRangeInfo],
        symbolicNames: [],
        symbolicTables: [],
        symbolicSpills: [],
        volatile: false,
        randCallCount: 0,
        producesSpill: false,
        jsPlan: [],
        program: EMPTY_PROGRAM,
        constants: EMPTY_CONSTANTS,
        symbolicRefs: EMPTY_STRINGS,
        parsedSymbolicRefs: [],
        symbolicRanges: [rangeInfo.address],
        parsedSymbolicRanges: [parsedRangeInfo],
        symbolicStrings: EMPTY_STRINGS,
    };
}
export function translateSimpleDirectAggregateFormula(compiled, rowDelta, colDelta, source) {
    const candidate = compiled.directAggregateCandidate;
    const range = compiled.parsedSymbolicRanges?.[candidate?.symbolicRangeIndex ?? -1];
    if (candidate === undefined ||
        range === undefined ||
        compiled.parsedSymbolicRanges?.length !== 1 ||
        compiled.symbolicRanges.length !== 1 ||
        (compiled.parsedSymbolicRefs?.length ?? 0) !== 0 ||
        compiled.symbolicRefs.length !== 0 ||
        range.kind !== 'range' ||
        range.refKind !== 'cells' ||
        range.sheetEndName !== undefined) {
        return undefined;
    }
    const startRow = range.startRow + rowDelta;
    const endRow = range.endRow + rowDelta;
    const startCol = range.startCol + colDelta;
    const endCol = range.endCol + colDelta;
    if (startRow < 0 || endRow < startRow || startCol < 0 || endCol < startCol) {
        return undefined;
    }
    const startAddress = formatAddress(startRow, startCol);
    const endAddress = formatAddress(endRow, endCol);
    const address = formatSimpleRangeAddress(range.sheetName, startAddress, endAddress);
    const translatedRange = {
        ...range,
        address,
        startAddress,
        endAddress,
        startRow,
        endRow,
        startCol,
        endCol,
    };
    const rangeNode = {
        kind: 'RangeRef',
        refKind: 'cells',
        ...(range.sheetName !== undefined ? { sheetName: range.sheetName } : {}),
        start: startAddress,
        end: endAddress,
    };
    const aggregateNode = {
        kind: 'CallExpr',
        callee: candidate.callee,
        args: [rangeNode],
    };
    const ast = candidate.resultOffset === undefined
        ? aggregateNode
        : {
            kind: 'BinaryExpr',
            operator: '+',
            left: aggregateNode,
            right: { kind: 'NumberLiteral', value: candidate.resultOffset },
        };
    return {
        ...compiled,
        source,
        ast,
        optimizedAst: ast,
        astMatchesSource: false,
        deps: [address],
        parsedDeps: [translatedRange],
        symbolicRanges: [address],
        parsedSymbolicRanges: [translatedRange],
    };
}
function formatSimpleRangeAddress(sheetName, startAddress, endAddress) {
    if (sheetName === undefined) {
        return `${startAddress}:${endAddress}`;
    }
    return `${formatSimpleSheetName(sheetName)}!${startAddress}:${endAddress}`;
}
function formatSimpleSheetName(sheetName) {
    return /^[A-Za-z_][A-Za-z0-9_.]*$/.test(sheetName) ? sheetName : `'${sheetName.replace(/'/g, "''")}'`;
}
export function translateAnchoredPrefixDirectAggregateFormula(compiled, ownerRow, colDelta, source) {
    const candidate = compiled.directAggregateCandidate;
    const range = compiled.parsedSymbolicRanges?.[candidate?.symbolicRangeIndex ?? -1];
    if (candidate === undefined ||
        range === undefined ||
        compiled.parsedSymbolicRanges?.length !== 1 ||
        compiled.symbolicRanges.length !== 1 ||
        (compiled.parsedSymbolicRefs?.length ?? 0) !== 0 ||
        compiled.symbolicRefs.length !== 0 ||
        range.kind !== 'range' ||
        range.refKind !== 'cells' ||
        range.sheetName !== undefined ||
        range.startRow !== 0 ||
        range.startCol !== range.endCol) {
        return undefined;
    }
    const startCol = range.startCol + colDelta;
    const endCol = range.endCol + colDelta;
    if (ownerRow < 0 || startCol < 0 || endCol < startCol) {
        return undefined;
    }
    const startAddress = formatAddress(0, startCol);
    const endAddress = formatAddress(ownerRow, endCol);
    const address = `${startAddress}:${endAddress}`;
    const translatedRange = {
        ...range,
        address,
        startAddress,
        endAddress,
        startRow: 0,
        endRow: ownerRow,
        startCol,
        endCol,
    };
    const rangeNode = {
        kind: 'RangeRef',
        refKind: 'cells',
        start: startAddress,
        end: endAddress,
    };
    const aggregateNode = {
        kind: 'CallExpr',
        callee: candidate.callee,
        args: [rangeNode],
    };
    const ast = candidate.resultOffset === undefined
        ? aggregateNode
        : {
            kind: 'BinaryExpr',
            operator: '+',
            left: aggregateNode,
            right: { kind: 'NumberLiteral', value: candidate.resultOffset },
        };
    return {
        ...compiled,
        source,
        ast,
        optimizedAst: ast,
        astMatchesSource: false,
        deps: [address],
        parsedDeps: [translatedRange],
        symbolicRanges: [address],
        parsedSymbolicRanges: [translatedRange],
    };
}
//# sourceMappingURL=simple-direct-aggregate-compile.js.map