"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createComponent = createComponent;
const tslib_1 = require("tslib");
const assert_1 = require("oak-domain/lib/utils/assert");
const react_1 = tslib_1.__importDefault(require("react"));
const toolkit_1 = require("oak-domain/lib/utils/toolkit");
const types_1 = require("oak-domain/lib/types");
const page_common_1 = require("./page.common");
const oakComponentSubscription_1 = require("./utils/oakComponentSubscription");
class OakComponentBase extends react_1.default.PureComponent {
    oakFullpath = '';
    featuresSubscribed = [];
    addFeatureSub(name, callback) {
        const unsubHandler = this.features[name].subscribe(callback);
        this.featuresSubscribed.push({
            name,
            callback,
            unsubHandler,
        });
    }
    removeFeatureSub(name, callback) {
        const f = this.featuresSubscribed.find(ele => ele.callback === callback && ele.name === name);
        (0, toolkit_1.pull)(this.featuresSubscribed, f);
        f.unsubHandler && f.unsubHandler();
    }
    unsubscribeAll() {
        this.featuresSubscribed.forEach(ele => {
            if (ele.unsubHandler) {
                ele.unsubHandler();
                ele.unsubHandler = undefined;
            }
        });
    }
    subscribeAll() {
        this.featuresSubscribed.forEach(ele => {
            if (!ele.unsubHandler) {
                ele.unsubHandler = this.features[ele.name].subscribe(ele.callback);
            }
        });
    }
    save(key, item) {
        return this.features.localStorage.save(key, item);
    }
    load(key) {
        return this.features.localStorage.load(key);
    }
    clear(key) {
        if (key) {
            return this.features.localStorage.remove(key);
        }
        return this.features.localStorage.clear();
    }
    setNotification(data) {
        this.features.notification.setNotification(data);
    }
    consumeNotification() {
        return this.features.notification.consumeNotification();
    }
    setMessage(data) {
        return this.features.message.setMessage(data);
    }
    consumeMessage() {
        return this.features.message.consumeMessage();
    }
    reRender(extra) {
        return page_common_1.reRender.call(this, this.oakOption, extra);
    }
    navigateTo(options, state, disableNamespace) {
        // 路由传入namespace
        return this.features.navigator.navigateTo(options, state, disableNamespace);
    }
    navigateBack(delta) {
        return this.features.navigator.navigateBack(delta);
    }
    redirectTo(options, state, disableNamespace) {
        return this.features.navigator.redirectTo(options, state, disableNamespace);
    }
    addItem(data, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        return this.features.runningTree.addItem(path2, data);
    }
    addItems(data, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        return this.features.runningTree.addItems(path2, data);
    }
    removeItem(id, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.removeItem(path2, id);
    }
    removeItems(ids, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.removeItems(path2, ids);
    }
    updateItem(data, id, action, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.updateItem(path2, data, id, action);
    }
    updateItems(data, ids, action, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.updateItems(path2, data, ids, action);
    }
    recoverItem(id, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.recoverItem(path2, id);
    }
    recoverItems(ids, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.recoverItems(path2, ids);
    }
    resetItem(id, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.resetItem(path2, id);
    }
    update(data, action, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.update(path2, data, action);
    }
    create(data, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.create(path2, data);
    }
    remove(path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.remove(path2);
    }
    isCreation(path) {
        const value = this.getFreshValue(path);
        (0, assert_1.assert)(!(value instanceof Array));
        return value?.$$createAt$$ === 1;
    }
    clean(lsn, dontPublish, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.clean(path2, lsn, dontPublish);
    }
    savePoint() {
        return this.features.runningTree.savePoint();
    }
    t(key, params) {
        return this.features.locales.t(key, params);
    }
    execute(action, messageProps, path, opers) {
        return page_common_1.execute.call(this, action, path, messageProps, opers);
    }
    isDirty(path) {
        return this.features.runningTree.isDirty(path || this.state.oakFullpath);
    }
    getFreshValue(path) {
        return page_common_1.getFreshValue.call(this, path);
    }
    select(entity, selection) {
        return page_common_1.select.call(this, entity, selection);
    }
    checkOperation(entity, operation, checkerTypes, cacheInsensative) {
        return this.features.cache.checkOperation(entity, operation, checkerTypes, cacheInsensative);
    }
    tryExecute(path, action) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        const operations = this.features.runningTree.getOperations(path2);
        if (operations?.length) {
            for (const oper of operations) {
                const { entity, operation } = oper;
                const operation2 = action ? {
                    ...operation,
                    action,
                } : operation;
                const result = this.checkOperation(entity, operation2);
                if (result !== true) {
                    return result;
                }
            }
            return true;
        }
        return false;
    }
    getOperations(path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        return this.features.runningTree.getOperations(path2);
    }
    refresh(pageNumber, resetTotal) {
        return page_common_1.refresh.call(this, pageNumber, resetTotal);
    }
    loadMore() {
        return page_common_1.loadMore.call(this);
    }
    setId(id, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        return this.features.runningTree.setId(path2, id);
    }
    unsetId() {
        return this.features.runningTree.unsetId(this.state.oakFullpath);
    }
    getId(path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        return this.features.runningTree.getId(path2);
    }
    setFilters(filters, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.setNamedFilters(path2, filters);
    }
    setNamedFilters(filters, refresh, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.setNamedFilters(path2, filters, refresh);
    }
    getFilters(path) {
        if (this.state.oakFullpath) {
            const path2 = path
                ? `${this.state.oakFullpath}.${path}`
                : this.state.oakFullpath;
            const namedFilters = this.features.runningTree.getNamedFilters(path2);
            const filters = namedFilters.map(({ filter }) => {
                if (typeof filter === 'function') {
                    return filter();
                }
                return filter;
            });
            return filters;
        }
    }
    getFilterByName(name, path) {
        if (this.state.oakFullpath) {
            const path2 = path
                ? `${this.state.oakFullpath}.${path}`
                : this.state.oakFullpath;
            const filter = this.features.runningTree.getNamedFilterByName(path2, name);
            if (filter?.filter) {
                if (typeof filter.filter === 'function') {
                    return filter.filter();
                }
                return filter.filter;
            }
        }
    }
    addNamedFilter(namedFilter, refresh, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.addNamedFilter(path2, namedFilter, refresh);
    }
    removeNamedFilter(namedFilter, refresh, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.removeNamedFilter(path2, namedFilter, refresh);
    }
    removeNamedFilterByName(name, refresh, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.removeNamedFilterByName(path2, name, refresh);
    }
    setNamedSorters(namedSorters, refresh, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.setNamedSorters(path2, namedSorters, refresh);
    }
    getSorters(path) {
        if (this.state.oakFullpath) {
            const path2 = path
                ? `${this.state.oakFullpath}.${path}`
                : this.state.oakFullpath;
            const namedSorters = this.features.runningTree.getNamedSorters(path2);
            const sorters = namedSorters
                .map(({ sorter }) => {
                if (typeof sorter === 'function') {
                    return sorter();
                }
                return sorter;
            })
                .filter((ele) => !!ele);
            return sorters;
        }
    }
    getSorterByName(name, path) {
        if (this.state.oakFullpath) {
            const path2 = path
                ? `${this.state.oakFullpath}.${path}`
                : this.state.oakFullpath;
            const sorter = this.features.runningTree.getNamedSorterByName(path2, name);
            if (sorter?.sorter) {
                if (typeof sorter.sorter === 'function') {
                    return sorter.sorter();
                }
                return sorter.sorter;
            }
        }
    }
    addNamedSorter(namedSorter, refresh, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.addNamedSorter(path2, namedSorter, refresh);
    }
    removeNamedSorter(namedSorter, refresh, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.removeNamedSorter(path2, namedSorter, refresh);
    }
    removeNamedSorterByName(name, refresh, path) {
        const path2 = path
            ? `${this.state.oakFullpath}.${path}`
            : this.state.oakFullpath;
        this.features.runningTree.removeNamedSorterByName(path2, name, refresh);
    }
    getPagination(path) {
        if (this.state.oakFullpath) {
            const path2 = path
                ? `${this.state.oakFullpath}.${path}`
                : this.state.oakFullpath;
            return this.features.runningTree.getPagination(path2);
        }
    }
    setPageSize(pageSize, path) {
        if (this.state.oakFullpath) {
            const path2 = path
                ? `${this.state.oakFullpath}.${path}`
                : this.state.oakFullpath;
            this.features.runningTree.setPageSize(path2, pageSize);
        }
    }
    setCurrentPage(currentPage, path) {
        (0, assert_1.assert)(currentPage !== 0);
        if (this.state.oakEntity && this.state.oakFullpath) {
            const path2 = path
                ? `${this.state.oakFullpath}.${path}`
                : this.state.oakFullpath;
            this.features.runningTree.setCurrentPage(path2, currentPage);
        }
    }
    subDataEvents(events, callback) {
        return this.features.subscriber.sub(events, callback);
    }
}
function translateListeners(listeners) {
    return {
        fn(prevProps, prevState) {
            const { state, props } = this;
            for (const obs in listeners) {
                const keys = obs.split(',').map((ele) => ele.trim());
                let changed = false;
                for (const k of keys) {
                    if (k.includes('*')) {
                        throw new Error('web模式下带*的observer通配符暂不支持');
                    }
                    if ((0, toolkit_1.get)(props, k) !== (0, toolkit_1.get)(prevProps, k) ||
                        (0, toolkit_1.get)(state, k) !== (0, toolkit_1.get)(prevState, k)) {
                        changed = true;
                        break;
                    }
                }
                const prev = {};
                const next = {};
                if (changed) {
                    for (const k of keys) {
                        next[k] = (0, toolkit_1.get)(props, k) === undefined
                            ? (0, toolkit_1.get)(state, k)
                            : (0, toolkit_1.get)(props, k);
                        prev[k] = (0, toolkit_1.get)(prevProps, k) === undefined
                            ? (0, toolkit_1.get)(prevState, k)
                            : (0, toolkit_1.get)(prevProps, k);
                    }
                    listeners && listeners[obs] && listeners[obs].call(this, prev, next);
                }
            }
        },
    };
}
function createComponent(option, features) {
    const { data, methods, lifetimes, getRender, path, listeners } = option;
    const { fn } = translateListeners(listeners);
    class OakComponentWrapper extends OakComponentBase {
        features = features;
        oakOption = option;
        oakLifetime = 'born';
        isReachBottom = false;
        methodProps;
        defaultProperties;
        constructor(props) {
            super(props);
            const methodProps = {
                t: (key, params) => this.t(key, params),
                execute: (action, messageProps, path, opers) => {
                    return this.execute(action, messageProps, path, opers);
                },
                isDirty: (path) => this.isDirty(path),
                aggregate: (aggregation) => {
                    return this.features.cache.aggregate(this.state.oakEntity, aggregation);
                },
                refresh: () => {
                    return this.refresh();
                },
                setNotification: (data) => {
                    return this.setNotification(data);
                },
                setMessage: (data) => {
                    return this.setMessage(data);
                },
                navigateTo: (options, state, disableNamespace) => {
                    return this.navigateTo(options, state, disableNamespace);
                },
                navigateBack: (delta) => {
                    return this.navigateBack(delta);
                },
                redirectTo: (options, state, disableNamespace) => {
                    return this.redirectTo(options, state, disableNamespace);
                },
                clean: (lsn, dontPublish, path) => {
                    return this.clean(lsn, dontPublish, path);
                },
                checkOperation: (entity, operation, checkerTypes) => {
                    return this.checkOperation(entity, operation, checkerTypes);
                }
            };
            Object.assign(methodProps, {
                addItem: (data, path) => {
                    return this.addItem(data, path);
                },
                addItems: (data, path) => {
                    return this.addItems(data, path);
                },
                removeItem: (id, path) => {
                    return this.removeItem(id, path);
                },
                removeItems: (ids, path) => {
                    return this.removeItems(ids, path);
                },
                updateItem: (data, id, action, path) => {
                    return this.updateItem(data, id, action, path);
                },
                updateItems: (data, ids, action, path) => {
                    return this.updateItems(data, ids, action, path);
                },
                setFilters: (filters, path) => {
                    return this.setFilters(filters, path);
                },
                setNamedFilters: (filters, refresh, path) => {
                    return this.setNamedFilters(filters, refresh, path);
                },
                addNamedFilter: (filter, refresh, path) => {
                    return this.addNamedFilter(filter, refresh, path);
                },
                removeNamedFilter: (filter, refresh, path) => {
                    return this.removeNamedFilter(filter, refresh, path);
                },
                removeNamedFilterByName: (name, refresh, path) => {
                    return this.removeNamedFilterByName(name, refresh, path);
                },
                setNamedSorters: (sorters, refresh, path) => {
                    return this.setNamedSorters(sorters, refresh, path);
                },
                addNamedSorter: (sorter, refresh, path) => {
                    return this.addNamedSorter(sorter, refresh, path);
                },
                removeNamedSorter: (sorter, refresh, path) => {
                    return this.removeNamedSorter(sorter, refresh, path);
                },
                removeNamedSorterByName: (name, refresh, path) => {
                    return this.removeNamedSorterByName(name, refresh, path);
                },
                setPageSize: (pageSize, path) => {
                    return this.setPageSize(pageSize, path);
                },
                setCurrentPage: (current, path) => {
                    return this.setCurrentPage(current, path);
                },
                loadMore: () => {
                    return this.loadMore();
                },
                recoverItem: (id, path) => {
                    return this.recoverItem(id, path);
                },
                recoverItems: (ids, path) => {
                    return this.recoverItems(ids, path);
                },
                resetItem: (id, path) => {
                    return this.resetItem(id, path);
                },
                setId: (id) => {
                    return this.setId(id);
                },
                getId: (id) => {
                    return this.getId();
                },
                unsetId: () => {
                    return this.unsetId();
                }
            });
            Object.assign(methodProps, {
                update: (data, action, path) => {
                    return this.update(data, action, path);
                },
                create: (data, path) => {
                    return this.create(data, path);
                },
                remove: (path) => {
                    return this.remove(path);
                },
                isCreation: (path) => {
                    return this.isCreation(path);
                },
                getId: (path) => this.getId(path),
                setId: (id, path) => this.setId(id, path)
            });
            if (methods) {
                for (const m in methods) {
                    Object.assign(this, {
                        [m]: (...args) => methods[m].call(this, ...args),
                    });
                    Object.assign(methodProps, {
                        [m]: (...args) => methods[m].call(this, ...args),
                    });
                }
            }
            const data2 = typeof data === 'function' ? data.call(this) : data;
            for (const k in data2) {
                if (typeof data2[k] === 'function') {
                    data2[k] = data2[k].bind(this);
                }
            }
            this.state = Object.assign({}, data2, {
                oakLoading: !!option.entity && !!option.projection,
                oakLoadingMore: false,
                oakPullDownRefreshLoading: false,
                oakExecuting: false,
                oakDirty: false,
            });
            this.methodProps = methodProps;
            // 处理默认的properties
            this.defaultProperties = {};
            const { properties } = option;
            if (properties) {
                for (const property in properties) {
                    this.defaultProperties[property] = properties[property];
                }
            }
            const createResult = lifetimes?.created && lifetimes.created.call(this);
            (0, assert_1.assert)(!(createResult instanceof Promise), 'created方法不能是异步');
            this.oakLifetime = 'created';
        }
        // 编译器只会在page层注入path，component依赖父亲设置的oakPath
        iAmThePage() {
            return !!this.oakOption.path;
        }
        isMobile() {
            // 现按屏幕宽度判断是否为mobile
            return this.props.width === 'xs';
        }
        supportPullDownRefresh() {
            const { oakDisablePulldownRefresh = false } = this.props;
            return (this.isMobile() &&
                this.iAmThePage() &&
                !oakDisablePulldownRefresh);
        }
        async componentDidMount() {
            (0, oakComponentSubscription_1.bindOakComponentBuiltinSubscriptions)(option.entity, this);
            lifetimes?.attached && lifetimes.attached.call(this);
            if (option.features) {
                option.features.forEach(ele => {
                    if (typeof ele === 'string') {
                        this.addFeatureSub(ele, () => this.reRender());
                    }
                    else {
                        (0, assert_1.assert)(typeof ele === 'object');
                        const { feature, behavior, callback } = ele;
                        this.addFeatureSub(feature, () => {
                            if (behavior) {
                                switch (behavior) {
                                    case 'reRender': {
                                        this.reRender();
                                        return;
                                    }
                                    default: {
                                        (0, assert_1.assert)(behavior === 'refresh');
                                        this.refresh(undefined, true);
                                        return;
                                    }
                                }
                            }
                            else if (callback) {
                                callback.call(this);
                            }
                            else {
                                this.reRender();
                            }
                        });
                    }
                });
            }
            this.oakLifetime = 'attached';
            const { oakPath } = this.props;
            if (oakPath || path) {
                const { oakShouldInitialRefresh = false, oakShouldMature = false, ...pathState } = page_common_1.onPathSet.call(this, this.oakOption, this.iAmThePage());
                /**
                 * 实际中出现了setState后先调willUnmount析构，而callback不被调用的情况，此时willUnmount需要将runningTree上的node正确析构
                 * by Xc 20240806
                 */
                this.oakFullpath = pathState.oakFullpath;
                this.setState(pathState, async () => {
                    if (this.oakLifetime === 'detached') {
                        return;
                    }
                    try {
                        const { oakFullpath } = this.state;
                        if (oakFullpath && oakShouldInitialRefresh) {
                            await this.refresh();
                            lifetimes?.mature && lifetimes.mature.call(this);
                        }
                        else {
                            this.reRender();
                            if (oakFullpath && oakShouldMature && !oakShouldInitialRefresh) {
                                lifetimes?.mature && lifetimes.mature.call(this);
                            }
                        }
                        if (this.oakLifetime === 'detached') {
                            return;
                        }
                        lifetimes?.ready && await lifetimes.ready.call(this);
                    }
                    catch (err) {
                        if (err instanceof types_1.OakException) {
                            err.tag2 = true;
                        }
                        throw err;
                    }
                    if (this.oakLifetime === 'detached') {
                        return;
                    }
                    lifetimes?.show && lifetimes.show.call(this);
                    this.oakLifetime = 'ready';
                });
            }
            else if (!this.oakOption.entity) {
                // 如果没有entity，也不需要onPathSet，直接走ready
                lifetimes?.ready && await lifetimes.ready.call(this);
                lifetimes?.show && lifetimes.show.call(this);
                this.reRender();
                this.oakLifetime = 'ready';
            }
        }
        componentWillUnmount() {
            this.unsubscribeAll();
            // 这里用this.oakFullpath去析构，见上面didMount的注释
            this.oakFullpath && page_common_1.destroyNode.call(this, this.iAmThePage(), this.oakFullpath);
            lifetimes?.detached && lifetimes.detached.call(this);
            this.oakLifetime = 'detached';
        }
        async componentDidUpdate(prevProps, prevState) {
            if (prevProps.oakPath !== this.props.oakPath) {
                // oakPath如果是用变量初始化，在这里再执行onPathSet，如果有entity的结点在此执行ready
                (0, assert_1.assert)(this.props.oakPath && this.oakOption.entity);
                const { oakShouldInitialRefresh = false, oakShouldMature = false, ...pathState } = page_common_1.onPathSet.call(this, this.oakOption, this.iAmThePage());
                if (pathState.oakFullpath) {
                    this.oakFullpath = pathState.oakFullpath;
                }
                this.setState(pathState, async () => {
                    if (this.oakLifetime === 'detached') {
                        return;
                    }
                    if (prevProps.oakPath === undefined) {
                        // 如果每个页面都在oakFullpath形成后再渲染子结点，这个if感觉是不应该命中的
                        console.warn('发生了结点先形成再配置oakPath的情况，请检查代码修正');
                        lifetimes?.ready && lifetimes.ready.call(this);
                        lifetimes?.show && lifetimes.show.call(this);
                    }
                    const { oakFullpath } = this.state;
                    if (oakFullpath && oakShouldInitialRefresh) {
                        await this.refresh();
                        lifetimes?.mature && lifetimes.mature.call(this);
                    }
                    else {
                        this.reRender();
                        if (oakFullpath && oakShouldMature && !oakShouldInitialRefresh) {
                            lifetimes?.mature && lifetimes.mature.call(this);
                        }
                    }
                });
            }
            else if (this.props.oakId !== prevProps.oakId) {
                if (this.props.oakId) {
                    this.setId(this.props.oakId);
                }
                else {
                    // 组件销毁时会出现这种情况
                    this.unsetId();
                }
            }
            fn && fn.call(this, prevProps, prevState);
        }
        render() {
            const Render = getRender.call(this);
            // 传入oakPath或page入口页 需要等待oakFullpath初始化完成
            if ((this.props.oakPath || path) && !this.state.oakFullpath) {
                return null;
            }
            // option有entity，也需要等待oakFullpath初始化完成
            if (this.oakOption.entity && !this.state.oakFullpath) {
                return null;
            }
            return (<Render methods={this.methodProps} data={{
                    ...this.defaultProperties,
                    ...this.state,
                    ...this.props,
                }}/>);
        }
    }
    ;
    return OakComponentWrapper;
}
