import { __awaiter, __rest } from "tslib";
/* eslint-disable react/prop-types */
import * as React from 'react';
import { AppRender } from '../handlers/RenderWrapper';
import { OwnerComponentContext } from '../utils/hooks';
import { ECodeType, EComponentType } from '@cloudbase/cals';
import { createCompApi, app } from '../app/global-api';
import { generateLowcodeModules, generateStyleModuleMap } from '../utils/adapter/lowcode';
import { getComponentSchema } from '../utils/adapter/generate';
import { runtime } from '../utils/adapter';
import { autorun, observable } from 'mobx';
import jsonSchemaDefaults from 'json-schema-defaults';
import { createWidgets, retryDataBinds } from '../utils';
function useLoad(defaultValue, callback, options = {}, deps) {
    const { looseError = false } = options;
    const [loaded, setLoaded] = React.useState(false);
    const [error, setError] = React.useState(null);
    const [data, setData] = React.useState(defaultValue);
    React.useEffect(() => {
        function run() {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    const lowcode = yield callback();
                    setData(lowcode);
                    setLoaded(true);
                    setError(null);
                }
                catch (e) {
                    if (!looseError) {
                        setError(e);
                    }
                    else {
                        console.log(e);
                    }
                    setLoaded(true);
                }
            });
        }
        run();
    }, deps);
    return { data, error, loaded, setData, setError, setLoaded };
}
function loadLowcodes(comp, module, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const { processCssUnit = 'rem' } = options;
        const codeModulesMap = {
            $comp: (comp.resources || []).map((module) => {
                if (module.codeType === ECodeType.THEME || module.codeType === ECodeType.STYLE) {
                    return module;
                }
                const { code } = module, restModule = __rest(module, ["code"]);
                return Object.assign(Object.assign({}, restModule), { code: code.replace(/\$comp/g, 'this.$comp') });
            }),
        };
        const styleModulesMap = {
            $comp: {
                resources: codeModulesMap.$comp,
                processCssUnit,
                cssContextWrapper: getCompositedComponentClass({ module, component: comp.name }),
            },
        };
        const [styleCodesMap, { state, computedFuncs, lifeCycle, handler }] = yield Promise.all([
            generateStyleModuleMap(styleModulesMap),
            generateLowcodeModules({
                codeModulesMap,
                runtimeWindow: runtime,
            }).catch((e) => {
                e.message = `[低码模块加载失败] ${e.absoluteReqPath ? `[${e.absoluteReqPath}]` : ''} ${e.message}`;
                throw e;
            }),
        ]);
        const lowcode = {
            state: state.$comp || {},
            computed: computedFuncs.$comp || {},
            lifecycle: lifeCycle.$comp || {},
            handler: handler.$comp || {},
            style: styleCodesMap.$comp || [],
        };
        return lowcode;
    });
}
export function generateCompositeComponent({ comp, module, componentsMap, componentActionsMap, componentsInfoMap, options, }) {
    return __awaiter(this, void 0, void 0, function* () {
        const lowcode = yield loadLowcodes(comp, module, options);
        const Component = React.forwardRef((props, ref) => {
            // console.log('composite component props', props);
            // const { data, children, $node } = useComponentProps(props as any, true);
            return (React.createElement(CompositeComponent, { ref: ref, comp: comp, props: props, generateLowcodeModule: () => lowcode, generateComponentsMap: () => componentsMap, generateActionsMap: () => componentActionsMap, generateContext: (ctx, comp) => {
                    const wrapperClass = getCompositedComponentClass({ module, component: comp.name });
                    const { widgets, componentSchema, dataBinds } = getComponentSchema(ctx, { items: comp.items, type: EComponentType.COMPLEX, extra: {} }, componentsInfoMap, wrapperClass, {
                        isComposite: true,
                        looseError: options.looseError,
                        /**
                         * TODO:
                         * 是否固定为 rpx
                         */
                        processCssUnit: options.processCssUnit,
                    });
                    return {
                        widgetContext: widgets,
                        componentSchema,
                        dataBinds,
                    };
                } }));
        });
        Component.displayName = comp.name;
        return Component;
    });
}
export function getCompositedComponentClass({ module, component }) {
    return `wa-comp-${module}-${component}`;
}
function useObserverDataRef(data, defaultData) {
    const ref = React.useRef(observable(Object.assign(Object.assign({}, defaultData))));
    const [_, setForceUpate] = React.useReducer((data) => {
        return data + 1;
    }, 0);
    React.useEffect(() => {
        const dispose = autorun(() => {
            let update = false;
            for (const prop in data) {
                // 更新 propsData
                if (typeof data[prop] !== 'function') {
                    ref.current[prop] = data[prop];
                    update = true;
                }
            }
            if (update) {
                console.log('-->>');
                setForceUpate();
            }
        });
        return () => {
            dispose();
        };
    }, [data]);
    return { ref, key: _ };
}
const CompositeComponent = React.forwardRef(({ comp, props, generateComponentsMap, generateLowcodeModule, generateActionsMap, generateContext }, ref) => {
    const { events, $node, data, className, style } = props;
    const codeContextRef = React.useRef(null);
    const { ref: dataRef } = useObserverDataRef(data, jsonSchemaDefaults(comp.data));
    const widgetsContextRef = React.useRef({});
    const globalCtxRef = React.useRef({});
    const $comp = codeContextRef.current;
    const [componentCodeContextInited, setcomponentCodeContextInited] = React.useState(false);
    const lowcodeRef = React.useRef(generateLowcodeModule());
    const lowcode = lowcodeRef.current;
    // React.useEffect(() => {
    //   console.log('>>>>>>>>>>>>>>>>>>>>', props);
    // }, [dataRef.current.default]);
    React.useEffect(() => {
        const $comp = createCompApi({
            id: comp.id,
            ref,
            state: observable(lowcode.state),
            computed: lowcode.computed,
            handler: lowcode.handler,
            props,
            propsData: dataRef.current,
            events,
            node: Object.assign($node || {}, {
                getConfig: () => comp.compConfig,
                getOwnerWidget: () => $node.ownerWidget,
                _debug: () => {
                    console.warn('仅用于调试，行为随时发生变化，不要用于生产环境。');
                    return $comp;
                },
            }),
        });
        codeContextRef.current = $comp;
        setcomponentCodeContextInited(true);
        return () => {
            var _a, _b, _c;
            (_c = (_b = (_a = lowcode.lifecycle.onAttached) === null || _a === void 0 ? void 0 : _a.bind) === null || _b === void 0 ? void 0 : _b.call(_a, codeContextRef.current)) === null || _c === void 0 ? void 0 : _c();
        };
    }, []);
    const { componentSchema, widgetsContext } = React.useMemo(() => {
        if (componentCodeContextInited) {
            globalCtxRef.current = {
                $comp,
                $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, comp);
                widgetsContext = context.widgetContext;
                databinds = context.dataBinds;
                componentSchema = context.componentSchema;
            }
            // widgets 内的 dataBinds 可能需要关联 widgets，需要重新执行 dataBinds
            $comp._dataBinds = databinds;
            return { componentSchema, widgetsContext };
        }
        return {};
    }, [componentCodeContextInited, comp]);
    widgetsContextRef.current = React.useMemo(() => {
        if (componentCodeContextInited) {
            const globalCtx = globalCtxRef.current;
            const { widgets, nodeMap } = createWidgets({ apiContext: $comp, globalCtx }, widgetsContext, $comp._dataBinds || {}, {});
            retryDataBinds();
            Object.entries(widgets).forEach(([key, value]) => {
                $comp.widgets[key] = value;
                const list = !Array.isArray(value) ? [value] : value;
                list.forEach((item) => {
                    item.ownerWidget = $comp.node;
                    item.getOwnerWidget = () => $comp.node;
                });
            });
            return { widgetProps: widgetsContext, nodeMap, widgets };
        }
        else {
            return widgetsContextRef.current;
        }
    }, [widgetsContext]);
    return (componentCodeContextInited && (React.createElement(CompositeComponentContent, { className: className, style: style, comp: comp, lowcode: lowcode, componentSchema: componentSchema, componentCodeContext: codeContextRef.current, virtualFields: generateComponentsMap() })));
});
CompositeComponent.displayName = 'CompositeComponent';
function CompositeComponentContent({ lowcode, componentSchema, componentCodeContext, virtualFields, className = '', style = {}, }) {
    const { attached } = React.useContext(OwnerComponentContext);
    const [mount, setMounted] = React.useState(false);
    React.useEffect(() => {
        setMounted(true);
        return () => {
            var _a, _b, _c;
            if (componentCodeContext.widgets._disposers) {
                componentCodeContext.widgets._disposers.forEach((disposer) => disposer());
                componentCodeContext.widgets._disposers = null;
            }
            (_c = (_b = (_a = lowcode === null || lowcode === void 0 ? void 0 : lowcode.lifecycle.onDetached) === null || _a === void 0 ? void 0 : _a.bind) === null || _b === void 0 ? void 0 : _b.call(_a, componentCodeContext)) === null || _c === void 0 ? void 0 : _c();
            componentCodeContext._instanceRef.current = undefined;
            setMounted(false);
        };
    }, []);
    React.useEffect(() => {
        var _a, _b, _c;
        if (attached) {
            (_c = (_b = (_a = lowcode.lifecycle.onReady) === null || _a === void 0 ? void 0 : _a.bind) === null || _b === void 0 ? void 0 : _b.call(_a, componentCodeContext)) === null || _c === void 0 ? void 0 : _c();
        }
    }, [attached]);
    const ownerComponentContext = React.useMemo(() => {
        return { attached: mount };
    }, [mount]);
    return (React.createElement(OwnerComponentContext.Provider, { value: ownerComponentContext },
        React.createElement(AppRender, { className: className, style: style, virtualFields: virtualFields, componentSchema: componentSchema, codeContext: componentCodeContext })));
}
