"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ast_1 = require("../ast");
const toCodeString_1 = __importDefault(require("../toCodeString"));
const splitExpressions_1 = __importDefault(require("./splitExpressions"));
//  a  \  b |  true   false   null
//  --------------------------------
//  true    |  true   true    true
//  false   |  true   false   null
//  null    |  true   null    null
function max(a, b) {
    if (a === true || b === true)
        return true;
    if (a == null || b == null)
        return null;
    return false;
}
//  a  \  b |  true   false   null
//  --------------------------------
//  true    |  true   false   null
//  false   |  false  false   false
//  null    |  null   false   null
function min(a, b) {
    if (a === false || b === false)
        return false;
    if (a == null || b == null)
        return null;
    return true;
}
//  a  \  b |  true   false   null
//  --------------------------------
//  true    |  true   null    null
//  false   |  null   false   null
//  null    |  null   null    null
function same(a, b) {
    return a === b ? a : null;
}
/**
 * Assuming expression 'a' is true then this function returns
 * true if 'b' is necessarily true
 * false if 'b' is necessarily false
 * null if we cannot determine
 */
function isConsequent(a, b, isType = (a, b) => a.path === b.path) {
    if (ast_1.TypeExpression.is(a)) {
        a = a.value;
    }
    if (ast_1.TypeExpression.is(b)) {
        b = b.value;
    }
    if (toCodeString_1.default(a) === toCodeString_1.default(b)) {
        return true;
    }
    if (ast_1.BinaryExpression.is(a)) {
        if (ast_1.BinaryExpression.is(b)) {
            if (toCodeString_1.default(a.left) === toCodeString_1.default(b.left)) {
                if (ast_1.Reference.is(a.right) && ast_1.Reference.is(b.right)) {
                    if (a.operator === "is" && b.operator === "is") {
                        // use our isType to see if we know that a is b
                        return isType(a.right, b.right);
                    }
                }
                if (ast_1.Literal.is(a.right) && ast_1.Literal.is(b.right)) {
                    let ar = a.right.value;
                    let br = b.right.value;
                    switch (a.operator) {
                        case '>':
                            switch (b.operator) {
                                case '>=': // > 0 is >= 0, > 1 is >= 0
                                case '!=': // > 0 is != 0, > 1 is != 0
                                case '>': return ar >= br ? true : null; // > 0 is > 0, > 1 is > 0
                                case '<=': // > 0 isnt <= 0, > 1 isnt <= 0
                                case '==': // > 0 isnt == 0, > 1 isnt == 0
                                case '<': return ar >= br ? false : null; // > 0 isnt < 0, > 1 isnt < 0
                            }
                            break;
                        case '>=':
                            switch (b.operator) {
                                case '>=': // >= 1 is >= 0
                                case '>': return ar > br ? true : null; // >= 1 is > 0
                                case '==': // >= 1 isnt == 0
                                case '<=': return ar > br ? false : null; // >= 1 isnt <= 0
                                case '<': return ar >= br ? false : null; // >= 0 isnt < 0, >= 1 isnt < 0
                            }
                            break;
                        case '<':
                            switch (b.operator) {
                                case '<=': // < 0 is <= 0, < -1 is <= 0
                                case '!=': // < 0 is != 0, < -1 is != 0
                                case '<': return ar <= br ? true : null; // < 0 is < 0, < -1 is < 0
                                case '>=': // < 0 isnt >= 0, < -1 isnt >= 0
                                case '==': // < 0 isnt == 0, < -1 isnt == 0
                                case '>': return ar <= br ? false : null; // < 0 isnt > 0, < -1 isnt > 0
                            }
                            break;
                        case '<=':
                            switch (b.operator) {
                                case '<=': // <= -1 is <= 0
                                case '<': return ar < br ? true : null; // <= -1 is < 0
                                case '==': // <= -1 isnt == 0
                                case '>=': return ar < br ? false : null; // <= -1 isnt >= 0
                                case '>': return ar <= br ? false : null; // <= 0 isnt > 0, <= -1 isnt > 0
                            }
                            break;
                        case '==':
                            switch (b.operator) {
                                case '<=': return ar <= br; // == 0 is <= 0, == 0 is <= 1
                                case '<': return ar < br; // == 0 is < 1
                                case '==': return ar === br; // == 0 is == 0
                                case '>=': return ar >= br; // == 0 is >= 0
                                case '>': return ar > br; // == 0 is > -1
                                case '!=': return ar != br; // == 0 is != 1
                            }
                            break;
                    }
                }
                else if (toCodeString_1.default(a.right) === toCodeString_1.default(b.right)) {
                    // we can still analyze some comparisons if we know the both right hand operators are the same.
                    switch (a.operator) {
                        case '>':
                            switch (b.operator) {
                                case '>=':
                                case '!=': return true;
                                case '<':
                                case '<=':
                                case '==': return false;
                            }
                            break;
                        case '>=':
                            switch (b.operator) {
                                case '<': return false;
                            }
                            break;
                        case '<':
                            switch (b.operator) {
                                case '<=':
                                case '!=': return true;
                                case '>':
                                case '>=':
                                case '==': return false;
                            }
                            break;
                        case '<=':
                            switch (b.operator) {
                                case '>': return false;
                            }
                            break;
                        case '==':
                            switch (b.operator) {
                                case '>=':
                                case '<=': return true;
                                case '>':
                                case '<':
                                case '!=': return false;
                            }
                            break;
                        case '!=':
                            switch (b.operator) {
                                case '==': return false;
                            }
                            break;
                        case 'is':
                            switch (b.operator) {
                                case 'isnt': return false;
                            }
                            break;
                        case 'isnt':
                            switch (b.operator) {
                                case 'is': return false;
                            }
                            break;
                    }
                }
            }
        }
    }
    //  A & B & C => C & D & E
    //  if any term on the left results in a false on the right then false (not consequent)
    //  if all terms on the right are true based on any term on the left then true (consequent)
    //  otherwise null (unknown)
    if (ast_1.BinaryExpression.is(b) && b.operator === "&&" || ast_1.BinaryExpression.is(a) && a.operator === "&&") {
        let allTrue = true;
        for (let bTerm of splitExpressions_1.default(b, "&&")) {
            let bTermResult = null;
            for (let aTerm of splitExpressions_1.default(a, "&&")) {
                let aTermResult = isConsequent(aTerm, bTerm, isType);
                if (aTermResult === false) {
                    return false;
                }
                if (aTermResult === true) {
                    bTermResult = true;
                    break;
                }
            }
            if (bTermResult !== true) {
                allTrue = false;
            }
        }
        return allTrue || null;
    }
    //  A & B => C & D
    if (ast_1.BinaryExpression.is(a) && a.operator === "||") {
        return same(isConsequent(a.left, b, isType), isConsequent(a.right, b, isType));
    }
    if (ast_1.BinaryExpression.is(b) && b.operator === "||") {
        return max(isConsequent(a, b.left, isType), isConsequent(a, b.right, isType));
    }
    return null;
}
exports.default = isConsequent;
