/* oxlint-disable @typescript-eslint/no-explicit-any */
import { must } from "../../../shared/src/must.js";
import { toStaticParam, } from "../../../zero-protocol/src/ast.js";
export class ExpressionBuilder {
    #exists;
    constructor(exists) {
        this.#exists = exists;
        this.exists = this.exists.bind(this);
    }
    get eb() {
        return this;
    }
    cmp(field, opOrValue, value) {
        return cmp(field, opOrValue, value);
    }
    cmpLit(left, op, right) {
        return {
            type: 'simple',
            left: isParameterReference(left)
                ? left[toStaticParam]()
                : { type: 'literal', value: left },
            right: isParameterReference(right)
                ? right[toStaticParam]()
                : { type: 'literal', value: right },
            op,
        };
    }
    and = and;
    or = or;
    not = not;
    exists = (relationship, cb, options) => this.#exists(relationship, cb, options);
}
export function and(...conditions) {
    const expressions = filterTrue(filterUndefined(conditions));
    if (expressions.length === 1) {
        return expressions[0];
    }
    if (expressions.some(isAlwaysFalse)) {
        return FALSE;
    }
    return { type: 'and', conditions: expressions };
}
export function or(...conditions) {
    const expressions = filterFalse(filterUndefined(conditions));
    if (expressions.length === 1) {
        return expressions[0];
    }
    if (expressions.some(isAlwaysTrue)) {
        return TRUE;
    }
    return { type: 'or', conditions: expressions };
}
export function not(expression) {
    switch (expression.type) {
        case 'and':
            return {
                type: 'or',
                conditions: expression.conditions.map(not),
            };
        case 'or':
            return {
                type: 'and',
                conditions: expression.conditions.map(not),
            };
        case 'correlatedSubquery':
            return {
                type: 'correlatedSubquery',
                related: expression.related,
                op: negateOperator(expression.op),
            };
        case 'simple':
            return {
                type: 'simple',
                op: negateOperator(expression.op),
                left: expression.left,
                right: expression.right,
            };
    }
}
export function cmp(field, opOrValue, value) {
    let op;
    if (value === undefined) {
        value = opOrValue;
        op = '=';
    }
    else {
        op = opOrValue;
    }
    return {
        type: 'simple',
        left: { type: 'column', name: field },
        right: isParameterReference(value)
            ? value[toStaticParam]()
            : { type: 'literal', value },
        op,
    };
}
function isParameterReference(value) {
    return (value !== null && typeof value === 'object' && value[toStaticParam]);
}
export const TRUE = {
    type: 'and',
    conditions: [],
};
const FALSE = {
    type: 'or',
    conditions: [],
};
function isAlwaysTrue(condition) {
    return condition.type === 'and' && condition.conditions.length === 0;
}
function isAlwaysFalse(condition) {
    return condition.type === 'or' && condition.conditions.length === 0;
}
export function simplifyCondition(c) {
    if (c.type === 'simple' || c.type === 'correlatedSubquery') {
        return c;
    }
    if (c.conditions.length === 1) {
        return simplifyCondition(c.conditions[0]);
    }
    const conditions = flatten(c.type, c.conditions.map(simplifyCondition));
    if (c.type === 'and' && conditions.some(isAlwaysFalse)) {
        return FALSE;
    }
    if (c.type === 'or' && conditions.some(isAlwaysTrue)) {
        return TRUE;
    }
    return {
        type: c.type,
        conditions,
    };
}
export function flatten(type, conditions) {
    const flattened = [];
    for (const c of conditions) {
        if (c.type === type) {
            flattened.push(...c.conditions);
        }
        else {
            flattened.push(c);
        }
    }
    return flattened;
}
const negateSimpleOperatorMap = {
    ['=']: '!=',
    ['!=']: '=',
    ['<']: '>=',
    ['>']: '<=',
    ['>=']: '<',
    ['<=']: '>',
    ['IN']: 'NOT IN',
    ['NOT IN']: 'IN',
    ['LIKE']: 'NOT LIKE',
    ['NOT LIKE']: 'LIKE',
    ['ILIKE']: 'NOT ILIKE',
    ['NOT ILIKE']: 'ILIKE',
    ['IS']: 'IS NOT',
    ['IS NOT']: 'IS',
};
const negateOperatorMap = {
    ...negateSimpleOperatorMap,
    ['EXISTS']: 'NOT EXISTS',
    ['NOT EXISTS']: 'EXISTS',
};
export function negateOperator(op) {
    return must(negateOperatorMap[op]);
}
function filterUndefined(array) {
    return array.filter(e => e !== undefined);
}
function filterTrue(conditions) {
    return conditions.filter(c => !isAlwaysTrue(c));
}
function filterFalse(conditions) {
    return conditions.filter(c => !isAlwaysFalse(c));
}
//# sourceMappingURL=expression.js.map