import { __awaiter, __rest } from "tslib";
// Import Libs and Handlers
import * as React from 'react';
import { autorun, observable, reaction } from 'mobx';
import { get, isEqual, noop } from 'lodash-es';
import { app, createPageApi, $w as baseAPI } from '../app/global-api'; // 取对应子包app
import { AppRender } from '../handlers/RenderWrapper';
import { _WEDA_CLOUD_SDK as WEDA_CLOUD_SDK, AppContext } from '@cloudbase/weda-client';
import { CALC_RPX_ROOT_VAL_NAME, ENABLE_PAGE_ROOT_ID, generateEmit, reportTime, generateWidgetAPIContext, } from '../utils';
import { bindFuncObj, checkAuth, createComputed, createWidgets, retryDataBinds, toCssText, defaultGenerateLoginUrl, } from '../utils/index';
import { generatePageLifecycleHandlers, usePageLifeCycle } from '../utils/lifecycle';
import { useScrollTop, OwnerComponentContext } from '../utils/hooks';
import { processCommonStyle2CSSProperties, processListeners } from '@cloudbase/cals';
import { runtime } from '../utils/adapter';
import { loadMicroApp } from 'qiankun';
import { InjectContext } from '../router';
import { useLocation } from 'react-router-dom';
import { generateDatasetQuery, generateEventFlows } from '../utils/adapter/generate';
import { watchForDebugExpr, resetDebugExprMap } from '../utils/debugExpr';
const { createDataset, createStateDataSourceVar, generateParamsParser, EXTRA_API } = WEDA_CLOUD_SDK;
function useMemoOrDeep(value, memo) {
    const ref = React.useRef(value);
    return React.useMemo(() => {
        if (memo || !isEqual(ref.current, value)) {
            ref.current = value;
        }
        return ref.current;
    }, [value]);
}
function useObserverControledDataContext(inited, injectContext, initDataContext) {
    const dataContextRef = React.useRef(observable((injectContext === null || injectContext === void 0 ? void 0 : injectContext.dataContext) || initDataContext));
    return React.useMemo(() => {
        if (inited && (injectContext === null || injectContext === void 0 ? void 0 : injectContext.dataContext)) {
            if (dataContextRef.current) {
                // eslint-disable-next-line no-restricted-syntax
                for (const key in dataContextRef.current) {
                    if (!injectContext.dataContext[key]) {
                        dataContextRef.current[key] = null;
                    }
                }
            }
            // eslint-disable-next-line no-restricted-syntax
            for (const key in injectContext.dataContext) {
                dataContextRef.current[key] = injectContext.dataContext[key];
            }
            // setFourceUpdate(Date.now());
        }
        return dataContextRef.current;
    }, [inited, injectContext === null || injectContext === void 0 ? void 0 : injectContext.dataContext]);
}
// Init
// eslint-disable-next-line react/display-name
export default function App({ page: __page, generateComponentsMap, generateActionsMap, generateLowcodeModule, generateWidgetsContext, generateDataBinds, generateComponentSchema, generateContext, onRender, onMounted, initDataContext = {}, renderLoading, options, }) {
    const _page = useMemoOrDeep(__page, !options.reuseWidget);
    const { page, eventFlows = [] } = React.useMemo(() => {
        const { listeners, eventFlows } = processListeners(_page.listeners || []);
        return { page: Object.assign(Object.assign({}, _page), { listeners }), eventFlows };
    }, [_page]);
    const injectContext = React.useContext(InjectContext);
    const [pageContextInited, setPageContextInited] = React.useState(false);
    const [_, setFourceUpdate] = React.useState(Date.now());
    // 数据容器的上下文数据
    const widgetsContextRef = React.useRef({});
    const { reuseWidget = false } = options || {};
    const globalCtxRef = React.useRef({});
    // 兼容 this.state / $page.state 两种模式
    const pageCodeContextRef = React.useRef((injectContext === null || injectContext === void 0 ? void 0 : injectContext.$page) || createPageApi());
    const $page = pageCodeContextRef.current;
    const lowcode = generateLowcodeModule();
    const { pathname } = useLocation();
    React.useEffect(() => {
        var _a, _b;
        if (!(injectContext === null || injectContext === void 0 ? void 0 : injectContext.$page)) {
            const dataset = createDataset(page.id);
            const state = observable(lowcode.state || {});
            state.dataset = dataset;
            Object.assign($page, {
                id: page.id,
                label: (_a = page === null || page === void 0 ? void 0 : page.attributes) === null || _a === void 0 ? void 0 : _a.title,
                path: pathname,
                state,
                computed: createComputed(lowcode.computed || {}, pageCodeContextRef.current),
                handler: bindFuncObj(lowcode.handler || {}, pageCodeContextRef.current),
                randerer: lowcode.renderer,
                dataset,
            });
        }
        Object.assign($page, {
            __internal__: Object.assign(Object.assign({}, $page.__internal__), { $w: new Proxy(baseAPI, {
                    get(obj, prop) {
                        var _a, _b, _c, _d;
                        /**
                         * 使用当前的实例进行覆盖
                         */
                        if (prop === '$page' || prop === 'page') {
                            return $page;
                        }
                        // 代理页面级 query
                        if ((_b = (_a = $page.dataset) === null || _a === void 0 ? void 0 : _a.query) === null || _b === void 0 ? void 0 : _b[prop]) {
                            return $page.dataset.query[prop];
                        }
                        // 代理页面 flow
                        if ((_c = $page.__internal__.eventFlows) === null || _c === void 0 ? void 0 : _c[prop]) {
                            return $page.__internal__.eventFlows[prop];
                        }
                        // 尝试代理页面级别组件实例
                        let pageWidget = (_d = $page.widgets) === null || _d === void 0 ? void 0 : _d[prop];
                        if (pageWidget) {
                            return pageWidget._userWidget;
                        }
                        return obj[prop];
                    },
                }) }),
        });
        runtime.$page = pageCodeContextRef.current;
        if (!(injectContext === null || injectContext === void 0 ? void 0 : injectContext.$page)) {
            $page.dataset.query = generateDatasetQuery(((_b = page.dataset) === null || _b === void 0 ? void 0 : _b.query) || {}, {
                $w: $page.__internal__.$w,
                $page,
                $app: app,
                actionsMap: (generateActionsMap === null || generateActionsMap === void 0 ? void 0 : generateActionsMap()) || {},
            }, {
                isComposite: false,
                looseError: options.looseError,
                useScopeWidgetData: true,
            });
        }
        resetDebugExprMap();
        setPageContextInited(true);
    }, []);
    React.useEffect(() => {
        if (pageContextInited) {
            $page.__internal__.eventFlows = generateEventFlows(eventFlows, { $w: $page.__internal__.$w, $app: app, $page }, {
                isComposite: false,
                looseError: options.looseError,
                useScopeWidgetData: true,
            });
        }
    }, [pageContextInited, eventFlows]);
    const dataContext = useObserverControledDataContext(pageContextInited, injectContext, initDataContext);
    const { componentSchema, widgetsContext } = React.useMemo(() => {
        if (pageContextInited) {
            globalCtxRef.current = {
                $page,
                $app: app,
                actionsMap: (generateActionsMap === null || generateActionsMap === void 0 ? void 0 : generateActionsMap()) || {},
            };
            const globalCtx = globalCtxRef.current;
            let widgetsContext;
            let databinds;
            let componentSchema;
            if (generateContext) {
                let context = generateContext(globalCtx, page);
                widgetsContext = context.widgetContext;
                databinds = context.dataBinds;
                componentSchema = context.componentSchema;
            }
            else {
                widgetsContext = generateWidgetsContext(page);
                databinds = generateDataBinds(globalCtx, page);
                componentSchema = generateComponentSchema(globalCtx, page);
            }
            // widgets 内的 dataBinds 可能需要关联 widgets，需要重新执行 dataBinds
            $page._dataBinds = databinds;
            return { componentSchema, widgetsContext };
        }
        return {};
    }, [pageContextInited, page]);
    widgetsContextRef.current = React.useMemo(() => {
        if (pageContextInited) {
            const globalCtx = globalCtxRef.current;
            const { widgets, nodeMap } = createWidgets(Object.assign(Object.assign({ apiContext: $page }, (reuseWidget ? widgetsContextRef.current : {})), { globalCtx }), widgetsContext, $page._dataBinds || {}, dataContext);
            Object.entries(widgets).forEach(([key, value]) => {
                $page.widgets[key] = value;
            });
            retryDataBinds();
            if (reuseWidget) {
                /**
                 * 复用时处理挂载新的
                 * 还要处理老的
                 */
                Object.keys($page.widgets).forEach((key) => {
                    if (!(key in widgets)) {
                        $page.widgets[key] = undefined;
                    }
                });
                const { widgets: lastWdigets } = widgetsContextRef.current || {};
                if (lastWdigets === null || lastWdigets === void 0 ? void 0 : lastWdigets._disposers) {
                    lastWdigets._disposers.forEach((dispose) => dispose());
                    lastWdigets._disposers = [];
                }
            }
            return { widgetProps: widgetsContext, nodeMap, widgets };
        }
        else {
            return widgetsContextRef.current;
        }
    }, [widgetsContext, dataContext]);
    React.useEffect(() => {
        return () => {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
            if ((_b = (_a = pageCodeContextRef.current) === null || _a === void 0 ? void 0 : _a.widgets) === null || _b === void 0 ? void 0 : _b._disposers) {
                (_d = (_c = pageCodeContextRef.current) === null || _c === void 0 ? void 0 : _c.widgets) === null || _d === void 0 ? void 0 : _d._disposers.forEach((dispose) => dispose());
                pageCodeContextRef.current.widgets._disposers = [];
            }
            if ((_f = (_e = pageCodeContextRef.current) === null || _e === void 0 ? void 0 : _e.dataset) === null || _f === void 0 ? void 0 : _f.query) {
                for (const key in (_h = (_g = pageCodeContextRef.current) === null || _g === void 0 ? void 0 : _g.dataset) === null || _h === void 0 ? void 0 : _h.query) {
                    const query = (_k = (_j = pageCodeContextRef.current) === null || _j === void 0 ? void 0 : _j.dataset) === null || _k === void 0 ? void 0 : _k.query[key];
                    (_l = query === null || query === void 0 ? void 0 : query.destroy) === null || _l === void 0 ? void 0 : _l.call(query);
                }
            }
        };
    }, []);
    React.useEffect(() => {
        let forDebugExprDispose = null;
        if (pageContextInited) {
            /**
             * 页面节点的表达式执行
             * 只在编辑器中开启调试表达式的功能
             */
            forDebugExprDispose = baseAPI.wedaContext.isEditorMode
                ? watchForDebugExpr({
                    getDataContextFn: () => ({}),
                    get$wFn: () => { var _a; return generateWidgetAPIContext((_a = $page === null || $page === void 0 ? void 0 : $page.__internal__) === null || _a === void 0 ? void 0 : _a.$w, widgetsContext, {}); },
                    get$scopeFn: () => ({}),
                    widgetId: 'page',
                    subForItems: {},
                    globalCtx: globalCtxRef.current,
                })
                : noop;
        }
        return function () {
            forDebugExprDispose === null || forDebugExprDispose === void 0 ? void 0 : forDebugExprDispose();
        };
    }, [pageContextInited]);
    return (pageContextInited && (React.createElement(AppContent, { page: page, lowcode: lowcode, componentSchema: componentSchema, pageCodeContext: pageCodeContextRef.current, virtualFields: generateComponentsMap(), onMounted: onMounted, onRender: onRender, 
        // dataContext={dataContext}
        /**
         * 更新数据容器的上下文的方法
         * 会传递到事件emit的地方，将从组件获取到的数据data赋值给上下文
         * 当组件卸载时，传过来的data为undefined即可
         * {
         *   id1: [{...}],
         *   id2: {...},
         *   id3: undefined,
         *   id4: null,
         *   ...
         * }
         * @param id
         * @param data
         */
        // updateContext={function (id, data) {
        //   if (id && !injectContext?.dataContext) {
        //     dataContext[id] = { data };
        //   }
        //   injectContext?.onDataContextChange?.(id, data);
        // }}
        options: options, renderLoading: renderLoading })));
}
function AppContent({ page, lowcode, componentSchema, pageCodeContext, virtualFields, onMounted = (props) => {
    var _a;
    // eslint-disable-next-line react/prop-types
    (_a = props === null || props === void 0 ? void 0 : props.default) === null || _a === void 0 ? void 0 : _a.call(props);
}, onRender = () => { }, 
// dataContext,
// updateContext,
renderLoading = () => null, options, }) {
    var _a, _b, _c, _d, _e;
    const { processCssUnit = 'rem' } = options;
    // 检查权限
    const [weDaHasLogin, setWeDaHasLogin] = React.useState(false);
    const containerRef = React.useRef(null);
    const externalClassRef = React.useRef('');
    const microApp = React.useRef(null);
    const pureSrc = (_a = page.attributes) === null || _a === void 0 ? void 0 : _a.src;
    const isPure = !!pureSrc;
    const { checkPageAuth = checkAuth, generateLoginUrl = defaultGenerateLoginUrl } = React.useContext(InjectContext);
    const pageReadyResolveRef = React.useRef((data) => { });
    const pageReadyCalledRef = React.useRef(false);
    const onReady = React.useCallback(() => {
        pageReadyCalledRef.current = true;
        pageReadyResolveRef.current(null);
    }, []);
    const events = React.useMemo(() => getPageEvents(pageCodeContext, componentSchema.listenerInstances), [pageCodeContext, componentSchema.listenerInstances]);
    const lifecycle = React.useMemo(() => {
        return generatePageLifecycleHandlers(Object.assign(Object.assign({}, lowcode === null || lowcode === void 0 ? void 0 : lowcode.lifecycle), { beforePageCustomLaunch: (query) => __awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c, _d, _e;
                onMounted({
                    page,
                    default: () => {
                        var _a;
                        const document = (runtime === null || runtime === void 0 ? void 0 : runtime.document) || window.document;
                        document.title = `${((_a = page.attributes) === null || _a === void 0 ? void 0 : _a.title) || page.id}`;
                    },
                });
                EXTRA_API.setParams(page.id, query || {});
                initWatchMethods(pageCodeContext);
                let checkAuthResult = yield checkPageAuth(app, app.id, pageCodeContext, generateLoginUrl);
                setWeDaHasLogin(checkAuthResult);
                createStateDataSourceVar(page.id, generateParamsParser({ app, $page: pageCodeContext, $w: pageCodeContext === null || pageCodeContext === void 0 ? void 0 : pageCodeContext.__internal__.$w }));
                // eslint-disable-next-line no-restricted-syntax
                for (const queryId in ((_a = page === null || page === void 0 ? void 0 : page.dataset) === null || _a === void 0 ? void 0 : _a['query']) || {}) {
                    if (page.dataset['query'][queryId].trigger === 'auto') {
                        try {
                            (_e = (_d = (_c = (_b = pageCodeContext === null || pageCodeContext === void 0 ? void 0 : pageCodeContext.dataset) === null || _b === void 0 ? void 0 : _b.query) === null || _c === void 0 ? void 0 : _c[queryId]) === null || _d === void 0 ? void 0 : _d.trigger) === null || _e === void 0 ? void 0 : _e.call(_d);
                        }
                        catch (e) {
                            console.error(`query ${queryId} 初始化失败：`, e);
                        }
                    }
                }
                onRender === null || onRender === void 0 ? void 0 : onRender();
                reportTime('FIRST_PAGE_CONTENT_RENDERED', undefined, true);
            }) }), events);
    }, []);
    // Web 环境页面级别生命周期
    usePageLifeCycle(Object.assign(Object.assign({}, lifecycle), { 
        // eslint-disable-next-line @typescript-eslint/no-misused-promises
        onPageReady: (...args) => __awaiter(this, void 0, void 0, function* () {
            yield new Promise((resolve) => {
                pageReadyResolveRef.current = resolve;
                if (pageReadyCalledRef.current) {
                    resolve(null);
                }
            });
            const res = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.onPageReady.call(this, ...args);
            events === null || events === void 0 ? void 0 : events.ready();
            return res;
        }) }), { app, page: pageCodeContext, options: { looseError: options === null || options === void 0 ? void 0 : options.looseError } }, []);
    // 切换页面滚动到顶部
    useScrollTop();
    React.useEffect(() => {
        var _a, _b;
        if (isPure) {
            if (weDaHasLogin) {
                if (!microApp.current) {
                    microApp.current = loadMicroApp({
                        name: page.id,
                        entry: pureSrc,
                        container: containerRef.current,
                    }, {
                        fetch: runtime.fetch,
                    });
                }
                else {
                    (_b = (_a = microApp.current).mount) === null || _b === void 0 ? void 0 : _b.call(_a);
                }
            }
            else if (microApp.current) {
                microApp.current.unmount();
            }
        }
    }, [weDaHasLogin]);
    React.useEffect(() => {
        pageCodeContext.__internal__.active = true;
        app.__internal__.activePage = pageCodeContext;
        return () => {
            if (pageCodeContext.__internal__) {
                pageCodeContext.__internal__.active = false;
            }
            if (microApp.current) {
                microApp.current.unmount();
            }
        };
    }, []);
    React.useEffect(() => {
        function resize() {
            var _a, _b;
            const className = `wd-${((_b = (_a = app === null || app === void 0 ? void 0 : app.__internal__) === null || _a === void 0 ? void 0 : _a.isMobile) === null || _b === void 0 ? void 0 : _b.call(_a)) ? 'h5' : 'pc'}-root`;
            if (externalClassRef.current === className) {
                return;
            }
            if (containerRef.current.classList.contains(externalClassRef.current)) {
                containerRef.current.classList.replace(externalClassRef.current, className);
            }
            else {
                containerRef.current.classList.add(className);
            }
            externalClassRef.current = className;
        }
        resize();
        runtime === null || runtime === void 0 ? void 0 : runtime.addEventListener('resize', resize);
        return () => {
            runtime === null || runtime === void 0 ? void 0 : runtime.removeEventListener('resize', resize);
        };
    }, []);
    return (React.createElement("div", { id: ENABLE_PAGE_ROOT_ID ? 'wd-page-root' : undefined, ref: containerRef, className: `weapps-page weapps-page-${page.id} ${externalClassRef.current}` },
        React.createElement("style", { id: "wd-page-style-tag", type: "text/css", dangerouslySetInnerHTML: {
                __html: `${toCssText(((_b = page.attributes) === null || _b === void 0 ? void 0 : _b.style) ||
                    processCommonStyle2CSSProperties(((_c = page === null || page === void 0 ? void 0 : page.extra) === null || _c === void 0 ? void 0 : _c.commonStyle) || {}, {
                        defaultUnit: processCssUnit,
                        rpxCalcRoot: `var(${CALC_RPX_ROOT_VAL_NAME})`,
                    }), 'body')}${(lowcode.style || []).map((item) => item.code).join('\n')}`,
            } }),
        weDaHasLogin
            ? !isPure &&
                (((_d = lowcode.lifecycle) === null || _d === void 0 ? void 0 : _d.onPageRender) ? ((_e = lowcode.lifecycle) === null || _e === void 0 ? void 0 : _e.onPageRender()) : (React.createElement(IAppRender, { virtualFields: virtualFields, componentSchema: componentSchema, codeContext: pageCodeContext, onReady: onReady })))
            : renderLoading({ message: '登录鉴权中...' })));
}
function IAppRender(_a) {
    var { onReady } = _a, props = __rest(_a, ["onReady"]);
    const [mount, setMounted] = React.useState(false);
    React.useEffect(() => {
        setMounted(true);
        return () => {
            setMounted(false);
        };
    }, []);
    /**
     * @important
     * 需要子节点先触发，page 的最后触发，
     * 因此不在 setMount 时直接调用
     */
    React.useEffect(() => {
        if (mount) {
            onReady();
        }
    }, [mount]);
    const { appContext, ownerComponentContext } = React.useMemo(() => {
        return { appContext: { ready: mount }, ownerComponentContext: { attached: mount } };
    }, [mount]);
    return (React.createElement(AppContext.Provider, { value: appContext },
        React.createElement(OwnerComponentContext.Provider, { value: ownerComponentContext },
            React.createElement(AppRender, Object.assign({}, props)))));
}
function initWatchMethods(pageContext) {
    // const { watch = {}, watchState = {}, watchWidget = {}, watchEffects = {}} = pageAPI
    const { watch = {}, watchState = {}, watchWidget = {}, watchEffects = {} } = {};
    // # watch effect
    const watchEffectDisposers = Object.keys(watchEffects).map((fnName) => {
        return autorun(watchEffects[fnName].bind(pageContext));
    });
    const disposers = watchEffectDisposers;
    // # watch state
    Object.keys(watchState).map((key) => runWatcher(parseWatcher(watchState[key]), pageContext.state, key, 'watchState'));
    // # watch widgets
    Object.keys(watchWidget).map((key) => runWatcher(parseWatcher(watchWidget[key]), pageContext.widgets, key, 'watchWidgets'));
    function runWatcher({ handler, immediate } = {}, target, key, label) {
        if (!handler) {
            console.error(`Invalid ${label}(${key}) of ${pageContext.node.widgetType}, watch must a function or {handler: function}`);
            return;
        }
        const disposer = reaction(() => get(target, key), handler.bind(pageContext), { fireImmediately: immediate });
        disposers.push(disposer);
    }
}
function parseWatcher(watcher) {
    if (!watcher)
        return;
    if (watcher instanceof Function) {
        return { handler: watcher, immediate: false };
    }
    const { handler, immediate = false } = watcher;
    if (!(handler instanceof Function))
        return;
    return { handler, immediate };
}
function getPageEvents(pageCodeContext, listenerInstances) {
    var _a;
    const emit = generateEmit((_a = pageCodeContext.__internal__) === null || _a === void 0 ? void 0 : _a.$w);
    return new Proxy({}, {
        get: (target, eventName) => {
            return (eventData) => emit(eventName, listenerInstances, eventData, {}, undefined, {}, {}, pageCodeContext.id);
        },
    });
}
