import { ValueTag } from '@bilig/protocol';
import { parseArithmeticScalarText } from '@bilig/formula';
export const ROW_PAIR_LEFT_PLUS_RIGHT = 1;
export const ROW_PAIR_LEFT_MINUS_RIGHT = 2;
export const ROW_PAIR_RIGHT_MINUS_LEFT = 3;
export const ROW_PAIR_LEFT_TIMES_RIGHT = 4;
export const ROW_PAIR_LEFT_DIV_RIGHT = 5;
export const ROW_PAIR_RIGHT_DIV_LEFT = 6;
export function directScalarLiteralNumericValue(value) {
    if (value === null) {
        return 0;
    }
    switch (typeof value) {
        case 'number':
            return Object.is(value, -0) ? 0 : value;
        case 'boolean':
            return value ? 1 : 0;
        case 'string':
        case 'bigint':
        case 'function':
        case 'object':
        case 'symbol':
        case 'undefined':
            return undefined;
    }
    return undefined;
}
export function directScalarValueNumber(value, dateSystem = '1900') {
    switch (value.tag) {
        case ValueTag.Number:
            return Object.is(value.value, -0) ? 0 : value.value;
        case ValueTag.Boolean:
            return value.value ? 1 : 0;
        case ValueTag.Empty:
            return 0;
        case ValueTag.Error:
            return undefined;
        case ValueTag.String:
            return parseArithmeticScalarText(value.value, dateSystem);
    }
}
export function directScalarCellNumber(cellStore, cellIndex) {
    if (cellIndex === undefined) {
        return 0;
    }
    switch (cellStore.tags[cellIndex] ?? ValueTag.Empty) {
        case ValueTag.Number: {
            const value = cellStore.numbers[cellIndex] ?? 0;
            return Object.is(value, -0) ? 0 : value;
        }
        case ValueTag.Boolean:
            return (cellStore.numbers[cellIndex] ?? 0) !== 0 ? 1 : 0;
        case ValueTag.Empty:
            return 0;
        case ValueTag.Error:
        case ValueTag.String:
            return undefined;
    }
}
function readDirectScalarOperandNumber(operand, changedCellIndex, replacementNumber, touched, readCellNumber) {
    switch (operand.kind) {
        case 'literal-number':
            return operand.value;
        case 'error':
            return undefined;
        case 'cell':
            if (operand.cellIndex === changedCellIndex) {
                touched.value = true;
                return replacementNumber;
            }
            return readCellNumber(operand.cellIndex);
    }
}
function readDirectScalarOperandReplacementNumber(operand, primaryCellIndex, primaryNumber, secondaryCellIndex, secondaryNumber, readCellNumber) {
    switch (operand.kind) {
        case 'literal-number':
            return operand.value;
        case 'error':
            return undefined;
        case 'cell':
            if (operand.cellIndex === primaryCellIndex) {
                return primaryNumber;
            }
            if (secondaryCellIndex !== undefined && operand.cellIndex === secondaryCellIndex) {
                return secondaryNumber;
            }
            return readCellNumber(operand.cellIndex);
    }
}
export function evaluateDirectScalarNumber(directScalar, changedCellIndex, replacementNumber, touched, readCellNumber) {
    if (directScalar.kind === 'abs') {
        const operand = readDirectScalarOperandNumber(directScalar.operand, changedCellIndex, replacementNumber, touched, readCellNumber);
        return operand === undefined ? undefined : Math.abs(operand);
    }
    const left = readDirectScalarOperandNumber(directScalar.left, changedCellIndex, replacementNumber, touched, readCellNumber);
    const right = readDirectScalarOperandNumber(directScalar.right, changedCellIndex, replacementNumber, touched, readCellNumber);
    if (left === undefined || right === undefined) {
        return undefined;
    }
    let result;
    switch (directScalar.operator) {
        case '+':
            result = left + right;
            break;
        case '-':
            result = left - right;
            break;
        case '*':
            result = left * right;
            break;
        case '/':
            if (right === 0) {
                return undefined;
            }
            result = left / right;
            break;
    }
    return result + (directScalar.resultOffset ?? 0);
}
export function evaluateDirectScalarWithReplacementNumbers(directScalar, primaryCellIndex, primaryNumber, readCellNumber, secondaryCellIndex, secondaryNumber) {
    if (directScalar.kind === 'abs') {
        const operand = readDirectScalarOperandReplacementNumber(directScalar.operand, primaryCellIndex, primaryNumber, secondaryCellIndex, secondaryNumber, readCellNumber);
        return operand === undefined ? undefined : Math.abs(operand);
    }
    const left = readDirectScalarOperandReplacementNumber(directScalar.left, primaryCellIndex, primaryNumber, secondaryCellIndex, secondaryNumber, readCellNumber);
    const right = readDirectScalarOperandReplacementNumber(directScalar.right, primaryCellIndex, primaryNumber, secondaryCellIndex, secondaryNumber, readCellNumber);
    if (left === undefined || right === undefined) {
        return undefined;
    }
    let result;
    switch (directScalar.operator) {
        case '+':
            result = left + right;
            break;
        case '-':
            result = left - right;
            break;
        case '*':
            result = left * right;
            break;
        case '/':
            if (right === 0) {
                return undefined;
            }
            result = left / right;
            break;
    }
    return result + (directScalar.resultOffset ?? 0);
}
export function directScalarDeltaFromNumbers(directScalar, changedCellIndex, oldChangedNumber, newChangedNumber, readCellNumber) {
    if (directScalar.kind === 'abs') {
        return directScalar.operand.kind === 'cell' && directScalar.operand.cellIndex === changedCellIndex
            ? Math.abs(newChangedNumber) - Math.abs(oldChangedNumber)
            : undefined;
    }
    const changedDelta = newChangedNumber - oldChangedNumber;
    if (directScalar.left.kind === 'cell' && directScalar.left.cellIndex === changedCellIndex) {
        switch (directScalar.operator) {
            case '+':
            case '-':
                return changedDelta;
            case '*':
                if (directScalar.right.kind === 'literal-number') {
                    return changedDelta * directScalar.right.value;
                }
                break;
            case '/':
                return directScalar.right.kind === 'literal-number' && directScalar.right.value !== 0
                    ? changedDelta / directScalar.right.value
                    : undefined;
        }
    }
    if (directScalar.right.kind === 'cell' && directScalar.right.cellIndex === changedCellIndex) {
        switch (directScalar.operator) {
            case '+':
                return changedDelta;
            case '-':
                return -changedDelta;
            case '*':
                if (directScalar.left.kind === 'literal-number') {
                    return directScalar.left.value * changedDelta;
                }
                break;
            case '/':
                return directScalar.left.kind === 'literal-number' && oldChangedNumber !== 0 && newChangedNumber !== 0
                    ? directScalar.left.value / newChangedNumber - directScalar.left.value / oldChangedNumber
                    : undefined;
        }
    }
    const oldTouched = { value: false };
    const oldResult = evaluateDirectScalarNumber(directScalar, changedCellIndex, oldChangedNumber, oldTouched, readCellNumber);
    if (!oldTouched.value || oldResult === undefined) {
        return undefined;
    }
    const newTouched = { value: false };
    const newResult = evaluateDirectScalarNumber(directScalar, changedCellIndex, newChangedNumber, newTouched, readCellNumber);
    if (!newTouched.value || newResult === undefined) {
        return undefined;
    }
    return newResult - oldResult;
}
export function directScalarDeltaFromValues(directScalar, changedCellIndex, oldValue, newValue, readCellNumber) {
    const oldChangedNumber = directScalarValueNumber(oldValue);
    const newChangedNumber = directScalarValueNumber(newValue);
    return oldChangedNumber === undefined || newChangedNumber === undefined
        ? undefined
        : directScalarDeltaFromNumbers(directScalar, changedCellIndex, oldChangedNumber, newChangedNumber, readCellNumber);
}
export function singleInputAffineDirectScalar(directScalar, inputCellIndex) {
    const affine = { scale: 0, offset: 0 };
    return writeSingleInputAffineDirectScalar(directScalar, inputCellIndex, affine) ? affine : null;
}
export function writeSingleInputAffineDirectScalar(directScalar, inputCellIndex, target) {
    if (directScalar.kind === 'abs') {
        return false;
    }
    const leftIsInput = directScalar.left.kind === 'cell' && directScalar.left.cellIndex === inputCellIndex;
    const rightIsInput = directScalar.right.kind === 'cell' && directScalar.right.cellIndex === inputCellIndex;
    const leftLiteral = directScalar.left.kind === 'literal-number' ? directScalar.left.value : undefined;
    const rightLiteral = directScalar.right.kind === 'literal-number' ? directScalar.right.value : undefined;
    if (leftIsInput && rightLiteral !== undefined) {
        const resultOffset = directScalar.resultOffset ?? 0;
        switch (directScalar.operator) {
            case '+':
                target.scale = 1;
                target.offset = rightLiteral + resultOffset;
                return true;
            case '-':
                target.scale = 1;
                target.offset = -rightLiteral + resultOffset;
                return true;
            case '*':
                target.scale = rightLiteral;
                target.offset = resultOffset;
                return true;
            case '/':
                if (rightLiteral === 0) {
                    return false;
                }
                target.scale = 1 / rightLiteral;
                target.offset = resultOffset;
                return true;
        }
    }
    if (rightIsInput && leftLiteral !== undefined) {
        const resultOffset = directScalar.resultOffset ?? 0;
        switch (directScalar.operator) {
            case '+':
                target.scale = 1;
                target.offset = leftLiteral + resultOffset;
                return true;
            case '-':
                target.scale = -1;
                target.offset = leftLiteral + resultOffset;
                return true;
            case '*':
                target.scale = leftLiteral;
                target.offset = resultOffset;
                return true;
            case '/':
                return false;
        }
    }
    return false;
}
export function rowPairDirectScalarCode(directScalar, leftCellIndex, rightCellIndex) {
    if (directScalar.kind === 'abs' || directScalar.resultOffset !== undefined) {
        return 0;
    }
    const leftOperandIsLeft = directScalar.left.kind === 'cell' && directScalar.left.cellIndex === leftCellIndex;
    const leftOperandIsRight = directScalar.left.kind === 'cell' && directScalar.left.cellIndex === rightCellIndex;
    const rightOperandIsLeft = directScalar.right.kind === 'cell' && directScalar.right.cellIndex === leftCellIndex;
    const rightOperandIsRight = directScalar.right.kind === 'cell' && directScalar.right.cellIndex === rightCellIndex;
    if (leftOperandIsLeft && rightOperandIsRight) {
        switch (directScalar.operator) {
            case '+':
                return ROW_PAIR_LEFT_PLUS_RIGHT;
            case '-':
                return ROW_PAIR_LEFT_MINUS_RIGHT;
            case '*':
                return ROW_PAIR_LEFT_TIMES_RIGHT;
            case '/':
                return ROW_PAIR_LEFT_DIV_RIGHT;
        }
    }
    if (leftOperandIsRight && rightOperandIsLeft) {
        switch (directScalar.operator) {
            case '+':
                return ROW_PAIR_LEFT_PLUS_RIGHT;
            case '-':
                return ROW_PAIR_RIGHT_MINUS_LEFT;
            case '*':
                return ROW_PAIR_LEFT_TIMES_RIGHT;
            case '/':
                return ROW_PAIR_RIGHT_DIV_LEFT;
        }
    }
    return 0;
}
export function evaluateRowPairDirectScalarCode(code, leftValue, rightValue) {
    switch (code) {
        case ROW_PAIR_LEFT_PLUS_RIGHT:
            return leftValue + rightValue;
        case ROW_PAIR_LEFT_MINUS_RIGHT:
            return leftValue - rightValue;
        case ROW_PAIR_RIGHT_MINUS_LEFT:
            return rightValue - leftValue;
        case ROW_PAIR_LEFT_TIMES_RIGHT:
            return leftValue * rightValue;
        case ROW_PAIR_LEFT_DIV_RIGHT:
            return rightValue === 0 ? undefined : leftValue / rightValue;
        case ROW_PAIR_RIGHT_DIV_LEFT:
            return leftValue === 0 ? undefined : rightValue / leftValue;
        default:
            return undefined;
    }
}
export function rowPairDirectScalarCodeNeedsZeroGuard(code) {
    return code === ROW_PAIR_LEFT_DIV_RIGHT || code === ROW_PAIR_RIGHT_DIV_LEFT;
}
//# sourceMappingURL=direct-scalar-helpers.js.map