Object.defineProperty(exports, "__esModule", { value: true });
const config_1 = require("../config");
const operations_1 = require("../operations");
const invokeIfFunction_1 = require("../invokeIfFunction");
const logging_1 = require("./logging");
const util_1 = require("../util");
var global = Function("return this")();
// todo: would be better if the server provided this value
exports.getOperationIndex = (function () {
    var operationIndexBase = Math.round(Math.random() * 1000 * 1000 * 1000);
    var operationIndex = 0;
    return function getOperationIndex() {
        var index = operationIndex;
        operationIndex++;
        return operationIndexBase + operationIndex;
    };
})();
function serializeValue(value, knownValues) {
    // todo: consider accessing properties that are getters could have negative impact...
    const type = typeof value;
    if (typeof value === "string" ||
        typeof value === "number" ||
        typeof value === "boolean" ||
        value === null) {
        return value;
    }
    return getSerializedValueObject(value, type, knownValues);
}
function getSerializedValueObject(value, type, knownValues) {
    var knownValue = knownValues && knownValues.getName(value);
    if (type === null) {
        type = typeof value;
    }
    var length;
    if (Array.isArray(value) || type === "string") {
        length = value.length;
    }
    var knownTypes = undefined;
    if (global["HTMLInputElement"] &&
        value instanceof global["HTMLInputElement"]) {
        knownTypes = [];
        knownTypes.push("HTMLInputElement");
    }
    var primitive;
    if (["string", "number", "boolean", "null"].includes(type)) {
        primitive = value;
    }
    let keys;
    try {
        if (type === "object" &&
            value !== null &&
            !Array.isArray(value) &&
            !knownValue) {
            // todo: rethink this regarding perf
            // maybe don't collect keys, maybe do for...in instead
            // also: when inspecting i really want the trakcing data for
            // values/keys to be accessible, so maybe just storing keys makes more sense
            keys = Object.keys(value);
            if (keys.length > 5) {
                keys = keys.slice(0, 5);
                keys.push("...");
            }
        }
    }
    catch (err) { }
    return {
        length,
        type,
        primitive,
        knownValue,
        knownTypes,
        keys
    };
}
exports.getSerializedValueObject = getSerializedValueObject;
class SerializedValue {
    constructor(data) {
        this.length = data.length;
        this.type = data.type;
        this.keys = data.keys;
        this.primitive = data.primitive;
        this.knownValue = data.knownValue;
        this.knownTypes = data.knownTypes;
    }
    getTruncatedUIString() {
        if (this.knownValue) {
            return this.knownValue;
        }
        if (["string", "null", "number", "boolean"].includes(this.type)) {
            let ret = this.primitive + "";
            if (ret.length > 200) {
                return ret.slice(0, 200);
            }
            else {
                return ret;
            }
        }
        let str = "[" + this.type + "]";
        if (this.keys && this.keys.length > 0) {
            str += " {" + this.keys.filter(k => k !== "__elOrigin").join(", ") + "}";
        }
        return str;
    }
}
class OperationLog {
    get result() {
        const resultIsSerializedValueObject = !["string", "boolean", "number"].includes(typeof this._result) &&
            this._result !== null &&
            this._result &&
            "type" in this._result;
        let sv;
        if (resultIsSerializedValueObject) {
            sv = this._result;
        }
        else {
            sv = getSerializedValueObject(this._result, null, null);
        }
        return new SerializedValue(sv);
    }
    constructor({ operation, _result, args, astArgs, extraArgs, stackFrames, loc, runtimeArgs, index }) {
        this.stackFrames = stackFrames;
        this.operation = operation;
        this._result = _result;
        this.runtimeArgs = runtimeArgs;
        this.loc = loc;
        this.args = args;
        this.astArgs = astArgs;
        this.extraArgs = extraArgs;
        this.index = index;
    }
}
exports.default = OperationLog;
OperationLog.createAtRuntime = function ({ operation, result, args, astArgs, extraArgs, loc, runtimeArgs, index }, knownValues) {
    if (config_1.VERIFY && !loc) {
        logging_1.consoleLog("no loc at runtime for operation", operation);
    }
    const op = operations_1.default[operation];
    if (astArgs && util_1.countObjectKeys(astArgs) === 0) {
        astArgs = undefined;
    }
    if (Array.isArray(args)) {
        const newArgs = [];
        for (var i = 0; i < args.length; i++) {
            const arg = args[i];
            if (op["argIsArray"] &&
                invokeIfFunction_1.default(op["argIsArray"][i], arguments[0])) {
                const a = [];
                for (var arrayArgIndex = 0; arrayArgIndex < arg.length; arrayArgIndex++) {
                    const arrayArg = arg[arrayArgIndex];
                    a.push(arrayArg[1]);
                }
                newArgs.push(a);
            }
            else {
                newArgs.push(arg[1]);
            }
        }
        args = newArgs;
        if (args.length === 1 && !args[0]) {
            args = undefined;
        }
    }
    else if (args) {
        if (operation === "objectExpression" && args.properties) {
            // todo: centralize this logic, shouldn't need to do if, see "arrayexpression" above also"
            args.properties = args.properties.map(prop => {
                return {
                    key: prop.key[1],
                    type: prop.type[1],
                    value: prop.value[1]
                };
            });
        }
        else {
            // only store argument operation log because ol.result === a[0]
            eachArgument(args, (arg, argName, updateArg) => {
                verifyArg(arg);
                updateArg(arg[1]);
            });
        }
    }
    if (typeof extraArgs === "object") {
        eachArgument(extraArgs, (arg, argName, updateArg) => {
            verifyArg(arg);
            updateArg(arg[1]);
        });
    }
    let _result;
    let canInfer = false;
    if (config_1.MINIMIZE_LOG_DATA_SIZE) {
        let opCanInfer = op && op.canInferResult;
        if (typeof opCanInfer === "boolean") {
            canInfer = opCanInfer;
        }
        else if (typeof opCanInfer === "function" && args) {
            canInfer = opCanInfer(args, extraArgs);
        }
    }
    if (canInfer) {
        // args can be inferred on BE by checking the AST loc data, or by checking argument values
    }
    else {
        _result = serializeValue(result, knownValues);
    }
    return {
        operation,
        _result,
        index,
        extraArgs,
        args,
        astArgs,
        loc,
        runtimeArgs
    };
};
// TODO: don't copy/paste this
function eachArgument(args, fn) {
    const keys = Object.keys(args);
    for (var i = 0; i < keys.length; i++) {
        const key = keys[i];
        fn(args[key], key, newValue => (args[key] = newValue));
    }
}
function verifyArg(arg) {
    if (config_1.VERIFY &&
        typeof arg[1] !== "number" &&
        arg[1] !== null &&
        arg[1] !== undefined &&
        arg[1] !== false) {
        debugger;
        throw Error("no arg operationlog found, did you only pass in an operationlog");
    }
}
//# sourceMappingURL=OperationLog.js.map