import { __assign } from "tslib";
import * as React from 'react';
import { observable, autorun, untracked } from 'mobx';
import { remove } from 'lodash-es';
import { EventProxy } from './eventProxy';
import { runtime } from './adapter';
export var widgetKeys = [
    'getConfig',
    'findWidgets',
    'getWidgetsByType',
    'extends',
    'addWidgetsListener',
    'removeWidgetsListener',
    'getOwnerWidget',
    'getDom',
    'domRef',
];
export var WidgetsContext = React.createContext({ parent: null });
export function checkVisible(_a) {
    var _visible = _a._visible;
    return _visible !== false && _visible !== '';
}
export function getDom(element, options) {
    if (!element) {
        console.warn('getDom 接口未传入有效的 element');
        return Promise.resolve({});
    }
    var result = {};
    if (options.id)
        result.id = element.id;
    if (options.dataset)
        result.dataset = element.dataset;
    if (options.rect) {
        var rect = element.getBoundingClientRect();
        Object.assign(result, {
            left: rect.left,
            right: rect.right,
            top: rect.top,
            bottom: rect.bottom,
        });
    }
    if (options.size) {
        var rect = element.getBoundingClientRect();
        Object.assign(result, {
            width: rect.width,
            height: rect.height,
        });
    }
    if (options.scrollOffset) {
        Object.assign(result, {
            scrollLeft: element.scrollLeft,
            scrollTop: element.scrollTop,
        });
    }
    if (options.properties && Array.isArray(options.properties)) {
        options.properties.forEach(function (propName) {
            result[propName] = element.getAttribute(propName);
        });
    }
    if (options.computedStyle && Array.isArray(options.computedStyle)) {
        var computedStyle_1 = runtime.getComputedStyle(element);
        options.computedStyle.forEach(function (propName) {
            result[propName] = computedStyle_1[propName];
        });
    }
    return Promise.resolve(result);
}
export function findWidgets(widget, parentType, filterFn, includeInvisibleDescendants) {
    if (!widget)
        return [];
    var _a = widget.children, children = _a === void 0 ? [] : _a;
    var matched = children.filter(filterFn);
    // 过滤掉不可见后代
    if (!includeInvisibleDescendants) {
        children = children.filter(function (item) { return checkVisible(item); });
    }
    children.forEach(function (child) {
        // 如果递归过程中发现了自己，则停止递归
        matched = matched.concat(findWidgets(child, parentType, filterFn, includeInvisibleDescendants));
    });
    // 过滤掉不可见的匹配项
    if (!includeInvisibleDescendants) {
        matched = matched.filter(function (item) { return checkVisible(item); });
    }
    return matched;
}
var retryQueue = [];
// 递归执行
export function retryDataBinds(tryTime) {
    if (tryTime === void 0) { tryTime = 10; }
    var fn = retryQueue.shift();
    if (!fn || tryTime <= 0)
        return;
    try {
        fn({ silent: tryTime > 1 });
    }
    catch (e) {
        console.error('retryDataBinds', e);
    }
    retryDataBinds(tryTime - 1);
}
export function createWidgets(widgetProps, dataBinds, context) {
    var nodeTree = createWidgetTree(widgetProps, dataBinds);
    var widgets = runFor(nodeTree, {}, null, null, context);
    return widgets;
    /**
     *
     * @param {*} curForNode
     * @param {*} forItems
     * @param {*} parentLevelWidgets
     * @param {*} parentWidget
     * @returns top level widgets or for dispose
     */
    function runFor(curForNode, forItems, parentLevelWidgets, parentWidget, context) {
        var nodeId = curForNode.id;
        if (!curForNode.value) {
            return createSubTree(curForNode, {}, context);
        }
        var dispose = autorun(function () {
            var forList = [];
            try {
                forList = dataBinds[nodeId]._waFor(forItems, undefined, context);
            }
            catch (e) {
                console.error('waFor error', e);
            }
            if (!Array.isArray(forList)) {
                console.warn(nodeId, 'For 循环绑定的数据并不是数组，请检查');
                forList = [];
            }
            // 让 forList 进行监听
            forList.forEach(function () { });
            untracked(function () {
                disposeWidgets(parentLevelWidgets[curForNode.id]);
                // clean nodes of previouse for run
                dfsTree(curForNode, function (node) {
                    var arr = parentLevelWidgets[node.id];
                    arr.splice(0, arr.length);
                    parentWidget &&
                        remove(parentWidget.children, function (_a) {
                            var id = _a.id;
                            return id === node.id;
                        });
                }, undefined);
                forList.forEach(function (item, index) {
                    var _a;
                    var subForItems = __assign(__assign({}, forItems), (_a = {}, _a[nodeId] = item, _a));
                    createSubTree(curForNode, subForItems, context);
                });
                // 非初始化时遇到需要重新构建 dataBinds
                retryDataBinds();
            });
        });
        return dispose;
        function createSubTree(curForNode, subForItems, context) {
            var widgets = {};
            // traverse down the tree to set all widgets
            dfsTree(curForNode, function (node, parentNode) {
                if (node.forCount === curForNode.forCount) {
                    // Leaf node
                    var w_1 = observable(widgetProps[node.id]);
                    w_1.id = node.id;
                    if (node === curForNode) {
                        w_1._disposers = [];
                    }
                    widgets[node.id] = w_1;
                    w_1._listeners = new EventProxy();
                    // 提供一个给 Node 挂载 API 的方式
                    untracked(function () {
                        if (!w_1.getConfig) {
                            // 兼容非复合组件调用 getConfig 能力
                            w_1.getConfig = function () { return ({}); };
                        }
                        w_1.findWidgets = function (type, includeInvisibleDescendants) {
                            return findWidgets(w_1, w_1.widgetType, type, includeInvisibleDescendants);
                        };
                        w_1.getWidgetsByType = function (type, includeInvisibleDescendants) {
                            return w_1.findWidgets(function (currentWidget) { return currentWidget.widgetType === type; }, includeInvisibleDescendants);
                        };
                        w_1.extends = function (name, fnOrData) {
                            return Object.defineProperty(w_1, name, {
                                value: fnOrData,
                                writable: true,
                            });
                        };
                        w_1.addWidgetsListener = function (name, listener) {
                            return w_1._listeners.on(name, listener);
                        };
                        w_1.removeWidgetsListener = function (name, listener) {
                            return w_1._listeners.cancel(name, listener);
                        };
                        w_1.getOwnerWidget = function () { return null; }; // 默认带上一个 widget
                    });
                    w_1.children = [];
                    var parent_1 = parentNode ? widgets[parentNode.id] : parentWidget;
                    if (parent_1) {
                        w_1.parent = parent_1;
                        // 只有可显示 visible 的才存入 children 里
                        if (checkVisible(w_1)) {
                            parent_1.children.push(w_1);
                        }
                    }
                    parentLevelWidgets && parentLevelWidgets[node.id].push(w_1);
                    // Setup data binds
                    Object.keys(dataBinds[node.id] || {}).map(function (prop) {
                        if (prop === '_waFor') {
                            return;
                        }
                        function getBindData(options) {
                            if (options === void 0) { options = { silent: false }; }
                            var disposeError = false;
                            var dispose = autorun(function () {
                                try {
                                    // Computed data bind in the next tick since data bind may read widgets data
                                    w_1[prop] = dataBinds[node.id][prop](subForItems, undefined, context);
                                    disposeError = false;
                                }
                                catch (e) {
                                    if (prop === '_visible') {
                                        w_1[prop] = false;
                                    }
                                    else {
                                        retryQueue.push(getBindData);
                                        disposeError = true;
                                    }
                                    if (!(options === null || options === void 0 ? void 0 : options.silent)) {
                                        console.warn("Error computing data bind " + w_1.id + "." + prop, e);
                                    }
                                }
                            });
                            if (!disposeError && curForNode.id) {
                                widgets[curForNode.id]._disposers.push(dispose);
                            }
                        }
                        getBindData();
                    });
                }
                else {
                    if (parentLevelWidgets) {
                        var len = parentLevelWidgets[node.id].push([]);
                        widgets[node.id] = parentLevelWidgets[node.id][len - 1];
                    }
                    else {
                        widgets[node.id] = observable([]);
                    }
                }
            }, undefined);
            // run for of next level
            dfsTree(curForNode, function (node, parentNode) {
                if (node.forCount === curForNode.forCount + 1 &&
                    dataBinds[node.id] &&
                    dataBinds[node.id]._waFor) {
                    widgets[node.id]._disposers = { dataBinds: [] };
                    var dispose_1 = runFor(node, subForItems, widgets, node.parent && widgets[node.parent.id], context);
                    curForNode.id && widgets[curForNode.id]._disposers.push(dispose_1);
                }
            }, undefined);
            return widgets;
        }
    }
}
/**
 * Add parent, children to widget
 */
function createWidgetTree(widgets, dataBinds) {
    var virtualRoot = { children: [], forCount: 0 };
    var nodes = Object.keys(widgets).reduce(function (result, id) {
        result[id] = {
            id: id,
            value: widgets[id],
            children: [],
            parent: null,
            forCount: 0,
        };
        return result;
    }, {});
    // Create widgets tree API
    Object.keys(nodes).map(function (id) {
        var curNode = nodes[id];
        var parent = nodes[widgets[id]._parentId];
        if (!parent) {
            virtualRoot.children.push(curNode);
            return;
        }
        curNode.parent = parent;
        parent.children.push(curNode);
    });
    virtualRoot.children.map(addForCount);
    // dfs, add forCount
    function addForCount(node) {
        if (node.parent) {
            node.forCount = node.parent.forCount;
        }
        if (dataBinds[node.id] && dataBinds[node.id]._waFor) {
            node.forCount++;
        }
        node.children.map(addForCount);
    }
    return virtualRoot;
}
function dfsTree(node, fn, parent) {
    node.value && fn(node, parent);
    node.children.map(function (e) { return dfsTree(e, fn, node.value ? node : null); });
}
// dispose autorun
function disposeWidgets(widgets) {
    if (widgets === void 0) { widgets = []; }
    widgets.forEach(function (widget) {
        var disposers = widget._disposers;
        if (disposers) {
            disposers.map(function (dispose) { return dispose(); });
            disposers.splice(0, disposers.length);
        }
        disposeWidgets(widget.children);
    });
}
//# sourceMappingURL=widgets.js.map