Object.defineProperty(exports, "__esModule", { value: true });
const babelPluginHelpers_1 = require("../babelPluginHelpers");
const traverseStringConcat_1 = require("../traverseStringConcat");
const mapInnerHTMLAssignment_1 = require("./domHelpers/mapInnerHTMLAssignment");
const addElOrigin_1 = require("./domHelpers/addElOrigin");
const MemoValueNames = require("../MemoValueNames");
const util_1 = require("../util");
const OperationTypes = require("../OperationTypes");
exports.default = {
    argNames: log => {
        if (log.astArgs.assignmentType === "MemberExpression") {
            return ["object", "propertyName", "argument"];
        }
        else {
            return ["currentValue", "newValue", "argument"];
        }
    },
    exec: (args, astArgs, ctx, logData) => {
        var ret;
        const assignmentType = astArgs.type;
        const operator = astArgs.operator;
        if (assignmentType === "MemberExpression") {
            const [objArg, propertyNameArg, argumentArg] = args;
            var obj = objArg[0];
            var propName = propertyNameArg[0];
            var objT = objArg[1];
            var propNameT = propertyNameArg[1];
            var currentValue = obj[propName];
            var currentValueT = ctx.createOperationLog({
                operation: "memexpAsLeftAssExp",
                args: {
                    object: [obj, objT],
                    propertyName: [propName, propNameT]
                },
                extraArgs: {
                    propertyValue: [
                        currentValue,
                        ctx.getObjectPropertyTrackingValue(obj, propName)
                    ]
                },
                astArgs: {},
                result: currentValue,
                loc: logData.loc
            });
            var argument = argumentArg[0];
            let newValue;
            switch (operator) {
                case "=":
                    newValue = argument;
                    break;
                case "+=":
                    newValue = obj[propName] + argument;
                    break;
                case "-=":
                    newValue = obj[propName] - argument;
                    break;
                case "*=":
                    newValue = obj[propName] * argument;
                    break;
                case "/=":
                    newValue = obj[propName] / argument;
                    break;
                case "|=":
                    newValue = obj[propName] | argument;
                    break;
                case "%=":
                    newValue = obj[propName] % argument;
                    break;
                case "&=":
                    newValue = obj[propName] & argument;
                    break;
                case "**=":
                    newValue = obj[propName] ** argument;
                    break;
                case "<<=":
                    newValue = obj[propName] << argument;
                    break;
                case ">>=":
                    newValue = obj[propName] >> argument;
                    break;
                case ">>>=":
                    newValue = obj[propName] >>> argument;
                    break;
                case "^=":
                    newValue = obj[propName] ^ argument;
                    break;
                default:
                    throw Error("unknown operator " + operator);
            }
            obj[propName] = newValue;
            ret = newValue;
            ctx.trackObjectPropertyAssignment(obj, propName, ctx.createOperationLog({
                result: ret,
                operation: "assignmentExpression",
                args: {
                    currentValue: [currentValue, currentValueT],
                    argument: argumentArg
                },
                astArgs: {
                    operator
                },
                loc: logData.loc
            }), propNameT);
            const objIsHTMLNode = typeof Node !== "undefined" && obj instanceof Node;
            if (objIsHTMLNode) {
                if (propName === "innerHTML") {
                    mapInnerHTMLAssignment_1.default(obj, argumentArg, "assignInnerHTML", 0);
                }
                else if (["text", "textContent", "nodeValue"].includes(propName)) {
                    if (obj.nodeType === Node.TEXT_NODE) {
                        addElOrigin_1.default(obj, "textValue", {
                            trackingValue: argumentArg[1]
                        });
                    }
                    else if (obj.nodeType === Node.ELEMENT_NODE) {
                        if (obj.childNodes.length > 0) {
                            // can be 0 still if textValue is ""
                            addElOrigin_1.default(obj.childNodes[0], "textValue", {
                                trackingValue: argumentArg[1]
                            });
                        }
                    }
                    else {
                        // e.g. document fragments etc... should ideally handle this one day
                    }
                }
                else if (
                // This is overly broad (and will track "elOrigins" for arbitraty property names),
                // but at least it makes sure all attributes are tracked
                util_1.safelyReadProperty(obj, "nodeType") === Node.ELEMENT_NODE &&
                    typeof propName === "string") {
                    let attrName = propName;
                    if (attrName === "className") {
                        attrName = "class";
                    }
                    addElOrigin_1.addElAttributeValueOrigin(obj, attrName, {
                        trackingValue: argumentArg[1]
                    });
                    addElOrigin_1.addElAttributeNameOrigin(obj, attrName, { trackingValue: propNameT });
                }
            }
            else if (typeof CSSStyleDeclaration !== "undefined" &&
                obj instanceof CSSStyleDeclaration &&
                obj["__element"]) {
                addElOrigin_1.trackSetElementStyle(obj["__element"], propName, ctx.createOperationLog({
                    operation: OperationTypes.styleAssignment,
                    loc: logData.loc,
                    args: {
                        styleName: [null, propNameT],
                        styleValue: argumentArg
                    },
                    result: propName + ": " + newValue
                }));
            }
        }
        else if (assignmentType === "Identifier") {
            const [currentValueArg, newValueArg, argumentArg] = args;
            ret = newValueArg[0];
        }
        else {
            throw Error("unknown: " + assignmentType);
        }
        return ret;
    },
    traverse(operationLog, charIndex) {
        const { operator } = operationLog.astArgs;
        if (operator === "=") {
            return {
                operationLog: operationLog.args.argument,
                charIndex: charIndex
            };
        }
        else if (operator === "+=") {
            return traverseStringConcat_1.default(operationLog.args.currentValue, operationLog.args.argument, charIndex);
        }
        else {
            return;
        }
    },
    visitor(path) {
        path.node.ignore = true;
        const type = path.node.left.type;
        let operationArguments;
        let trackingAssignment = null;
        if (path.node.left.type === "MemberExpression") {
            var property;
            if (path.node.left.computed === true) {
                property = path.node.left.property;
            }
            else {
                property = this.t.stringLiteral(path.node.left.property.name);
                property.loc = path.node.left.property.loc;
            }
            operationArguments = [
                [path.node.left.object, babelPluginHelpers_1.getLastOperationTrackingResultCall()],
                [property, babelPluginHelpers_1.getLastOperationTrackingResultCall()],
                [path.node.right, babelPluginHelpers_1.getLastOperationTrackingResultCall()]
            ];
        }
        else if (path.node.left.type === "Identifier") {
            var right = babelPluginHelpers_1.createSetMemoValue(MemoValueNames.lastAssignmentExpressionArgument, path.node.right, babelPluginHelpers_1.getLastOperationTrackingResultCall());
            path.node.right = right;
            trackingAssignment = babelPluginHelpers_1.runIfIdentifierExists(babelPluginHelpers_1.getTrackingVarName(path.node.left.name), babelPluginHelpers_1.ignoreNode(this.t.assignmentExpression("=", babelPluginHelpers_1.getTrackingIdentifier(path.node.left.name), 
            // Normally we want to reset the value after an operation, but the problem is
            // that after the tracking assignment the result value of the assignment operation could
            // be read.
            // Like this: `a = (b = 4)`
            // If we use reset the last tracking value the `a =` assignment will lose the tracking value
            // (In theory we could detect if the result is used somehow and only not reset if we know it's used
            // base on the AST)
            babelPluginHelpers_1.getLastOperationTrackingResultWithoutResettingCall())));
            const identifierAssignedTo = path.node.left;
            // we have to check if it exists because outside strict mode
            // you can assign to undeclared global variables
            const identifierValue = babelPluginHelpers_1.runIfIdentifierExists(identifierAssignedTo.name, identifierAssignedTo);
            operationArguments = [
                [identifierValue, babelPluginHelpers_1.getLastOperationTrackingResultCall()],
                [path.node, babelPluginHelpers_1.getLastOperationTrackingResultCall()],
                babelPluginHelpers_1.createGetMemoArray(MemoValueNames.lastAssignmentExpressionArgument)
            ];
        }
        else if (path.node.left.type === "ObjectPattern") {
            console.log("assignment expression with objectpattern - not handled right now");
            return;
        }
        else if (path.node.left.type === "ArrayPattern") {
            console.log("assignment expression with arraypattern - not handled right now");
            return;
        }
        else {
            throw Error("unhandled assignmentexpression node.left type " + path.node.left.type);
        }
        const operation = this.createNode(operationArguments, {
            operator: babelPluginHelpers_1.ignoredStringLiteral(path.node.operator),
            type: babelPluginHelpers_1.ignoredStringLiteral(type)
        }, path.node.loc);
        if (trackingAssignment) {
            path.replaceWith(this.t.sequenceExpression([
                operation,
                trackingAssignment,
                babelPluginHelpers_1.getLastOpValueCall()
            ]));
        }
        else {
            path.replaceWith(operation);
        }
    }
};
//# sourceMappingURL=AssignmentExpression.js.map