(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", "./types"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.AtomBinder = void 0;
    const types_1 = require("./types");
    class AtomBinder {
        static refreshValue(target, key) {
            const handlers = AtomBinder.get_WatchHandler(target, key);
            if (handlers === undefined || handlers == null) {
                return;
            }
            for (const item of handlers) {
                item(target, key);
            }
            if (target.onPropertyChanged) {
                target.onPropertyChanged(key);
            }
        }
        static add_WatchHandler(target, key, handler) {
            if (target == null) {
                return;
            }
            const handlers = AtomBinder.get_WatchHandler(target, key);
            handlers.push(handler);
            if (Array.isArray(target)) {
                return;
            }
            // get existing property definition if it ha any
            const pv = AtomBinder.getPropertyDescriptor(target, key);
            // return if it has a getter
            // in case of getter/setter, it is responsibility of setter to refresh
            // object
            if (pv && pv.get) {
                return;
            }
            const tw = target;
            if (!tw._$_bindable) {
                tw._$_bindable = {};
            }
            if (!tw._$_bindable[key]) {
                tw._$_bindable[key] = 1;
                const o = target[key];
                const nk = `_$_${key}`;
                target[nk] = o;
                const set = function (v) {
                    const ov = this[nk];
                    // tslint:disable-next-line:triple-equals
                    if (ov === undefined ? ov === v : ov == v) {
                        return;
                    }
                    this[nk] = v;
                    AtomBinder.refreshValue(this, key);
                };
                const get = function () {
                    return this[nk];
                };
                if (pv) {
                    delete target[key];
                    Object.defineProperty(target, key, {
                        get,
                        set,
                        configurable: true,
                        enumerable: true
                    });
                }
                else {
                    Object.defineProperty(target, key, {
                        get, set, enumerable: true, configurable: true
                    });
                }
            }
        }
        static getPropertyDescriptor(target, key) {
            const pv = Object.getOwnPropertyDescriptor(target, key);
            if (!pv) {
                const pt = Object.getPrototypeOf(target);
                if (pt) {
                    return AtomBinder.getPropertyDescriptor(pt, key);
                }
            }
            return pv;
        }
        static get_WatchHandler(target, key) {
            if (target == null) {
                return null;
            }
            let handlers = target._$_handlers;
            if (!handlers) {
                handlers = {};
                target._$_handlers = handlers;
            }
            let handlersForKey = handlers[key];
            if (handlersForKey === undefined || handlersForKey == null) {
                handlersForKey = [];
                handlers[key] = handlersForKey;
            }
            return handlersForKey;
        }
        static remove_WatchHandler(target, key, handler) {
            if (target == null) {
                return;
            }
            if (!target._$_handlers) {
                return;
            }
            const handlersForKey = target._$_handlers[key];
            if (handlersForKey === undefined || handlersForKey == null) {
                return;
            }
            // handlersForKey = handlersForKey.filter( (f) => f !== handler);
            types_1.ArrayHelper.remove(handlersForKey, (f) => f === handler);
            if (!handlersForKey.length) {
                target._$_handlers[key] = null;
                delete target._$_handlers[key];
            }
        }
        static invokeItemsEvent(target, mode, index, item) {
            const key = "_items";
            const handlers = AtomBinder.get_WatchHandler(target, key);
            if (!handlers) {
                return;
            }
            for (const obj of handlers) {
                obj(target, mode, index, item);
            }
            AtomBinder.refreshValue(target, "length");
        }
        static refreshItems(ary) {
            AtomBinder.invokeItemsEvent(ary, "refresh", -1, null);
        }
        static add_CollectionChanged(target, handler) {
            if (target == null) {
                throw new Error("Target Array to watch cannot be null");
            }
            if (handler == null) {
                throw new Error("Target handle to watch an Array cannot be null");
            }
            const handlers = AtomBinder.get_WatchHandler(target, "_items");
            handlers.push(handler);
            return { dispose: () => {
                    AtomBinder.remove_CollectionChanged(target, handler);
                }
            };
        }
        static remove_CollectionChanged(t, handler) {
            if (t == null) {
                return;
            }
            const target = t;
            if (!target._$_handlers) {
                return;
            }
            const key = "_items";
            const handlersForKey = target._$_handlers[key];
            if (handlersForKey === undefined || handlersForKey == null) {
                return;
            }
            types_1.ArrayHelper.remove(handlersForKey, (f) => f === handler);
            if (!handlersForKey.length) {
                target._$_handlers[key] = null;
                delete target._$_handlers[key];
            }
        }
        static watch(item, property, f) {
            AtomBinder.add_WatchHandler(item, property, f);
            return {
                dispose: () => {
                    AtomBinder.remove_WatchHandler(item, property, f);
                }
            };
        }
        static clear(a) {
            a.length = 0;
            this.invokeItemsEvent(a, "refresh", -1, null);
            AtomBinder.refreshValue(a, "length");
        }
        static addItem(a, item) {
            const index = a.length;
            a.push(item);
            this.invokeItemsEvent(a, "add", index, item);
            AtomBinder.refreshValue(a, "length");
        }
        static removeItem(a, item) {
            const i = a.findIndex((x) => x === item);
            if (i === -1) {
                return false;
            }
            a.splice(i, 1);
            AtomBinder.invokeItemsEvent(a, "remove", i, item);
            AtomBinder.refreshValue(a, "length");
            return true;
        }
    }
    exports.AtomBinder = AtomBinder;
});
//# sourceMappingURL=AtomBinder.js.map