"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
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);
};
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __spreadArrays = (this && this.__spreadArrays) || function () {
    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
    for (var r = Array(s), k = 0, i = 0; i < il; i++)
        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
            r[k] = a[j];
    return r;
};
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = require("react");
// import ReactDOM from 'react-dom'
var builder_content_component_1 = require("./builder-content.component");
var builder_blocks_component_1 = require("./builder-blocks.component");
var sdk_1 = require("@builder.io/sdk");
var builder_store_1 = require("../store/builder-store");
var immer_1 = require("immer");
var pick_1 = require("lodash-es/pick");
var throttle_1 = require("lodash-es/throttle");
var size_1 = require("lodash-es/size");
var attempt_1 = require("lodash-es/attempt");
var isError_1 = require("lodash-es/isError");
var debounce_1 = require("lodash-es/debounce");
var device_sizes_constant_1 = require("../constants/device-sizes.constant");
var builder_async_requests_1 = require("../store/builder-async-requests");
var debonce_next_tick_1 = require("../functions/debonce-next-tick");
var react_native_1 = require("react-native");
var sizeMap = {
    desktop: 'large',
    tablet: 'medium',
    mobile: 'small',
};
function decorator(fn) {
    return function argReceiver() {
        var fnArgs = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            fnArgs[_i] = arguments[_i];
        }
        // Check if the decorator is being called without arguments (ex `@foo methodName() {}`)
        if (fnArgs.length === 3) {
            var target = fnArgs[0], key = fnArgs[1], descriptor = fnArgs[2];
            if (descriptor && (descriptor.value || descriptor.get)) {
                fnArgs = [];
                return descriptorChecker(target, key, descriptor);
            }
        }
        return descriptorChecker;
        // descriptorChecker determines whether a method or getter is being decorated
        // and replaces the appropriate key with the decorated function.
        function descriptorChecker(target, key, descriptor) {
            var _a;
            var descriptorKey = descriptor.value ? 'value' : 'get';
            return __assign(__assign({}, descriptor), (_a = {}, _a[descriptorKey] = fn.apply(void 0, __spreadArrays([descriptor[descriptorKey]], fnArgs)), _a));
        }
    };
}
var Throttle = decorator(throttle_1.default);
var fetchCache = {};
var tryEval = function (str, data, errors) {
    if (data === void 0) { data = {}; }
    var value = str;
    if (!(typeof value === 'string' && value.trim())) {
        return;
    }
    var useReturn = !(value.includes(';') || value.includes(' return '));
    var fn = function () {
        /* Intentionally empty */
    };
    try {
        if (sdk_1.Builder.isBrowser) {
            // tslint:disable-next-line:no-function-constructor-with-string-args
            // TODO: VM in node......
            fn = new Function('state', "with (state) {\n          " + (useReturn ? "return (" + value + ");" : value) + ";\n         }");
        }
    }
    catch (error) {
        if (sdk_1.Builder.isBrowser) {
            console.warn('Could not compile javascript', error);
        }
        else {
            // Add to req.options.errors to return to client
        }
    }
    try {
        if (sdk_1.Builder.isBrowser) {
            return fn(data || {});
        }
        else {
            // 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
            // tslint:disable:comment-format
            var VM = require('vm2').VM;
            return new VM({
                sandbox: __assign(__assign({}, data), { state: data }),
            }).run(value.replace(/^return /, ''));
            // tslint:enable:comment-format
        }
    }
    catch (error) {
        if (errors) {
            errors.push(error);
        }
        if (sdk_1.Builder.isBrowser) {
            console.warn('Builder custom code error:', error.message, 'in', str, error.stack);
        }
        else {
            console.debug('Builder custom code error:', error.message, 'in', str, error.stack);
            // Add to req.options.errors to return to client
        }
    }
    return;
};
function searchToObject(location) {
    var pairs = (location.search || '').substring(1).split('&');
    var obj = {};
    for (var i in pairs) {
        if (pairs[i] === '')
            continue;
        var pair = pairs[i].split('=');
        obj[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
    }
    return obj;
}
var BuilderPage = /** @class */ (function (_super) {
    __extends(BuilderPage, _super);
    function BuilderPage(props) {
        var _this = _super.call(this, props) || this;
        _this.subscriptions = new sdk_1.Subscription();
        _this.onStateChange = new sdk_1.BehaviorSubject(null);
        _this.lastJsCode = '';
        _this.lastHttpRequests = '';
        _this.ref = null;
        _this.resizeListener = debounce_1.default(function () {
            var deviceSize = _this.deviceSizeState;
            if (deviceSize !== _this.state.state.deviceSize) {
                _this.setState(__assign(__assign({}, _this.state), { state: __assign(__assign({}, _this.state.state), { deviceSize: deviceSize }) }));
            }
        }, 200, { leading: false, trailing: true });
        _this.updateState = function (fn) {
            var nextState = immer_1.default(_this.state.state, function (draftState) {
                fn(draftState);
                // TODO: emit dom event - what element? global?
            });
            _this.setState({
                update: _this.updateState,
                state: nextState,
            });
            _this.notifyStateChange();
        };
        _this.onContentLoaded = function (data) {
            // TODO: if model is page... hmm
            if (_this.name === 'page' && sdk_1.Builder.isBrowser) {
                if (data) {
                    var title = data.title, description = data.description;
                    // if (title) {
                    //   document.title = title;
                    // }
                    // if (description) {
                    //   let descriptionTag = document.querySelector('meta[name="description"]');
                    //   if (!descriptionTag) {
                    //     descriptionTag = document.createElement('meta');
                    //     descriptionTag.setAttribute('name', 'description');
                    //     document.head.appendChild(descriptionTag);
                    //   }
                    //   descriptionTag!.setAttribute('content', description);
                    // }
                }
            }
            // Unsubscribe all? TODO: maybe don't continuous fire when editing.....
            if (_this.props.contentLoaded) {
                sdk_1.Builder.nextTick(function () {
                    if (_this.props.contentLoaded) {
                        _this.props.contentLoaded(data);
                    }
                });
            }
            if (data && data.inputs && Array.isArray(data.inputs) && data.inputs.length) {
                if (!data.state) {
                    data.state = {};
                }
                // TODO: may not want this... or make sure anything overriden
                // explitily sets to null
                data.inputs.forEach(function (input) {
                    if (input) {
                        if (input.name &&
                            input.defaultValue !== undefined &&
                            data.state[input.name] === undefined) {
                            data.state[input.name] = input.defaultValue;
                        }
                    }
                });
            }
            if (data && data.state) {
                _this.setState(__assign(__assign({}, _this.state), { state: __assign(__assign(__assign(__assign({ isBrowser: sdk_1.Builder.isBrowser }, _this.state.state), { location: _this.locationState, deviceSize: _this.deviceSizeState, device: _this.device }), data.state), _this.props.data) }));
            }
            // TODO: also throttle on edits maybe
            if (data && data.jsCode && sdk_1.Builder.isBrowser) {
                // Don't rerun js code when editing and not changed
                var skip = false;
                if (sdk_1.Builder.isEditing) {
                    if (_this.lastJsCode === data.jsCode) {
                        skip = true;
                    }
                    else {
                        _this.lastJsCode = data.jsCode;
                    }
                }
                if (!skip) {
                    var state_1 = _this.state.state;
                    var getState_1 = function () { return _this.state.state; };
                    var getUpdate_1 = function () { return _this.state.update; };
                    // TODO: move to helper and deep wrap like immer in case people make references
                    // TODO: on set auto run in update if not already
                    if (typeof Proxy !== 'undefined') {
                        state_1 = new Proxy(__assign({}, state_1), {
                            getOwnPropertyDescriptor: function (target, property) {
                                try {
                                    return Reflect.getOwnPropertyDescriptor(getState_1(), property);
                                }
                                catch (error) {
                                    return undefined;
                                }
                            },
                            // TODO: wrap other proxy properties
                            // TODO: ensure batching updates
                            set: function (target, key, value) {
                                // TODO: do these for deep sets from references hmm
                                // TODO: throttle these updates
                                getUpdate_1()(function (state) {
                                    Reflect.set(state, key, value);
                                });
                                return true;
                                // return Reflect.set(getState(), key, value)
                                // return false;
                            },
                            // to prevent variable doesn't exist errors with `with (state)`
                            has: function (target, property) {
                                try {
                                    // TODO: if dead trigger an immer update
                                    return Reflect.has(getState_1(), property);
                                }
                                catch (error) {
                                    return false;
                                }
                            },
                            get: function (object, property) {
                                if (property &&
                                    typeof property === 'string' &&
                                    property.endsWith('Item') &&
                                    !Reflect.has(getState_1(), property)) {
                                    // TODO: use $index to return a reference to the proxied version of item
                                    // so can be set as well
                                    return Reflect.get(state_1, property);
                                }
                                return Reflect.get(getState_1(), property);
                            },
                        });
                    }
                    // TODO: real editing method
                    try {
                        var result_1 = new Function('data', 'ref', 'state', 'update', 'element', 'Builder', 'builder', data.jsCode)(data, _this, state_1, _this.state.update, _this.ref, sdk_1.Builder, sdk_1.builder);
                        // TODO: what if is promise...
                        if (result_1 && typeof result_1 === 'object' && Object.keys(result_1).length) {
                            _this.state.update(function (state) {
                                Object.assign(result_1, state);
                            });
                        }
                        if (result_1 && typeof result_1.then === 'function') {
                            result_1.then(function (val) {
                                if (val && typeof val === 'object' && Object.keys(val).length) {
                                    _this.state.update(function (state) {
                                        Object.assign(val, state);
                                    });
                                }
                            });
                        }
                        // TODO: allow exports = { } syntax?
                        // TODO: do something with reuslt like view - methods, computed, actions, properties, template, etc etc
                    }
                    catch (error) {
                        if (sdk_1.Builder.isBrowser) {
                            console.warn('Builder custom code error:', error.message, error.stack);
                        }
                        else {
                            console.debug('Builder custom code error:', error.message, error.stack);
                            // Add to req.options.errors to return to client
                        }
                    }
                }
            }
            if (data && data.httpRequests /* || data.builderData @DEPRECATED */ && !_this.props.noAsync) {
                // Don't rerun http requests when editing and not changed
                var skip = false;
                if (sdk_1.Builder.isEditing) {
                    var httpRequestsString = attempt_1.default(function () { return JSON.stringify(data.httpRequests); });
                    if (_this.lastHttpRequests === httpRequestsString) {
                        skip = true;
                    }
                    else if (!isError_1.default(httpRequestsString)) {
                        _this.lastHttpRequests = httpRequestsString;
                    }
                }
                if (!skip) {
                    var _loop_1 = function (key) {
                        var url = data.httpRequests[key];
                        if (url && !_this.data[key]) {
                            // TODO: if Builder.isEditing and url patches https://builder.io/api/v2/content/{editingModel}
                            // Then use builder.get().subscribe(...)
                            if (sdk_1.Builder.isBrowser) {
                                var lastUrl_1 = _this.evalExpression(url);
                                var builderModelRe = /builder\.io\/api\/v2\/([^\/\?]+)/i;
                                var builderModelMatch = url.match(builderModelRe);
                                var model = builderModelMatch && builderModelMatch[1];
                                if (sdk_1.Builder.isEditing && model && sdk_1.builder.editingModel === model) {
                                    _this.subscriptions.add(sdk_1.builder.get(model).subscribe(function (data) {
                                        _this.state.update(function (state) {
                                            state[key] = data;
                                        });
                                    }));
                                }
                                else {
                                    _this.throttledHandleRequest(key, lastUrl_1);
                                    _this.subscriptions.add(_this.onStateChange.subscribe(function () {
                                        var newUrl = _this.evalExpression(url);
                                        if (newUrl !== lastUrl_1) {
                                            _this.throttledHandleRequest(key, newUrl);
                                            lastUrl_1 = newUrl;
                                        }
                                    }));
                                }
                            }
                            else {
                                _this.handleRequest(key, _this.evalExpression(url));
                            }
                        }
                    };
                    // TODO: another structure for this
                    for (var key in data.httpRequests) {
                        _loop_1(key);
                    }
                    // @DEPRECATED
                    // for (const key in data.builderData) {
                    //   const url = data.builderData[key]
                    //   if (url && !this.data[key]) {
                    //     this.handleBuilderRequest(key, this.evalExpression(url))
                    //   }
                    // }
                }
            }
        };
        _this.state = {
            state: __assign({ location: _this.locationState, deviceSize: _this.deviceSizeState, 
                // TODO: will user attributes be ready here?
                device: _this.device }, _this.props.data),
            update: _this.updateState,
        };
        return _this;
    }
    Object.defineProperty(BuilderPage.prototype, "name", {
        get: function () {
            return this.props.modelName || this.props.name; // || this.props.model
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(BuilderPage.prototype, "device", {
        // TODO: pass down with context
        get: function () {
            return sdk_1.builder.getUserAttributes().device || 'desktop';
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(BuilderPage.prototype, "locationState", {
        get: function () {
            return __assign(__assign({}, pick_1.default(this.location, 'pathname', 'hostname', 'search', 'host')), { path: (this.location.pathname && this.location.pathname.split('/').slice(1)) || '', query: searchToObject(this.location) });
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(BuilderPage.prototype, "deviceSizeState", {
        // TODO: trigger state change on screen size change
        get: function () {
            // TODO: use context to pass this down on server
            return sdk_1.Builder.isBrowser
                ? device_sizes_constant_1.sizes.getSizeForWidth(window.innerWidth)
                : sizeMap[this.device] || 'large';
        },
        enumerable: true,
        configurable: true
    });
    // TODO: different options per device size...........................
    // static renderInto(
    //   elementOrSelector: string | HTMLElement,
    //   props: BuilderPageProps = {},
    //   hydrate = false
    // ) {
    //   const element =
    //     elementOrSelector instanceof HTMLElement
    //       ? elementOrSelector
    //       : document.querySelector(elementOrSelector)
    //   if (!element) {
    //     return
    //   }
    //   if (hydrate) {
    //     return ReactDOM.hydrate(<BuilderPage {...props} />, element)
    //   }
    //   return ReactDOM.render(<BuilderPage {...props} />, element)
    // }
    BuilderPage.prototype.componentWillMount = function () {
        var key = this.props.apiKey;
        if (key && key !== sdk_1.builder.apiKey) {
            sdk_1.builder.apiKey = key;
        }
        if (this.props.content) {
            // TODO: this should be on didMount right bc of element ref??
            // TODO: possibly observe for change or throw error if changes
            this.onContentLoaded(this.props.content.data /*, this.props.content*/);
        }
    };
    BuilderPage.prototype.componentDidMount = function () {
        var _this = this;
        if (sdk_1.Builder.isIframe) {
            parent.postMessage({ type: 'builder.sdkInjected', data: { modelName: this.name } }, '*');
        }
        if (sdk_1.Builder.isBrowser) {
            window.addEventListener('resize', this.resizeListener);
            setTimeout(function () {
                window.dispatchEvent(new CustomEvent('builder:component:load', {
                    detail: {
                        ref: _this,
                    },
                }));
            });
        }
        if (typeof this.state.state.componentDidMount === 'function') {
            try {
                this.state.state.componentDidMount();
            }
            catch (err) {
                console.error(err);
            }
        }
    };
    BuilderPage.prototype.notifyStateChange = function () {
        var nextState = this.state.state;
        // TODO: only run the below once per tick...
        if (this.props.onStateChange) {
            this.props.onStateChange(nextState);
        }
        if (sdk_1.Builder.isBrowser) {
            window.dispatchEvent(new CustomEvent('builder:component:stateChange', {
                detail: {
                    state: nextState,
                    ref: this,
                },
            }));
        }
        this.onStateChange.next(nextState);
    };
    BuilderPage.prototype.processStateFromApi = function (state) {
        return state; //  mapValues(state, value => tryEval(value, this.data, this._errors))
    };
    Object.defineProperty(BuilderPage.prototype, "location", {
        get: function () {
            return this.props.location || (sdk_1.Builder.isBrowser ? location : {});
        },
        enumerable: true,
        configurable: true
    });
    BuilderPage.prototype.getCssFromFont = function (font) {
        if (!font) {
            return '';
        }
        // TODO: compute what font sizes are used and only load those.......
        var family = font.family + (font.kind && !font.kind.includes('#') ? ', ' + font.kind : '');
        var name = family.split(',')[0];
        var url = font.fileUrl ? font.fileUrl : font.files && font.files.regular;
        var str = '';
        if (url && family && name) {
            str += ("\n@font-face {\n  font-family: " + family + ";\n  src: local(\"" + name + "\"), url('" + url + "') format('woff2');\n  font-display: swap;\n  font-weight: 400;\n}\n        ").trim();
        }
        if (font.files) {
            for (var weight in font.files) {
                // TODO: maybe limit number loaded
                var weightUrl = font.files[weight];
                if (weightUrl && weightUrl !== url) {
                    str += ("\n@font-face {\n  font-family: " + family + ";\n  src: url('" + weightUrl + "') format('woff2');\n  font-display: swap;\n  font-weight: " + weight + ";\n}\n          ").trim();
                }
            }
        }
        return str;
    };
    BuilderPage.prototype.componentWillUnmount = function () {
        this.unsubscribe();
        if (sdk_1.Builder.isBrowser) {
            window.removeEventListener('resize', this.resizeListener);
        }
    };
    BuilderPage.prototype.getFontCss = function (data) {
        var _this = this;
        // TODO: separate internal data from external
        return (data.customFonts &&
            data.customFonts.length &&
            data.customFonts.map(function (font) { return _this.getCssFromFont(font); }).join(' '));
    };
    BuilderPage.prototype.getCss = function (data) {
        // .replace(/([^\s]|$)&([^\w])/g, '$1' + '.some-selector' + '$2')
        return (data.cssCode || '') + (this.getFontCss(data) || '');
    };
    Object.defineProperty(BuilderPage.prototype, "data", {
        get: function () {
            return __assign(__assign({}, this.props.data), this.state.state);
        },
        enumerable: true,
        configurable: true
    });
    BuilderPage.prototype.componentDidUpdate = function (prevProps) {
        var _this = this;
        // TODO: shallow diff
        if (this.props.data && prevProps.data !== this.props.data) {
            this.state.update(function (state) {
                Object.assign(state, _this.props.data);
            });
        }
        if (sdk_1.Builder.isEditing) {
            if (this.props.content && prevProps.content !== this.props.content) {
                this.onContentLoaded(this.props.content);
            }
        }
    };
    BuilderPage.prototype.render = function () {
        var _this = this;
        var content = this.props.content;
        return (
        // TODO: data attributes for model, id, etc?
        react_1.default.createElement(react_native_1.View, { "data-name": this.name },
            react_1.default.createElement(builder_async_requests_1.BuilderAsyncRequestsContext.Consumer, null, function (value) {
                _this._asyncRequests = value && value.requests;
                _this._errors = value && value.errors;
                _this._logs = value && value.logs;
                return (react_1.default.createElement(builder_store_1.BuilderStoreContext.Provider, { value: __assign(__assign({}, _this.state), { state: _this.data }) },
                    react_1.default.createElement(builder_content_component_1.BuilderContent, { inline: _this.props.inlineContent, 
                        // TODO: pass entry in
                        contentLoaded: _this.onContentLoaded, options: __assign(__assign({ entry: _this.props.entry, key: sdk_1.Builder.isEditing ? _this.name : _this.props.entry }, (content && size_1.default(content) && { initialContent: [content] })), _this.props.options), contentError: _this.props.contentError, modelName: _this.name || 'page' }, function (data, loading, fullData) {
                        // TODO: loading option - maybe that is what the children is or component prop
                        return data ? (react_1.default.createElement(react_native_1.View, { "data-builder-component": _this.name, "data-builder-content-id": fullData.id, "data-builder-variation-id": fullData.variationId },
                            react_1.default.createElement(builder_blocks_component_1.BuilderBlocks, { emailMode: _this.props.emailMode, fieldName: "blocks", blocks: data.blocks }))) : loading ? (react_1.default.createElement(react_native_1.View, { "data-builder-component": _this.name }, _this.props.children)) : (react_1.default.createElement(react_native_1.View, { "data-builder-component": _this.name }));
                    })));
            })));
    };
    BuilderPage.prototype.evalExpression = function (expression) {
        var _this = this;
        var data = this.data;
        return expression.replace(/{{([^}]+)}}/g, function (match, group) { return tryEval(group, data, _this._errors); });
    };
    // TODO: customizable hm
    BuilderPage.prototype.throttledHandleRequest = function (propertyName, url) {
        return this.handleRequest(propertyName, url);
    };
    BuilderPage.prototype.handleRequest = function (propertyName, url) {
        return __awaiter(this, void 0, void 0, function () {
            var request, existing, promise_1, promise;
            var _this = this;
            return __generator(this, function (_a) {
                // TODO: Builder.isEditing = just checks if iframe and parent page is builder.io or localhost:1234
                if (sdk_1.Builder.isIframe && fetchCache[url]) {
                    this.updateState(function (ctx) {
                        ctx[propertyName] = fetchCache[url];
                    });
                    return [2 /*return*/, fetchCache[url]];
                }
                request = function () { return __awaiter(_this, void 0, void 0, function () {
                    var requestStart, json, result, err_1;
                    return __generator(this, function (_a) {
                        switch (_a.label) {
                            case 0:
                                requestStart = Date.now();
                                if (!sdk_1.Builder.isBrowser) {
                                    console.time('Fetch ' + url);
                                }
                                _a.label = 1;
                            case 1:
                                _a.trys.push([1, 4, 5, 6]);
                                return [4 /*yield*/, fetch(url)];
                            case 2:
                                result = _a.sent();
                                return [4 /*yield*/, result.json()];
                            case 3:
                                json = _a.sent();
                                return [3 /*break*/, 6];
                            case 4:
                                err_1 = _a.sent();
                                if (this._errors) {
                                    this._errors.push(err_1);
                                }
                                if (this._logs) {
                                    this._logs.push("Fetch to " + url + " errored in " + (Date.now() - requestStart) + "ms");
                                }
                                return [2 /*return*/];
                            case 5:
                                if (!sdk_1.Builder.isBrowser) {
                                    console.timeEnd('Fetch ' + url);
                                    if (this._logs) {
                                        this._logs.push("Fetched " + url + " in " + (Date.now() - requestStart) + "ms");
                                    }
                                }
                                return [7 /*endfinally*/];
                            case 6:
                                if (json) {
                                    if (sdk_1.Builder.isIframe) {
                                        fetchCache[url] = json;
                                    }
                                    // TODO: debounce next tick all of these when there are a bunch
                                    this.updateState(function (ctx) {
                                        ctx[propertyName] = json;
                                    });
                                }
                                return [2 /*return*/, json];
                        }
                    });
                }); };
                existing = this._asyncRequests &&
                    this._asyncRequests.find(function (req) { return builder_async_requests_1.isRequestInfo(req) && req.url === url; });
                if (existing) {
                    promise_1 = existing.promise;
                    promise_1.then(function (json) {
                        if (json) {
                            _this.updateState(function (ctx) {
                                ctx[propertyName] = json;
                            });
                        }
                    });
                    return [2 /*return*/, promise_1];
                }
                promise = request();
                sdk_1.Builder.nextTick(function () {
                    if (_this._asyncRequests) {
                        _this._asyncRequests.push(promise);
                    }
                });
                return [2 /*return*/, promise];
            });
        });
    };
    BuilderPage.prototype.unsubscribe = function () {
        if (this.subscriptions) {
            this.subscriptions.unsubscribe();
            this.subscriptions = new sdk_1.Subscription();
        }
    };
    BuilderPage.prototype.handleBuilderRequest = function (propertyName, optionsString) {
        var _this = this;
        var options = tryEval(optionsString, this.data, this._errors);
        // TODO: this will screw up for multiple bits of data
        if (this.subscriptions) {
            this.unsubscribe();
        }
        // TODO: don't unsubscribe and resubscribe every time data changes, will make a TON of requests if that's the case when editing...
        // I guess will be cached then
        if (options) {
            // TODO: unsubscribe on destroy
            this.subscriptions.add(sdk_1.builder.queueGetContent(options.model, options).subscribe(function (matches) {
                if (matches) {
                    _this.updateState(function (ctx) {
                        ctx[propertyName] = matches;
                    });
                }
            }));
        }
    };
    __decorate([
        debonce_next_tick_1.debounceNextTick,
        __metadata("design:type", Function),
        __metadata("design:paramtypes", []),
        __metadata("design:returntype", void 0)
    ], BuilderPage.prototype, "notifyStateChange", null);
    __decorate([
        Throttle(100, { leading: true, trailing: true }),
        __metadata("design:type", Function),
        __metadata("design:paramtypes", [String, String]),
        __metadata("design:returntype", void 0)
    ], BuilderPage.prototype, "throttledHandleRequest", null);
    return BuilderPage;
}(react_1.default.Component));
exports.BuilderPage = BuilderPage;
//# sourceMappingURL=builder-page.component.js.map