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 __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "../App", "../core/AtomBridge", "../core/PropertyBinding", "../core/types", "../di/Inject", "./AtomDisposableList", "./Bind", "./PropertyMap", "./XNode"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.AtomComponent = void 0;
    var App_1 = require("../App");
    var AtomBridge_1 = require("../core/AtomBridge");
    var PropertyBinding_1 = require("../core/PropertyBinding");
    // tslint:disable-next-line:import-spacing
    var types_1 = require("../core/types");
    var Inject_1 = require("../di/Inject");
    var AtomDisposableList_1 = require("./AtomDisposableList");
    var Bind_1 = require("./Bind");
    var PropertyMap_1 = require("./PropertyMap");
    var XNode_1 = require("./XNode");
    var AtomComponent = /** @class */ (function () {
        function AtomComponent(app, element) {
            if (element === void 0) { element = null; }
            this.app = app;
            this.mInvalidated = 0;
            this.mPendingPromises = {};
            this.mData = undefined;
            this.mViewModel = undefined;
            this.mLocalViewModel = undefined;
            this.disposables = new AtomDisposableList_1.AtomDisposableList();
            this.bindings = [];
            this.eventHandlers = [];
            this.element = element;
            AtomBridge_1.AtomBridge.instance.attachControl(this.element, this);
            var a = this.beginEdit();
            this.preCreate();
            this.create();
            app.callLater(function () { return a.dispose(); });
        }
        Object.defineProperty(AtomComponent.prototype, "data", {
            get: function () {
                if (this.mData !== undefined) {
                    return this.mData;
                }
                var parent = this.parent;
                if (parent) {
                    return parent.data;
                }
                return undefined;
            },
            set: function (v) {
                this.mData = v;
                AtomBridge_1.AtomBridge.refreshInherited(this, "data");
            },
            enumerable: false,
            configurable: true
        });
        Object.defineProperty(AtomComponent.prototype, "viewModel", {
            get: function () {
                if (this.mViewModel !== undefined) {
                    return this.mViewModel;
                }
                var parent = this.parent;
                if (parent) {
                    return parent.viewModel;
                }
                return undefined;
            },
            set: function (v) {
                var old = this.mViewModel;
                if (old && old.dispose) {
                    old.dispose();
                }
                this.mViewModel = v;
                AtomBridge_1.AtomBridge.refreshInherited(this, "viewModel");
            },
            enumerable: false,
            configurable: true
        });
        Object.defineProperty(AtomComponent.prototype, "localViewModel", {
            get: function () {
                if (this.mLocalViewModel !== undefined) {
                    return this.mLocalViewModel;
                }
                var parent = this.parent;
                if (parent) {
                    return parent.localViewModel;
                }
                return undefined;
            },
            set: function (v) {
                var old = this.mLocalViewModel;
                if (old && old.dispose) {
                    old.dispose();
                }
                this.mLocalViewModel = v;
                AtomBridge_1.AtomBridge.refreshInherited(this, "localViewModel");
            },
            enumerable: false,
            configurable: true
        });
        Object.defineProperty(AtomComponent.prototype, "vsProps", {
            /** Do not ever use, only available as intellisense feature for
             * vs code editor.
             */
            get: function () {
                return undefined;
            },
            enumerable: false,
            configurable: true
        });
        AtomComponent.prototype.bind = function (element, name, path, twoWays, valueFunc, source) {
            var _this = this;
            // remove existing binding if any
            var binding = this.bindings.find(function (x) { return x.name === name && (element ? x.element === element : true); });
            if (binding) {
                binding.dispose();
                types_1.ArrayHelper.remove(this.bindings, function (x) { return x === binding; });
            }
            binding = new PropertyBinding_1.PropertyBinding(this, element, name, path, twoWays, valueFunc, source);
            this.bindings.push(binding);
            return {
                dispose: function () {
                    binding.dispose();
                    types_1.ArrayHelper.remove(_this.bindings, function (x) { return x === binding; });
                }
            };
        };
        /**
         * Remove all bindings associated with given element and optional name
         * @param element T
         * @param name string
         */
        AtomComponent.prototype.unbind = function (element, name) {
            var toDelete = this.bindings.filter(function (x) { return x.element === element && (!name || (x.name === name)); });
            var _loop_1 = function (iterator) {
                iterator.dispose();
                types_1.ArrayHelper.remove(this_1.bindings, function (x) { return x === iterator; });
            };
            var this_1 = this;
            for (var _i = 0, toDelete_1 = toDelete; _i < toDelete_1.length; _i++) {
                var iterator = toDelete_1[_i];
                _loop_1(iterator);
            }
        };
        AtomComponent.prototype.bindEvent = function (element, name, method, key) {
            var _this = this;
            if (!element) {
                return;
            }
            if (!method) {
                return;
            }
            var be = {
                element: element,
                name: name,
                handler: method
            };
            if (key) {
                be.key = key;
            }
            be.disposable = AtomBridge_1.AtomBridge.instance.addEventHandler(element, name, method, false);
            this.eventHandlers.push(be);
            return {
                dispose: function () {
                    be.disposable.dispose();
                    types_1.ArrayHelper.remove(_this.eventHandlers, function (e) { return e.disposable === be.disposable; });
                }
            };
        };
        AtomComponent.prototype.unbindEvent = function (element, name, method, key) {
            var _this = this;
            var deleted = [];
            var _loop_2 = function (be) {
                if (element && be.element !== element) {
                    return { value: void 0 };
                }
                if (key && be.key !== key) {
                    return { value: void 0 };
                }
                if (name && be.name !== name) {
                    return { value: void 0 };
                }
                if (method && be.handler !== method) {
                    return { value: void 0 };
                }
                be.disposable.dispose();
                be.handler = null;
                be.element = null;
                be.name = null;
                be.key = null;
                deleted.push(function () { return _this.eventHandlers.remove(be); });
            };
            for (var _i = 0, _a = this.eventHandlers; _i < _a.length; _i++) {
                var be = _a[_i];
                var state_1 = _loop_2(be);
                if (typeof state_1 === "object")
                    return state_1.value;
            }
            for (var _b = 0, deleted_1 = deleted; _b < deleted_1.length; _b++) {
                var iterator = deleted_1[_b];
                iterator();
            }
        };
        /**
         * Control checks if property is declared on the control or not.
         * Since TypeScript no longer creates enumerable properties, we have
         * to inspect name and PropertyMap which is generated by `@BindableProperty`
         * or the value is not set to undefined.
         * @param name name of Property
         */
        AtomComponent.prototype.hasProperty = function (name) {
            if (/^(data|viewModel|localViewModel|element)$/.test(name)) {
                return true;
            }
            var map = PropertyMap_1.PropertyMap.from(this);
            if (map.map[name]) {
                return true;
            }
            if (this[name] !== undefined) {
                return true;
            }
            return false;
        };
        /**
         * Use this method if you want to set attribute on HTMLElement immediately but
         * defer atom control property
         * @param element HTMLElement
         * @param name string
         * @param value any
         */
        AtomComponent.prototype.setPrimitiveValue = function (element, name, value) {
            var _this = this;
            var p = value;
            if (p && p.then && p.catch) {
                this.mPendingPromises[name] = p;
                p.then(function (r) {
                    if (_this.mPendingPromises[name] !== p) {
                        return;
                    }
                    _this.mPendingPromises[name] = null;
                    _this.setPrimitiveValue(element, name, r);
                }).catch(function (e) {
                    if (_this.mPendingPromises[name] !== p) {
                        return;
                    }
                    _this.mPendingPromises[name] = null;
                    // tslint:disable-next-line:no-console
                    console.error(e);
                });
                return;
            }
            if (/^(viewModel|localViewModel)$/.test(name)) {
                this[name] = value;
                return;
            }
            if ((!element || element === this.element) && this.hasProperty(name)) {
                this.runAfterInit(function () {
                    _this[name] = value;
                });
            }
            else {
                this.setElementValue(element, name, value);
            }
        };
        AtomComponent.prototype.setLocalValue = function (element, name, value) {
            var _this = this;
            // if value is a promise
            var p = value;
            if (p && p.then && p.catch) {
                this.mPendingPromises[name] = p;
                p.then(function (r) {
                    if (_this.mPendingPromises[name] !== p) {
                        return;
                    }
                    _this.mPendingPromises[name] = null;
                    _this.setLocalValue(element, name, r);
                }).catch(function (e) {
                    if (_this.mPendingPromises[name] !== p) {
                        return;
                    }
                    _this.mPendingPromises[name] = null;
                    // tslint:disable-next-line:no-console
                    console.error(e);
                });
                return;
            }
            if ((!element || element === this.element) && this.hasProperty(name)) {
                this[name] = value;
            }
            else {
                this.setElementValue(element, name, value);
            }
        };
        AtomComponent.prototype.dispose = function (e) {
            if (this.mInvalidated) {
                clearTimeout(this.mInvalidated);
                this.mInvalidated = 0;
            }
            AtomBridge_1.AtomBridge.instance.visitDescendents(e || this.element, function (ex, ac) {
                if (ac) {
                    ac.dispose();
                    return false;
                }
                return true;
            });
            if (!e) {
                this.unbindEvent(null, null, null);
                for (var _i = 0, _a = this.bindings; _i < _a.length; _i++) {
                    var binding = _a[_i];
                    binding.dispose();
                }
                this.bindings.length = 0;
                this.bindings = null;
                AtomBridge_1.AtomBridge.instance.dispose(this.element);
                this.element = null;
                var lvm = this.mLocalViewModel;
                if (lvm && lvm.dispose) {
                    lvm.dispose();
                    this.mLocalViewModel = null;
                }
                var vm = this.mViewModel;
                if (vm && vm.dispose) {
                    vm.dispose();
                    this.mViewModel = null;
                }
                this.disposables.dispose();
                this.pendingInits = null;
            }
        };
        // tslint:disable-next-line:no-empty
        AtomComponent.prototype.onPropertyChanged = function (name) {
        };
        AtomComponent.prototype.beginEdit = function () {
            var _this = this;
            this.pendingInits = [];
            var a = this.pendingInits;
            return {
                dispose: function () {
                    if (_this.pendingInits == null) {
                        // case where current control is disposed...
                        return;
                    }
                    _this.pendingInits = null;
                    if (a) {
                        for (var _i = 0, a_1 = a; _i < a_1.length; _i++) {
                            var iterator = a_1[_i];
                            iterator();
                        }
                    }
                    _this.invalidate();
                }
            };
        };
        AtomComponent.prototype.invalidate = function () {
            var _this = this;
            if (this.mInvalidated) {
                clearTimeout(this.mInvalidated);
            }
            this.mInvalidated = setTimeout(function () {
                _this.mInvalidated = 0;
                _this.app.callLater(function () {
                    _this.onUpdateUI();
                });
            }, 5);
        };
        AtomComponent.prototype.onUpdateUI = function () {
            // for implementors..
        };
        AtomComponent.prototype.runAfterInit = function (f) {
            if (this.pendingInits) {
                this.pendingInits.push(f);
            }
            else {
                f();
            }
        };
        AtomComponent.prototype.registerDisposable = function (d) {
            return this.disposables.add(d);
        };
        AtomComponent.prototype.render = function (node, e, creator) {
            creator = creator || this;
            var bridge = AtomBridge_1.AtomBridge.instance;
            var app = this.app;
            var renderFirst = AtomBridge_1.AtomBridge.platform === "xf";
            e = e || this.element;
            var attr = node.attributes;
            if (attr) {
                for (var key in attr) {
                    if (attr.hasOwnProperty(key)) {
                        var item = attr[key];
                        if (item instanceof Bind_1.default) {
                            item.setupFunction(key, item, this, e, creator);
                        }
                        else if (item instanceof XNode_1.default) {
                            // this is template..
                            if (item.isTemplate) {
                                this.setLocalValue(e, key, AtomBridge_1.AtomBridge.toTemplate(app, item, creator));
                            }
                            else {
                                var child = AtomBridge_1.AtomBridge.createNode(item, app);
                                this.setLocalValue(e, key, child.element);
                            }
                        }
                        else {
                            this.setLocalValue(e, key, item);
                        }
                    }
                }
            }
            for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
                var iterator = _a[_i];
                if (typeof iterator === "string") {
                    e.appendChild(document.createTextNode(iterator));
                    continue;
                }
                if (iterator.isTemplate) {
                    if (iterator.isProperty) {
                        this.setLocalValue(e, iterator.name, AtomBridge_1.AtomBridge.toTemplate(app, iterator.children[0], creator));
                    }
                    else {
                        e.appendChild(AtomBridge_1.AtomBridge.toTemplate(app, iterator, creator));
                    }
                    continue;
                }
                if (iterator.isProperty) {
                    for (var _b = 0, _c = iterator.children; _b < _c.length; _b++) {
                        var child = _c[_b];
                        var pc = AtomBridge_1.AtomBridge.createNode(child, app);
                        (pc.control || this).render(child, pc.element, creator);
                        // in Xamarin.Forms certain properties are required to be
                        // set in advance, so we append the element after setting
                        // all children properties
                        bridge.append(e, iterator.name, pc.element);
                    }
                    continue;
                }
                var t = iterator.attributes && iterator.attributes.template;
                if (t) {
                    this.setLocalValue(e, t, AtomBridge_1.AtomBridge.toTemplate(app, iterator, creator));
                    continue;
                }
                var c = AtomBridge_1.AtomBridge.createNode(iterator, app);
                if (renderFirst) {
                    (c.control || this).render(iterator, c.element, creator);
                }
                if (this.element === e) {
                    this.append(c.control || c.element);
                }
                else {
                    e.appendChild(c.element);
                }
                if (!renderFirst) {
                    (c.control || this).render(iterator, c.element, creator);
                }
            }
        };
        // tslint:disable-next-line:no-empty
        AtomComponent.prototype.create = function () {
        };
        // tslint:disable-next-line:no-empty
        AtomComponent.prototype.preCreate = function () {
        };
        AtomComponent.prototype.setElementValue = function (element, name, value) {
            AtomBridge_1.AtomBridge.instance.setValue(element, name, value);
        };
        AtomComponent.prototype.resolve = function (c, selfName) {
            var result = this.app.resolve(c, true);
            if (selfName) {
                if (typeof selfName === "function") {
                    // this is required as parent is not available
                    // in items control so binding becomes difficult
                    this.runAfterInit(function () {
                        var v = selfName();
                        if (v) {
                            for (var key in v) {
                                if (v.hasOwnProperty(key)) {
                                    var element = v[key];
                                    result[key] = element;
                                }
                            }
                        }
                    });
                }
                else {
                    result[selfName] = this;
                }
            }
            return result;
        };
        AtomComponent.isControl = true;
        AtomComponent = __decorate([
            __param(0, Inject_1.Inject),
            __metadata("design:paramtypes", [App_1.App, Object])
        ], AtomComponent);
        return AtomComponent;
    }());
    exports.AtomComponent = AtomComponent;
});
//# sourceMappingURL=AtomComponent.js.map