"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPredecessors = exports.getContainingIfTestAndOriginalDeclarator = void 0;
const traverse_1 = require("@glas/traverse");
const toposort_1 = __importDefault(require("../toposort"));
const ast_1 = require("../ast");
const ast = __importStar(require("../ast"));
const common_1 = require("../common");
function contains(graph, predicate) {
    let found = false;
    traverse_1.traverse(graph, {
        enter(node) {
            if (!found && predicate(node)) {
                found = true;
            }
            if (found) {
                return traverse_1.skip;
            }
        }
    });
    return found;
}
function getNonRecursiveReturnStatements(fn) {
    let statements = [];
    traverse_1.traverse(fn, {
        enter(node) {
            if (ast_1.CallExpression.is(node)) {
                return traverse_1.skip;
            }
        },
        leave(node) {
            var _a;
            if (ast_1.ReturnStatement.is(node) && node.argument != null) {
                // make sure the return argument doesn't contain a recursive reference to itself.
                if (((_a = fn.id) === null || _a === void 0 ? void 0 : _a.path) == null || !contains(node.argument, check => ast.Reference.is(check) && check.path === fn.id.path)) {
                    statements.push(node);
                }
            }
        }
    });
    return statements;
}
function getContainingIfTestAndOriginalDeclarator(node, scopeMap, ancestorMap) {
    var _a;
    let containingIf = common_1.getAncestor(node, ancestorMap, ast_1.IfStatement.is);
    let containingIfScope = scopeMap.get(containingIf);
    let name = node.id.name;
    let containingVarDeclarator = (_a = containingIfScope[name]) !== null && _a !== void 0 ? _a : null;
    return [containingIf.test, containingVarDeclarator];
}
exports.getContainingIfTestAndOriginalDeclarator = getContainingIfTestAndOriginalDeclarator;
function getPredecessors(node, scopeMap, ancestorMap) {
    return predecessors[node.constructor.name](node, scopeMap, ancestorMap);
}
exports.getPredecessors = getPredecessors;
const predecessors = {
    *ConditionalDeclaration(node, scopeMap, ancestorMap) {
        // the conditional declaration will add it's own local conditional assertion to the variable type
        // from the containing scope, so we are dependent on that variable being resolved first.
        let [containingIfTest, containingVarDeclarator] = getContainingIfTestAndOriginalDeclarator(node, scopeMap, ancestorMap);
        if (containingIfTest) {
            yield containingIfTest;
        }
        if (containingVarDeclarator) {
            yield containingVarDeclarator;
        }
    },
    *ArrayPattern(node) {
        for (let element of node.elements) {
            //  all pattern elements are dependent on this nodes type first.
            //  then their type will be a member of this nodes type
            if (element != null) {
                yield [node, element];
            }
        }
    },
    *ObjectPattern(node) {
        for (let prop of node.properties) {
            //  all object pattern properties are dependent on this nodes type first.
            //  then their type will be a member of this nodes type
            yield [node, prop];
        }
    },
    *BinaryExpression(node) {
        yield node.left;
        yield node.right;
    },
    *UnaryExpression(node) {
        yield node.argument;
    },
    *Declarator(node, scopeMap, ancestorMap) {
        let parent = ancestorMap.get(node);
        if (ast_1.Typed.is(parent)) {
            yield parent;
        }
    },
    *Literal(node, scopeMap, ancestorMap) {
        if (node.type) {
            // we need to know the type for these friggin literals right away.
            yield node.type;
        }
    },
    *ObjectExpression(node) {
        for (let property of node.properties) {
            if (ast_1.Property.is(property)) {
                if (ast_1.Typed.is(property.key)) {
                    yield property.key;
                }
                if (ast_1.Expression.is(property.value)) {
                    yield property.value;
                }
            }
            else {
                // SpreadElement
                yield property.argument;
            }
        }
    },
    *ClassDeclaration(node) {
        // this nodes declarator is dependent on this node
        yield [node, node.id];
        yield* node.baseClasses;
        yield* node.static.values();
        yield* node.instance.declarations.values();
    },
    *Variable(node) {
        // make the id pattern dependent on this type
        yield [node, node.id];
        if (node.value) {
            yield node.value;
        }
        if (node.type) {
            yield node.type;
        }
    },
    *Parameter(node, scopeMap, ancestorMap) {
        yield* predecessors.Variable(node, scopeMap, ancestorMap);
    },
    *VariableDeclaration(node, scopeMap, ancestorMap) {
        yield* predecessors.Variable(node, scopeMap, ancestorMap);
    },
    *TypeExpression(node) {
        yield node.value;
    },
    *FunctionExpression(node) {
        // a function depends on it's parameters which means it depends on it's parameter types
        yield* node.params;
        if (node.returnType === null) {
            for (let returnStatement of getNonRecursiveReturnStatements(node)) {
                if (returnStatement.argument != null) {
                    yield returnStatement.argument;
                }
            }
        }
    },
    *Reference(node, scopes) {
        let referencedNode = scopes.get(node)[node.name];
        if (referencedNode != null) {
            yield referencedNode;
            // we don't throw on unrealized references... we just will consider them type any
            // throw SemanticError("Referenced value not found", node)
        }
    },
    // *TemplateReference(node) {
    //     yield node.reference
    //     yield* node.arguments
    // },
    *MemberExpression(node) {
        yield node.object;
        if (ast_1.Expression.is(node.property)) {
            yield node.property;
        }
    },
    *ArrayExpression(node) {
        for (let element of node.elements) {
            if (ast.SpreadElement.is(element)) {
                yield element.argument;
            }
            else if (element != null) {
                yield element;
            }
        }
    },
    *CallExpression(node, scopeMap, ancestorMap) {
        yield node.callee;
        for (let arg of node.arguments) {
            if (ast.SpreadElement.is(arg)) {
                yield arg.argument;
            }
            else {
                yield arg;
            }
        }
    },
};
function getSortedTypedNodes(root, scopeMap, ancestorsMap) {
    let sentinel = {};
    let edges = [];
    function push(from, to) {
        if (from == null || to == null) {
            throw new Error("Edge nodes may not be null");
        }
        if (from === to) {
            console.error(from);
            throw new Error("Attempt to add same node as dependency of itself");
        }
        edges.push([from, to]);
    }
    let nodes = new Array();
    traverse_1.traverse(root, {
        leave(node) {
            if (ast_1.Typed.is(node)) {
                nodes.push(node);
            }
        }
    });
    // now... we can try to sort the nodes based on what order we think they should be in.
    // the reason for this is that we neeed UFCS functions to be defined before they are called.
    // FunctionExpression depends on parameter types => Classes
    nodes.sort((a, b) => {
        const afunc = ast_1.FunctionExpression.is(a);
        const bfunc = ast_1.FunctionExpression.is(b);
        if (afunc && !bfunc) {
            return -1;
        }
        if (bfunc && !afunc) {
            return +1;
        }
        return 0;
    });
    for (let node of nodes) {
        if (ast_1.BinaryExpression.is(node)) {
            push(node.left, node.right);
        }
        let count = 0;
        if (ast_1.Typed.is(node)) {
            let func = predecessors[node.constructor.name];
            if (func) {
                for (let pred of func(node, scopeMap, ancestorsMap)) {
                    count++;
                    if (Array.isArray(pred)) {
                        push(pred[0], pred[1]);
                    }
                    else {
                        push(pred, node);
                    }
                }
            }
            if (count === 0) {
                push(sentinel, node);
            }
        }
    }
    let sorted = toposort_1.default(edges);
    //  remove sentinel
    sorted.splice(sorted.indexOf(sentinel), 1);
    return sorted;
}
exports.default = getSortedTypedNodes;
