import { BuiltinId, FormulaMode, Opcode } from '@bilig/protocol';
import { parseA1RowIndex } from './a1-row-number.js';
const SIMPLE_DIRECT_CELL_REF_PATTERN = String.raw `(?:(?:([A-Za-z_][A-Za-z0-9_.]*)!)?([A-Za-z]+)([1-9][0-9]*))`;
const SIMPLE_DIRECT_BINARY_RE = new RegExp(`^${SIMPLE_DIRECT_CELL_REF_PATTERN}([+\\-*/])(?:${SIMPLE_DIRECT_CELL_REF_PATTERN}|(\\d+(?:\\.\\d+)?))(?:\\+(\\d+(?:\\.\\d+)?))?$`);
const SIMPLE_DIRECT_ABS_RE = new RegExp(`^ABS\\s*\\(\\s*${SIMPLE_DIRECT_CELL_REF_PATTERN}\\s*\\)$`, 'i');
const EMPTY_STRINGS = [];
const EMPTY_CONSTANTS = new Float64Array();
function encodeInstruction(opcode, operand = 0) {
    return (opcode << 24) | (operand & 0x00ff_ffff);
}
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 indexToColumn(index) {
    let value = index + 1;
    let label = '';
    while (value > 0) {
        value -= 1;
        label = String.fromCharCode(65 + (value % 26)) + label;
        value = Math.floor(value / 26);
    }
    return label;
}
function parseOperator(operator) {
    switch (operator) {
        case '+':
        case '-':
        case '*':
        case '/':
            return operator;
        default:
            return undefined;
    }
}
function operatorOpcode(operator) {
    switch (operator) {
        case '+':
            return Opcode.Add;
        case '-':
            return Opcode.Sub;
        case '*':
            return Opcode.Mul;
        case '/':
            return Opcode.Div;
    }
}
function parsedCellRef(column, rowText, sheetName) {
    const normalizedColumn = column.toUpperCase();
    const row = parseA1RowIndex(rowText);
    if (row === undefined) {
        return undefined;
    }
    const localAddress = `${normalizedColumn}${rowText}`;
    return {
        address: localAddress,
        ...(sheetName === undefined ? {} : { sheetName, explicitSheet: true }),
        row,
        col: columnToIndex(normalizedColumn),
        rowAbsolute: false,
        colAbsolute: false,
    };
}
function cellNode(ref) {
    return {
        kind: 'CellRef',
        ref: ref.address,
        ...(ref.sheetName === undefined ? {} : { sheetName: ref.sheetName }),
    };
}
function translateParsedLocalCellRef(ref, rowDelta, colDelta) {
    if (ref.sheetName !== undefined || ref.explicitSheet === true) {
        return undefined;
    }
    const baseRow = ref.row;
    const baseCol = ref.col;
    if (baseRow === undefined || baseCol === undefined) {
        return undefined;
    }
    const row = ref.rowAbsolute ? baseRow : baseRow + rowDelta;
    const col = ref.colAbsolute ? baseCol : baseCol + colDelta;
    if (row < 0 || col < 0) {
        return undefined;
    }
    const translated = {
        address: `${indexToColumn(col)}${row + 1}`,
        row,
        col,
    };
    if (ref.rowAbsolute !== undefined) {
        translated.rowAbsolute = ref.rowAbsolute;
    }
    if (ref.colAbsolute !== undefined) {
        translated.colAbsolute = ref.colAbsolute;
    }
    return translated;
}
function parsedCellDependency(ref) {
    const dependency = {
        kind: 'cell',
        address: ref.address,
    };
    if (ref.sheetName !== undefined) {
        dependency.sheetName = ref.sheetName;
    }
    if (ref.explicitSheet !== undefined) {
        dependency.explicitSheet = ref.explicitSheet;
    }
    if (ref.row !== undefined) {
        dependency.row = ref.row;
    }
    if (ref.col !== undefined) {
        dependency.col = ref.col;
    }
    if (ref.rowAbsolute !== undefined) {
        dependency.rowAbsolute = ref.rowAbsolute;
    }
    if (ref.colAbsolute !== undefined) {
        dependency.colAbsolute = ref.colAbsolute;
    }
    return dependency;
}
function parsedTranslatedSourceRef(column, rowText, baseRef, rowDelta, colDelta) {
    if (baseRef.sheetName !== undefined || baseRef.explicitSheet === true) {
        return undefined;
    }
    const baseRow = baseRef.row;
    const baseCol = baseRef.col;
    if (baseRow === undefined || baseCol === undefined) {
        return undefined;
    }
    const row = baseRef.rowAbsolute ? baseRow : baseRow + rowDelta;
    const col = baseRef.colAbsolute ? baseCol : baseCol + colDelta;
    const parsed = parsedCellRef(column, rowText);
    if (!parsed) {
        return undefined;
    }
    if (parsed.row !== row || parsed.col !== col) {
        return undefined;
    }
    if (baseRef.rowAbsolute !== undefined) {
        parsed.rowAbsolute = baseRef.rowAbsolute;
    }
    if (baseRef.colAbsolute !== undefined) {
        parsed.colAbsolute = baseRef.colAbsolute;
    }
    return parsed;
}
function translatedRefsFromSource(compiled, scalarAst, rowDelta, colDelta, source) {
    const parsedRefs = compiled.parsedSymbolicRefs;
    if (parsedRefs === undefined) {
        return undefined;
    }
    const trimmedSource = source.trim();
    const trimmed = trimmedSource.startsWith('=') ? trimmedSource.slice(1).trim() : trimmedSource;
    if (isSimpleDirectAbsAst(scalarAst)) {
        const absMatch = SIMPLE_DIRECT_ABS_RE.exec(trimmed);
        if (!absMatch || parsedRefs.length !== 1) {
            return undefined;
        }
        const operand = parsedTranslatedSourceRef(absMatch[2], absMatch[3], parsedRefs[0], rowDelta, colDelta);
        return operand ? [operand] : undefined;
    }
    const binaryMatch = SIMPLE_DIRECT_BINARY_RE.exec(trimmed);
    if (!binaryMatch) {
        return undefined;
    }
    const left = parsedTranslatedSourceRef(binaryMatch[2], binaryMatch[3], parsedRefs[0], rowDelta, colDelta);
    if (!left) {
        return undefined;
    }
    if (scalarAst.right.kind !== 'CellRef') {
        return parsedRefs.length === 1 && binaryMatch[6] === undefined ? [left] : undefined;
    }
    if (parsedRefs.length !== 2 || binaryMatch[6] === undefined) {
        return undefined;
    }
    const right = parsedTranslatedSourceRef(binaryMatch[6], binaryMatch[7], parsedRefs[1], rowDelta, colDelta);
    return right ? [left, right] : undefined;
}
function simpleDirectSourceHasResultOffset(source) {
    const trimmedSource = source.trim();
    const trimmed = trimmedSource.startsWith('=') ? trimmedSource.slice(1).trim() : trimmedSource;
    return SIMPLE_DIRECT_BINARY_RE.exec(trimmed)?.[9] !== undefined;
}
function translatedCompiledFormula(compiled, source, deps, parsedDeps, parsedSymbolicRefs) {
    const translated = {
        id: compiled.id,
        source,
        mode: compiled.mode,
        depsPtr: compiled.depsPtr,
        depsLen: compiled.depsLen,
        programOffset: compiled.programOffset,
        programLength: compiled.programLength,
        constNumberOffset: compiled.constNumberOffset,
        constNumberLength: compiled.constNumberLength,
        rangeListOffset: compiled.rangeListOffset,
        rangeListLength: compiled.rangeListLength,
        maxStackDepth: compiled.maxStackDepth,
        ast: compiled.ast,
        optimizedAst: compiled.optimizedAst,
        astMatchesSource: false,
        deps,
        parsedDeps,
        symbolicNames: compiled.symbolicNames,
        symbolicTables: compiled.symbolicTables,
        symbolicSpills: compiled.symbolicSpills,
        volatile: compiled.volatile,
        randCallCount: compiled.randCallCount,
        producesSpill: compiled.producesSpill,
        jsPlan: compiled.jsPlan,
        program: compiled.program,
        constants: compiled.constants,
        symbolicRefs: deps,
        parsedSymbolicRefs,
        symbolicRanges: compiled.symbolicRanges,
        symbolicStrings: compiled.symbolicStrings,
    };
    if (compiled.parsedSymbolicRanges !== undefined) {
        translated.parsedSymbolicRanges = compiled.parsedSymbolicRanges;
    }
    if (compiled.directAggregateCandidate !== undefined) {
        translated.directAggregateCandidate = compiled.directAggregateCandidate;
    }
    return translated;
}
function isSimpleDirectScalarAst(node) {
    return (node.kind === 'BinaryExpr' &&
        (node.operator === '+' || node.operator === '-' || node.operator === '*' || node.operator === '/') &&
        node.left.kind === 'CellRef' &&
        (node.right.kind === 'CellRef' || node.right.kind === 'NumberLiteral'));
}
function isSimpleDirectScalarOffsetAst(node) {
    return node.kind === 'BinaryExpr' && node.operator === '+' && isSimpleDirectScalarAst(node.left) && node.right.kind === 'NumberLiteral';
}
function isSimpleDirectAbsAst(node) {
    return node.kind === 'CallExpr' && node.callee === 'ABS' && node.args.length === 1 && node.args[0]?.kind === 'CellRef';
}
export function translateSimpleDirectScalarFormulaWithParsedRefs(compiled, source, parsedRefs) {
    if (compiled.symbolicRanges.length !== 0 ||
        compiled.symbolicNames.length !== 0 ||
        compiled.symbolicTables.length !== 0 ||
        compiled.symbolicSpills.length !== 0 ||
        (!isSimpleDirectScalarAst(compiled.optimizedAst) &&
            !isSimpleDirectScalarOffsetAst(compiled.optimizedAst) &&
            !isSimpleDirectAbsAst(compiled.optimizedAst))) {
        return undefined;
    }
    const scalarAst = isSimpleDirectScalarOffsetAst(compiled.optimizedAst) ? compiled.optimizedAst.left : compiled.optimizedAst;
    const expectedRefCount = isSimpleDirectScalarAst(scalarAst) && scalarAst.right.kind === 'CellRef' ? 2 : 1;
    if (parsedRefs.parsedSymbolicRefs.length !== expectedRefCount ||
        parsedRefs.symbolicRefs.length !== expectedRefCount ||
        parsedRefs.parsedDeps.length !== expectedRefCount) {
        return undefined;
    }
    return translatedCompiledFormula(compiled, source, parsedRefs.symbolicRefs, parsedRefs.parsedDeps, parsedRefs.parsedSymbolicRefs);
}
export function translateSimpleDirectScalarFormula(compiled, rowDelta, colDelta, source) {
    if (compiled.symbolicRanges.length !== 0 ||
        compiled.symbolicNames.length !== 0 ||
        compiled.symbolicTables.length !== 0 ||
        compiled.symbolicSpills.length !== 0 ||
        (!isSimpleDirectScalarAst(compiled.optimizedAst) &&
            !isSimpleDirectScalarOffsetAst(compiled.optimizedAst) &&
            !isSimpleDirectAbsAst(compiled.optimizedAst))) {
        return undefined;
    }
    const scalarAst = isSimpleDirectScalarOffsetAst(compiled.optimizedAst) ? compiled.optimizedAst.left : compiled.optimizedAst;
    const expectedRefCount = isSimpleDirectScalarAst(scalarAst) && scalarAst.right.kind === 'CellRef' ? 2 : 1;
    if (compiled.parsedSymbolicRefs === undefined || compiled.parsedSymbolicRefs.length !== expectedRefCount) {
        return undefined;
    }
    const sourceRefs = translatedRefsFromSource(compiled, scalarAst, rowDelta, colDelta, source);
    if (sourceRefs) {
        const sourceSymbolicRefs = sourceRefs.map((ref) => ref.address);
        const sourceParsedDeps = sourceRefs.map(parsedCellDependency);
        if (isSimpleDirectScalarOffsetAst(compiled.optimizedAst) && simpleDirectSourceHasResultOffset(source)) {
            const sourceCompiled = tryCompileSimpleDirectScalarFormula(source);
            if (sourceCompiled) {
                return translatedCompiledFormula({ ...sourceCompiled, id: compiled.id }, source, sourceSymbolicRefs, sourceParsedDeps, sourceRefs);
            }
        }
        return translatedCompiledFormula(compiled, source, sourceSymbolicRefs, sourceParsedDeps, sourceRefs);
    }
    const translatedRefs = [];
    const symbolicRefs = [];
    const parsedDeps = [];
    for (let index = 0; index < compiled.parsedSymbolicRefs.length; index += 1) {
        const translated = translateParsedLocalCellRef(compiled.parsedSymbolicRefs[index], rowDelta, colDelta);
        if (!translated) {
            return undefined;
        }
        translatedRefs[index] = translated;
        symbolicRefs[index] = translated.address;
        parsedDeps[index] = parsedCellDependency(translated);
    }
    return translatedCompiledFormula(compiled, source, symbolicRefs, parsedDeps, translatedRefs);
}
export function translateTrustedSimpleDirectScalarFormula(compiled, rowDelta, colDelta, source) {
    if (compiled.symbolicRanges.length !== 0 ||
        compiled.symbolicNames.length !== 0 ||
        compiled.symbolicTables.length !== 0 ||
        compiled.symbolicSpills.length !== 0 ||
        (!isSimpleDirectScalarAst(compiled.optimizedAst) &&
            !isSimpleDirectScalarOffsetAst(compiled.optimizedAst) &&
            !isSimpleDirectAbsAst(compiled.optimizedAst))) {
        return undefined;
    }
    const scalarAst = isSimpleDirectScalarOffsetAst(compiled.optimizedAst) ? compiled.optimizedAst.left : compiled.optimizedAst;
    const expectedRefCount = isSimpleDirectScalarAst(scalarAst) && scalarAst.right.kind === 'CellRef' ? 2 : 1;
    if (compiled.parsedSymbolicRefs === undefined || compiled.parsedSymbolicRefs.length !== expectedRefCount) {
        return undefined;
    }
    const translatedRefs = [];
    const symbolicRefs = [];
    const parsedDeps = [];
    for (let index = 0; index < compiled.parsedSymbolicRefs.length; index += 1) {
        const translated = translateParsedLocalCellRef(compiled.parsedSymbolicRefs[index], rowDelta, colDelta);
        if (!translated) {
            return undefined;
        }
        translatedRefs[index] = translated;
        symbolicRefs[index] = translated.address;
        parsedDeps[index] = parsedCellDependency(translated);
    }
    return translatedCompiledFormula(compiled, source, symbolicRefs, parsedDeps, translatedRefs);
}
export function translateTrustedSimpleDirectScalarFormulaWithResultOffset(compiled, rowDelta, colDelta, source, resultOffset) {
    if (!Number.isFinite(resultOffset) || !isSimpleDirectScalarOffsetAst(compiled.optimizedAst)) {
        return undefined;
    }
    const translated = translateTrustedSimpleDirectScalarFormula(compiled, rowDelta, colDelta, source);
    if (!translated) {
        return undefined;
    }
    const scalarAst = compiled.optimizedAst.left;
    const constants = scalarAst.right.kind === 'CellRef'
        ? Float64Array.of(resultOffset)
        : Float64Array.of(compiled.constants[0] ?? scalarAst.right.value, resultOffset);
    const optimizedAst = {
        ...compiled.optimizedAst,
        right: {
            ...compiled.optimizedAst.right,
            value: resultOffset,
        },
    };
    const ast = isSimpleDirectScalarOffsetAst(translated.ast)
        ? {
            ...translated.ast,
            right: {
                ...translated.ast.right,
                value: resultOffset,
            },
        }
        : translated.ast;
    const jsPlan = translated.jsPlan.map((instruction, index) => instruction.opcode === 'push-number' && index === translated.jsPlan.length - 3 ? { ...instruction, value: resultOffset } : instruction);
    return {
        ...translated,
        ast,
        optimizedAst,
        jsPlan,
        constants,
    };
}
export function tryCompileSimpleDirectScalarFormula(source) {
    const trimmedSource = source.trim();
    const trimmed = trimmedSource.startsWith('=') ? trimmedSource.slice(1).trim() : trimmedSource;
    const absMatch = SIMPLE_DIRECT_ABS_RE.exec(trimmed);
    if (absMatch) {
        const ref = parsedCellRef(absMatch[2], absMatch[3], absMatch[1]);
        if (!ref) {
            return undefined;
        }
        const ast = {
            kind: 'CallExpr',
            callee: 'ABS',
            args: [cellNode(ref)],
        };
        const program = Uint32Array.of(encodeInstruction(Opcode.PushCell, 0), encodeInstruction(Opcode.CallBuiltin, (BuiltinId.Abs << 8) | 1), encodeInstruction(Opcode.Ret));
        const jsPlan = [
            { opcode: 'push-cell', address: ref.address },
            { opcode: 'call', callee: 'ABS', argc: 1 },
            { opcode: 'return' },
        ];
        const baseRecord = {
            id: 0,
            source: trimmed,
            mode: FormulaMode.WasmFastPath,
            depsPtr: 0,
            depsLen: 0,
            programOffset: 0,
            programLength: program.length,
            constNumberOffset: 0,
            constNumberLength: 0,
            rangeListOffset: 0,
            rangeListLength: 0,
            maxStackDepth: 1,
        };
        return {
            ...baseRecord,
            ast,
            optimizedAst: ast,
            astMatchesSource: true,
            deps: [ref.address],
            parsedDeps: [{ kind: 'cell', ...ref }],
            symbolicNames: EMPTY_STRINGS,
            symbolicTables: EMPTY_STRINGS,
            symbolicSpills: EMPTY_STRINGS,
            volatile: false,
            randCallCount: 0,
            producesSpill: false,
            jsPlan,
            program,
            constants: EMPTY_CONSTANTS,
            symbolicRefs: [ref.address],
            parsedSymbolicRefs: [ref],
            symbolicRanges: EMPTY_STRINGS,
            parsedSymbolicRanges: [],
            symbolicStrings: EMPTY_STRINGS,
        };
    }
    const match = SIMPLE_DIRECT_BINARY_RE.exec(trimmed);
    if (!match) {
        return undefined;
    }
    const operator = parseOperator(match[4]);
    if (operator === undefined) {
        return undefined;
    }
    const opcode = operatorOpcode(operator);
    const leftRef = parsedCellRef(match[2], match[3], match[1]);
    if (!leftRef) {
        return undefined;
    }
    const rightRef = match[6] !== undefined ? parsedCellRef(match[6], match[7], match[5]) : undefined;
    if (match[6] !== undefined && !rightRef) {
        return undefined;
    }
    const rightNumber = rightRef === undefined ? Number.parseFloat(match[8]) : undefined;
    if (rightRef === undefined && !Number.isFinite(rightNumber)) {
        return undefined;
    }
    const resultOffset = match[9] === undefined ? undefined : Number.parseFloat(match[9]);
    if (resultOffset !== undefined && !Number.isFinite(resultOffset)) {
        return undefined;
    }
    const rightNode = rightRef ? cellNode(rightRef) : { kind: 'NumberLiteral', value: rightNumber };
    const baseAst = {
        kind: 'BinaryExpr',
        operator,
        left: cellNode(leftRef),
        right: rightNode,
    };
    const ast = resultOffset === undefined
        ? baseAst
        : {
            kind: 'BinaryExpr',
            operator: '+',
            left: baseAst,
            right: { kind: 'NumberLiteral', value: resultOffset },
        };
    const symbolicRefs = rightRef ? [leftRef.address, rightRef.address] : [leftRef.address];
    const parsedSymbolicRefs = rightRef ? [leftRef, rightRef] : [leftRef];
    const constants = rightRef
        ? resultOffset === undefined
            ? EMPTY_CONSTANTS
            : Float64Array.of(resultOffset)
        : resultOffset === undefined
            ? Float64Array.of(rightNumber)
            : Float64Array.of(rightNumber, resultOffset);
    const programInstructions = [
        encodeInstruction(Opcode.PushCell, 0),
        rightRef ? encodeInstruction(Opcode.PushCell, 1) : encodeInstruction(Opcode.PushNumber, 0),
        encodeInstruction(opcode),
    ];
    if (resultOffset !== undefined) {
        programInstructions.push(encodeInstruction(Opcode.PushNumber, rightRef ? 0 : 1), encodeInstruction(Opcode.Add));
    }
    programInstructions.push(encodeInstruction(Opcode.Ret));
    const program = Uint32Array.from(programInstructions);
    const jsPlan = [
        { opcode: 'push-cell', address: leftRef.address },
        rightRef ? { opcode: 'push-cell', address: rightRef.address } : { opcode: 'push-number', value: rightNumber },
        { opcode: 'binary', operator },
    ];
    if (resultOffset !== undefined) {
        jsPlan.push({ opcode: 'push-number', value: resultOffset }, { opcode: 'binary', operator: '+' });
    }
    jsPlan.push({ opcode: 'return' });
    const baseRecord = {
        id: 0,
        source: trimmed,
        mode: FormulaMode.WasmFastPath,
        depsPtr: 0,
        depsLen: 0,
        programOffset: 0,
        programLength: program.length,
        constNumberOffset: 0,
        constNumberLength: constants.length,
        rangeListOffset: 0,
        rangeListLength: 0,
        maxStackDepth: 2,
    };
    return {
        ...baseRecord,
        ast,
        optimizedAst: ast,
        astMatchesSource: true,
        deps: symbolicRefs,
        parsedDeps: parsedSymbolicRefs.map((ref) => ({ kind: 'cell', ...ref })),
        symbolicNames: EMPTY_STRINGS,
        symbolicTables: EMPTY_STRINGS,
        symbolicSpills: EMPTY_STRINGS,
        volatile: false,
        randCallCount: 0,
        producesSpill: false,
        jsPlan,
        program,
        constants,
        symbolicRefs,
        parsedSymbolicRefs,
        symbolicRanges: EMPTY_STRINGS,
        parsedSymbolicRanges: [],
        symbolicStrings: EMPTY_STRINGS,
    };
}
//# sourceMappingURL=simple-direct-scalar-compile.js.map