"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const traverse_1 = require("@glas/traverse");
const ast_1 = require("../ast");
const Position_1 = __importDefault(require("../ast/Position"));
const VariableDeclaration_1 = __importDefault(require("../ast/VariableDeclaration"));
const MemberExpression_1 = __importDefault(require("../ast/MemberExpression"));
const Expression_1 = __importDefault(require("../ast/Expression"));
const ClassDeclaration_1 = __importDefault(require("../ast/ClassDeclaration"));
const AssignmentStatement_1 = __importDefault(require("../ast/AssignmentStatement"));
const common_1 = require("../common");
const operatorMap = {
    "==": "==="
};
function toEsTree(root, options) {
    return traverse_1.traverse(root, {
        enter(node) {
            if (Position_1.default.is(node)) {
                return traverse_1.skip;
            }
        },
        merge(node, changes, helper, ancestors) {
            var _a, _b, _c, _d, _e, _f;
            if (ast_1.Node.is(node)) {
                if (ast_1.ConditionalDeclaration.is(node)) {
                    return traverse_1.remove;
                }
                // all dot expressions SHOULD be converted by now, but for dev purposes we'll write it out
                if (ast_1.DotExpression.is(node)) {
                    return {
                        type: "Identifier",
                        name: "DOT"
                    };
                }
                // convert negative literal number to unary negation
                if (ast_1.Literal.is(node)) {
                    if (typeof node.value === "number" && node.value < 0) {
                        return {
                            type: "UnaryExpression",
                            operator: "-",
                            prefix: true,
                            argument: { type: "Literal", value: Math.abs(node.value) }
                        };
                    }
                }
                //  Convert Reference and Declarators to Identifier
                //  (They are both Identifier subclasses)
                if (ast_1.Identifier.is(node)) {
                    return {
                        type: "Identifier",
                        name: node.name
                    };
                }
                // Our VariableDeclarations diverge from ESTree for simplicity so we convert back.
                let result;
                if (VariableDeclaration_1.default.is(node)) {
                    let values = { ...node, ...changes };
                    result = {
                        type: "VariableDeclaration",
                        kind: node.kind === "conditional" ? node.kind : node.kind === "var" ? "let" : "const",
                        declarations: [{
                                type: "VariableDeclarator",
                                id: values.id,
                                init: values.value,
                                kind: node.kind,
                            }]
                    };
                }
                else if (AssignmentStatement_1.default.is(node)) {
                    let values = { ...node, ...changes };
                    result = {
                        type: "ExpressionStatement",
                        expression: {
                            type: "AssignmentExpression",
                            left: values.left,
                            operator: values.operator,
                            right: values.right,
                        }
                    };
                }
                else if (ast_1.Parameter.is(node)) {
                    if (node.value != null) {
                        result = {
                            type: "AssignmentPattern",
                            left: changes.id,
                            right: changes.value,
                        };
                    }
                    else {
                        result = changes.id;
                    }
                }
                else if (ast_1.RegularExpression.is(node)) {
                    try {
                        return {
                            type: "Literal",
                            value: new RegExp(node.pattern, node.flags)
                        };
                    }
                    catch (e) {
                        throw common_1.SemanticError(e.message, node);
                    }
                }
                else {
                    result = { ...node, ...changes, type: node.constructor.name };
                }
                if (VariableDeclaration_1.default.is(node) && ast_1.FunctionExpression.is(node.value)
                    && ast_1.Identifier.is(node.id) && node.id.name === ((_a = node.value.id) === null || _a === void 0 ? void 0 : _a.name)
                    && !node.value.bind) {
                    let cls = ancestors.find(ClassDeclaration_1.default.is);
                    if (cls === null) {
                        // simplify from let fn = function() to function fn()
                        result = { ...changes.value, type: "FunctionDeclaration" };
                    }
                }
                if (ast_1.FunctionExpression.is(node)) {
                    if (node.bind) {
                        result.type = "ArrowFunctionExpression";
                        //  if it's just a return statement
                        //  then we make it an expression arrow function
                        if (ast_1.ReturnStatement.is((_c = (_b = node.body) === null || _b === void 0 ? void 0 : _b.body) === null || _c === void 0 ? void 0 : _c[0])) {
                            result.body = result.body.body[0].argument;
                            result.expression = true;
                        }
                    }
                }
                //  convert UnaryExpressions to UpdateExpressions if they use ++ or --
                if (ast_1.UnaryExpression.is(node) && (node.operator === "++" || node.operator === "--")) {
                    result.type = "UpdateExpression";
                }
                // Add computed to MemberExpressions with Expressions as their property.
                if (MemberExpression_1.default.is(node) && Expression_1.default.is(node.property)) {
                    result.computed = true;
                }
                if (ast_1.BinaryExpression.is(node)) {
                    result.operator = (_d = operatorMap[node.operator]) !== null && _d !== void 0 ? _d : node.operator;
                }
                if (ast_1.SwitchCase.is(node)) {
                    // EsTree SwitchCase.consequent: Array<Statement>
                    // Ours is BlockStatement, so we convert to theirs.
                    result.consequent = (_f = (_e = result.consequent) === null || _e === void 0 ? void 0 : _e.body) !== null && _f !== void 0 ? _f : [];
                }
                if (ast_1.CallExpression.is(node) && node.new) {
                    result.type = "NewExpression";
                }
                if (ClassDeclaration_1.default.is(node)) {
                    let values = { ...node, ...changes };
                    let declarations = [...values.instance.declarations, ...values.static];
                    let originals = [...node.instance.declarations, ...node.static];
                    result = {
                        type: "ClassDeclaration",
                        id: values.id,
                        superClass: values.baseClasses[0],
                        body: {
                            type: "ClassBody",
                            body: declarations.map((v, index) => {
                                let original = originals[index];
                                if (!ast_1.FunctionExpression.is(original.value)) {
                                    return null;
                                }
                                let d = v.declarations[0];
                                return {
                                    type: "MethodDefinition",
                                    key: d.id,
                                    value: d.init,
                                    kind: (() => {
                                        switch (d.kind) {
                                            case "let":
                                            case "var": return "method";
                                            case "get": return "get";
                                            case "set": return "set";
                                        }
                                    })(),
                                    //  calculate if computed or not
                                    computed: Expression_1.default.is(original.id),
                                    static: original.static
                                };
                            }).filter(value => value != null)
                        }
                    };
                }
                // finally, handle exports
                if (ast_1.Exportable.is(node) && node.export > 0) {
                    if (ast_1.ImportDeclaration.is(node)) {
                        throw new Error("This is not implemented right");
                        result = traverse_1.replace({
                            type: "ExpressionStatement",
                            expression: { type: "Literal", value: "???" },
                        }, {
                            type: "ExpressionStatement",
                            expression: { type: "Literal", value: "???" },
                        });
                    }
                    else {
                        if (node.export === 2) {
                            result = {
                                type: "ExportDefaultDeclaration",
                                declaration: VariableDeclaration_1.default.is(node) ? result.declarations[0].init : result,
                                specifiers: [],
                                source: null,
                            };
                        }
                        else {
                            result = {
                                type: "ExportNamedDeclaration",
                                declaration: result,
                                specifiers: [],
                                source: null,
                            };
                        }
                    }
                }
                return result;
            }
        }
    });
}
exports.default = toEsTree;
