"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getIsolateContext = exports.makeFn = exports.stringToFunction = exports.api = void 0;
var sdk_1 = require("@builder6/sdk");
var safe_dynamic_require_1 = require("./safe-dynamic-require");
var is_debug_1 = require("./is-debug");
var should_force_browser_runtime_in_node_1 = require("./should-force-browser-runtime-in-node");
var fnCache = {};
var api = function (state) { return sdk_1.builder; };
exports.api = api;
function stringToFunction(str, expression, errors, logs) {
    if (expression === void 0) { expression = true; }
    /* TODO: objedct */
    if (!str || !str.trim()) {
        return function () { return undefined; };
    }
    var cacheKey = str + ':' + expression;
    if (fnCache[cacheKey]) {
        return fnCache[cacheKey];
    }
    // FIXME: gross hack
    var useReturn = (expression &&
        !(str.includes(';') || str.includes(' return ') || str.trim().startsWith('return '))) ||
        str.trim().startsWith('builder.run');
    var fn = function () {
        /* intentionally empty */
    };
    try {
        // tslint:disable-next-line:no-function-constructor-with-string-args
        if (sdk_1.Builder.isBrowser) {
            // TODO: use strict and eval
            fn = new Function('state', 'event', 'block', 'builder', 'Device', 'update', 'Builder', 'context', 
            // TODO: remove the with () {} - make a page v3 that doesn't use this
            // Or only do if can't find state\s*\. anywhere hm
            "\n          var names = [\n            'state',\n            'event',\n            'block',\n            'builder',\n            'Device',\n            'update',\n            'Builder',\n            'context'\n          ];\n          var rootState = state;\n          if (typeof Proxy !== 'undefined') {\n            rootState = new Proxy(rootState, {\n              set: function () {\n                return false;\n              },\n              get: function (target, key) {\n                if (names.includes(key)) {\n                  return undefined;\n                }\n                return target[key];\n              }\n            });\n          }\n          /* Alias */\n          var ctx = context;\n          var log = console.log.bind(console);\n          with (rootState) {\n            ".concat(useReturn ? "return (".concat(str, ");") : str, ";\n          }\n        "));
        }
    }
    catch (error) {
        if (errors) {
            errors.push(error);
        }
        var message = error && error.message;
        if (message && typeof message === 'string') {
            if (logs && logs.indexOf(message) === -1) {
                logs.push(message);
            }
        }
        if (sdk_1.Builder.isBrowser) {
            console.warn("Function compile error in ".concat(str), error);
        }
    }
    var final = function () {
        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments[_i];
        }
        try {
            if (sdk_1.Builder.isBrowser || (0, should_force_browser_runtime_in_node_1.shouldForceBrowserRuntimeInNode)()) {
                return fn.apply(void 0, args);
            }
            else {
                // TODO: memoize on server
                // TODO: use something like this instead https://www.npmjs.com/package/rollup-plugin-strip-blocks
                // There must be something more widely used?
                // TODO: regex for between comments instead so can still type check the code... e.g. //SERVER-START ... code ... //SERVER-END
                // Below is a hack to get certain code to *only* load in the server build, to not screw with
                // browser bundler's like rollup and webpack. Our rollup plugin strips these comments only
                // for the server build
                // TODO: cache these for better performancs with new VmScript
                var isolateContext = (0, exports.getIsolateContext)();
                var ivm_1 = (0, safe_dynamic_require_1.safeDynamicRequire)('isolated-vm');
                var resultStr = isolateContext.evalClosureSync((0, exports.makeFn)(str, useReturn), args.map(function (arg, index) {
                    return typeof arg === 'object'
                        ? new ivm_1.Reference(index === indexOfBuilderInstance
                            ? __assign(__assign({}, arg), { getUserAttributes: function () { return arg.getUserAttributes(''); } }) : arg)
                        : null;
                }));
                try {
                    // returning objects throw errors in isolated vm, so we stringify it and parse it back
                    var res = JSON.parse(resultStr);
                    return res;
                }
                catch (_error) {
                    return resultStr;
                }
            }
        }
        catch (error) {
            if (sdk_1.Builder.isBrowser) {
                console.warn('Builder custom code error:', error.message || error, 'in', str, error.stack || error);
            }
            else {
                if ((0, is_debug_1.isDebug)()) {
                    console.debug('Builder custom code error:', error.message || error, 'in', str, error.stack || error);
                }
            }
            if (errors) {
                errors.push(error);
            }
            return null;
        }
    };
    if (sdk_1.Builder.isBrowser) {
        fnCache[cacheKey] = final;
    }
    return final;
}
exports.stringToFunction = stringToFunction;
var indexOfBuilderInstance = 3;
var makeFn = function (code, useReturn, args) {
    // Order must match the order of the arguments to the function
    var names = args || [
        'state',
        'event',
        'block',
        'builder',
        'Device',
        'update',
        'Builder',
        'context',
    ];
    // Convert all argument references to proxies, and pass `copySync` method to target object, to return a copy of the original JS object
    // https://github.com/laverdet/isolated-vm#referencecopysync
    var refToProxyFn = "\n  var refToProxy = (obj) => {\n    if (typeof obj !== 'object' || obj === null) {\n      return obj;\n    }\n    return new Proxy({}, {\n        get(target, key) {\n            if (key === 'copySync') {\n              return () => obj.copySync();\n            }\n            const val = obj.getSync(key);\n            if (typeof val?.copySync === 'function') {\n                return JSON.parse(stringify(val));\n            }\n            return val;\n        },\n        set(target, key, value) {\n            obj.setSync(key, value);\n        },\n        deleteProperty(target, key) {\n            obj.deleteSync(key);\n        }\n      })\n  }\n";
    // Returned object  will be stringified and parsed back to the parent isolate
    var strinfigyFn = "\n    var stringify = (val) => {\n      if (typeof val === 'object' && val !== null) {\n        return JSON.stringify(val.copySync ? val.copySync() : val);\n      }\n      return val;\n    }\n  ";
    return "\n".concat(refToProxyFn, "\n").concat(strinfigyFn, "\n").concat(names.map(function (arg, index) { return "var ".concat(arg, " = refToProxy($").concat(index, ");"); }).join('\n')).concat("\n".concat(names.includes('context') ? 'var ctx = context;' : '', "\nvar endResult = function() {\n  ").concat(useReturn ? "return (".concat(code, ");") : code, ";\n};\n\nreturn stringify(endResult());\n"));
};
exports.makeFn = makeFn;
var getIsolateContext = function () {
    var isolatedContext = sdk_1.Builder.serverContext;
    if (!isolatedContext) {
        var ivm = (0, safe_dynamic_require_1.safeDynamicRequire)('isolated-vm');
        var isolate = new ivm.Isolate({ memoryLimit: 128 });
        isolatedContext = isolate.createContextSync();
        sdk_1.Builder.setServerContext(isolatedContext);
    }
    var jail = isolatedContext.global;
    // This makes the global object available in the context as `global`. We use `derefInto()` here
    // because otherwise `global` would actually be a Reference{} object in the new isolate.
    jail.setSync('global', jail.derefInto());
    // We will create a basic `log` function for the new isolate to use.
    jail.setSync('log', function () {
        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments[_i];
        }
        if ((0, is_debug_1.isDebug)()) {
            console.log.apply(console, args);
        }
    });
    return isolatedContext;
};
exports.getIsolateContext = getIsolateContext;
//# sourceMappingURL=string-to-function.js.map