import * as React from 'react';
import { Router, Route, Redirect, Switch } from 'react-router-dom';
import PageRender from '../pages/index';
import { app, OFFICIAL_LIB_KEY } from '../app/global-api';
import { _WEDA_CLOUD_SDK as WEDA_CLOUD_SDK, auth } from '@cloudbase/weda-client';

import { cleanLowcodeContext, generateLowcodeModules, generateStyleModuleMap } from '../utils/adapter/lowcode';
import { getComponentsInfo, getComponentSchema } from '../utils/adapter/generate';
import { loadDependencies as _loadDependencies, injectExternals } from '../utils/adapter/dependencies';
import { createHistory, getAuthConfig, HistoryOptions, redirectToLogin, checkAnonymous } from '../utils/index';
import { initGlobalVar, removeGlobalVar } from '../utils/initGlobalVar';
import { initAppLifeCycle, removeAppLifeCycle } from '../utils/lifecycle';
import { ICodeResource, IPlatformApp, IApp, getUsedComps } from '@cloudbase/cals';
import { getConfig as _getConfig } from '../utils/adapter/datasource';
import { runtime } from '../utils/adapter';
import classnames from 'classnames';
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
import './index.css';

const { initTcb, setConfig, createStateDataSourceVar, generateParamsParser, EXTRA_API, createDataset } = WEDA_CLOUD_SDK;

const { getAccessToken } = auth;

function NotFound({
  onNotFound = () => {
    console.error('页面 404，可以在生命周期配置 onAppPageNotFound 并跳转到对应页面');
  },
}) {
  React.useEffect(() => {
    onNotFound();
  }, []);
  return <>页面404</>;
}

function Loading() {
  return (
    <>
      <style
        type="text/css"
        dangerouslySetInnerHTML={{
          __html: `.weda-loading-container {
            position: fixed;
            left:0;
            top:0;
            width: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: column;
            height:100%;

          }
          .weda-loading-container .logo {
            width: 100%;
            display: flex;
            justify-content: center;
            text-align: center;
            margin-bottom: 50px;
          }

          .weda-loading-container .sp{
            width: 48px;
            height:48px;
          }

          .weda-loading-container .sp-circle {
            border: 6px rgba(0,82,217,0.25) solid;
            border-top: 6px rgba(0,82,217,1) solid;
            border-radius: 50%;
            -webkit-animation: spCircRot .6s infinite linear;
            animation: spCircRot .6s infinite linear;
          }
          @-webkit-keyframes spCircRot {
            from { -webkit-transform: rotate(0deg); }
            to { -webkit-transform: rotate(359deg); }
          }
          @keyframes spCircRot {
            from { transform: rotate(0deg); }
            to { transform: rotate(359deg); }
          }`,
        }}
      />
      <div id="loading" className="weda-loading-container">
        <div className="logo">
          <div className="sp sp-circle"></div>
        </div>
        <div className="text" id="message">
          正在加载组件、编译代码...
        </div>
      </div>
    </>
  );
}

function ErrorStatus({ error }: { error: Error }) {
  return (
    <>
      <style
        type="text/css"
        dangerouslySetInnerHTML={{
          __html: `.weda-loading-container {
            position: fixed;
            left:0;
            top:0;
            width: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: column;
            height:100%;
            padding: 0 20px;
          }
          .weda-loading-container .logo {
            width: 100%;
            display: flex;
            justify-content: center;
            text-align: center;
            margin-bottom: 50px;
          }

          .weda-loading-container .sp{
            width: 48px;
            height:48px;
          }

          .weda-loading-container .sp-circle {
            border: 6px rgba(0,82,217,0.25) solid;
            border-top: 6px rgba(0,82,217,1) solid;
            border-radius: 50%;
            -webkit-animation: spCircRot .6s infinite linear;
            animation: spCircRot .6s infinite linear;
          }
          @-webkit-keyframes spCircRot {
            from { -webkit-transform: rotate(0deg); }
            to { -webkit-transform: rotate(359deg); }
          }
          @keyframes spCircRot {
            from { transform: rotate(0deg); }
            to { transform: rotate(359deg); }
          }`,
        }}
      />
      <div id="loading" className="weda-loading-container">
        <div className="logo">
          <img
            width="50"
            src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PG1hc2sgaWQ9ImEiIHN0eWxlPSJtYXNrLXR5cGU6YWxwaGEiIG1hc2tVbml0cz0idXNlclNwYWNlT25Vc2UiIHg9IjAiIHk9IjIiIHdpZHRoPSIxMCIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTS42MiAyLjE0M2g5LjMxNFYxNy44MkguNjJWMi4xNDN6IiBmaWxsPSIjZmZmIi8+PC9tYXNrPjxnIG1hc2s9InVybCgjYSkiPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOS45MzMgMTBsLTQuMzIgNy40ODRhLjY3Mi42NzIgMCAwMS0uOTE4LjI0NmwtMy40MDMtMS45NjRBMS4zNDQgMS4zNDQgMCAwMS42MiAxNC42VjUuNGMwLS40OC4yNTYtLjkyNC42NzItMS4xNjRsMy4zMTItMS45MTJjLjQxNS0uMjQuOTI4LS4yNCAxLjM0NCAwbDMuOTg1IDIuM0wxLjY0MSA5LjQyYS42NzIuNjcyIDAgMDAwIDEuMTYzTDQuNjA3IDEyLjNjLjQxNi4yNDIuOTMuMjQyIDEuMzQ2LjAwMWwzLjk4LTIuM3oiIGZpbGw9IiMwMEE4NzAiLz48L2c+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik05LjkzNCAxMGw0LjMyLTcuNDg0YS42NzIuNjcyIDAgMDEuOTE4LS4yNDZsMy40MDMgMS45NjRjLjQxNi4yNC42NzIuNjg0LjY3MiAxLjE2NVYxNC42YzAgLjQ4LS4yNTYuOTI0LS42NzIgMS4xNjRsLTMuMzEyIDEuOTEyYy0uNDE1LjI0LS45MjguMjQtMS4zNDQgMGwtMy45ODUtMi4zIDguMjkyLTQuNzk2YS42NzIuNjcyIDAgMDAwLTEuMTYzTDE1LjI2IDcuN2ExLjM0NCAxLjM0NCAwIDAwLTEuMzQ2IDBMOS45MzQgMTB6IiBmaWxsPSIjNDc4N0YwIi8+PC9zdmc+"
          />
        </div>
        <div className="text" id="message">
          {error.message}
        </div>
        {error.stack && (
          <div className="text" id="stack" style={{ whiteSpace: 'pre-line', width: '100%', wordBreak: 'break-all' }}>
            {error.stack.split('\n').slice(0, 5).join('\n')}
          </div>
        )}
      </div>
    </>
  );
}

export const InjectContext = React.createContext({});

type TLoadDependencies = (
  dependencies: any[],
  cache?: boolean,
) => Promise<{
  controlMap: {
    /**
     * libname@libversion
     */
    [nameWithVersion: string]: {
      /**
       * 组件库描述版本 '' | ‘2.0.0’ | ‘3.0.0’
       */
      schemaVersion?: string;
      /**
       * 组件定义描述
       */
      components: {
        type: string;
        /**
         * 组件名
         */
        name: string;
        /**
         * 属性配置
         */
        dataForm: {
          [prop: string]: {
            syncProps?: {
              changeEvent: string;
              valueFromEvent: string;
            };
            /**
             * @deprecated
             */
            inputProp?: {
              changeEvent: string;
              valueFromEvent: string;
            };
          };
        };
        /**
         * 事件配置
         */
        emitEvents: {
          /**
           * 事件标识
           */
          eventName: string;
          /**
           * 事件名
           */
          name: string;
        }[];
        isContainer?: boolean;
        meta?: {
          name: string;
        };
      }[];
    };
  };
  componentsMap: {
    /**
     * libname:componentName
     */
    [componentSourceKey: string]: React.FunctionComponent;
  };
  componentActionsMap: {
    /**
     * libname:componentName
     */
    [actionSourceKey: string]: Function;
  };
}>;

async function defaultloadDependencies(dependencies, cache?: boolean) {
  injectExternals();
  return _loadDependencies({ dependencies }, { cache });
}
interface IWedaAppProps {
  cals: IApp;
  dependencies: any[];
  isHash?: boolean;
  historyOption?: HistoryOptions & { type?: string };
  /**
   * @private 勿用
   */
  _processDependencies?: (dependencies, cals) => any[];
  loadDependencies?: TLoadDependencies;
  /**
   * @deprecated
   * use loadDependencies
   */
  loadDependiences?: TLoadDependencies;
  loadLowcodes?: (cals) => Promise<{ [key: string]: any }>;
  renderLoading?: () => React.ReactElement;
  onRender?: () => void;
  onPageMounted?: () => void;
  context?: {
    basic?: boolean;
    looseError?: boolean;
    inIdeEditView?: boolean;
    checkVisible?: () => boolean;
    defaultDataContext?: any;
    dataContext?: any;
    onDataContextChange?: (id, data) => void;
    $page?: any;
    initAppAPI?: ({
      common,
      state,
      computedFuncs,
      dataset,
    }: {
      common: any;
      state: any;
      computedFuncs: any;
      dataset?: any;
    }) => void;
    removeAppAPI?: () => void;
    getDatasourceConfig?: (cals, deployOptions?: any) => any;
    checkPageAuth?: (app, appId, $page) => boolean;
  };
  ErrorBoundary?: typeof ReactErrorBoundary;
}

function _WedaApp({
  cals,
  dependencies = [],
  _processDependencies = (dependencies, cals) => {
    let map = {};
    let processedDependiencies = dependencies
      .map((item) => {
        if (!map[`${item.name}@${item.version}`]) {
          map[`${item.name}@${item.version}`] = item;
          return item;
        }
        return null;
      })
      .filter((item) => !!item);

    let usedComponentMap = getUsedComps([cals], processedDependiencies);
    return processedDependiencies.filter((item) => item.name === OFFICIAL_LIB_KEY || !!usedComponentMap[item.name]);
  },
  isHash = false,
  historyOption = {},
  loadDependencies,
  loadDependiences,
  loadLowcodes = async (cals) => {
    const codeModulesMap = { global: cals.resources as ICodeResource[] };
    const styleModulesMap = { global: { resources: codeModulesMap.global } };
    cals.items.forEach((page) => {
      if (page.id) {
        codeModulesMap[page.id] = page.resources || [];
        styleModulesMap[page.id] = {
          resources: codeModulesMap[page.id],
        };
      }
    });

    const [styleCodesMap, { state, computedFuncs, common, lifeCycle, handler }] = await Promise.all([
      generateStyleModuleMap(styleModulesMap),
      generateLowcodeModules({
        codeModulesMap,
        runtimeWindow: runtime,
        externals: {
          react: 'window.React',
          'react-dom': 'window.ReactDOM',
        },
      }).catch((e) => {
        e.message = `[低码模块加载失败] ${e.absoluteReqPath ? `[${e.absoluteReqPath}]` : ''} ${e.message}`;
        throw e;
      }),
    ]);

    const keys = [
      'global',
      ...cals.items
        .map((page) => {
          return page.id;
        })
        .filter((item) => !!item),
    ];

    const lowcodeModuleMap: { [key: string]: any } = {};

    keys.forEach((id) => {
      lowcodeModuleMap[id] = {
        state: state[id] || {},
        computed: computedFuncs[id] || {},
        common: common[id] || undefined,
        lifecycle: lifeCycle[id] || {},
        handler: handler[id] || {},
        style: styleCodesMap[id] || [],
      };
    });

    return lowcodeModuleMap;
  },
  renderLoading = () => <Loading />,
  onPageMounted,
  onRender = () => {},
  context = {
    basic: false,
    looseError: false,
    inIdeEditView: false,
    initAppAPI: initGlobalVar,
    removeAppAPI: removeGlobalVar,
    getDatasourceConfig: _getConfig,
    // selectedComponentSchemaPath: 'id9',
    // checkVisible: () => true,
    // SlotRender: (props) => {
    //   const { id: slotId } = props;
    //   const isEmptyChildren =
    //     !props.children || (props.children as any).length === 0;
    //   const { selectedComponentSchemaPath } = React.useContext(
    //     InjectContext
    //   ) as any;
    //   return (
    //     <div
    //       className={classnames('_wa-comp-slot-wrapper', {
    //         '_wa-comp-inst-container': true,
    //         [`_wa-comp-id-${slotId}`]: true,
    //         [`_wa-comp-inst`]: true, // 插槽不支持拖拽
    //         '_wa-comp-selected': slotId === selectedComponentSchemaPath,
    //         // @ts-ignore
    //         '_wa-comp-container-empty-children': isEmptyChildren,
    //       })}
    //       title={`插槽: ${slotId}`}
    //       onClick={(e) => {
    //         e.stopPropagation();
    //         console.log(slotId);
    //         // setComponentSchemaPath({ selected: slotId });
    //       }}
    //     >
    //       {isEmptyChildren ? <div className="after" /> : props.children}
    //     </div>
    //   );
    // },
  },
}: IWedaAppProps) {
  const [lowcodeLoaded, setLowcodeLoaded] = React.useState(false);
  const [lowcodeError, setLowcodeError] = React.useState(null);
  const [dependenciesLoaded, setDependenciesLoaded] = React.useState(false);
  const [componentsMap, setComponentsMap] = React.useState({});
  const [componentActionsMap, setComponentActionsMap] = React.useState({});
  const [lowcodeModuleMap, setLowcodeModuleMap] = React.useState<{
    [pageId: string]: any;
  }>({});
  const [componentsInfoMap, setComponentsInfoMap] = React.useState({});
  const processedDependiencies = React.useMemo(() => _processDependencies(dependencies, cals), [dependencies, cals]);
  const loadedDependienceMapRef = React.useRef({});
  const _loadDependencies = loadDependencies || loadDependiences || defaultloadDependencies;

  const {
    looseError = false,
    initAppAPI = initGlobalVar,
    removeAppAPI = removeGlobalVar,
    getDatasourceConfig = _getConfig,
  } = context || {};

  // 数据源初始化
  React.useEffect(() => {
    let isMount = true;

    const datasourceConfig = getDatasourceConfig(cals, { looseError: context.looseError });
    // const time = `==========lowcode ${Date.now()}`;
    app.id = (cals as any).id || (cals as any).appId || '';
    app.domain = (cals.extra as any)?.domain || '';
    app.__internal__.getConfig = () => {
      return {
        id: app.id,
        envId: cals.extra?.envId || (cals as any).envId,
        clientId: cals.extra?.clientId,
        loginConfigVersion: cals.extra?.loginConfigVersion,
        staticResourceDomain: app.domain,
        envVersion: datasourceConfig.isProd ? 'production' : 'preview',
        pages: cals.items
          .map((page, index) => ({
            id: page.id,
            isHome: !cals.extra?.rootPath && (cals.main ? page.id === cals.main : index === 0),
            type: page?.attributes?.isCustomLoginPage ? 'login' : '',
            reachBottomDistance: page?.attributes?.reachBottomDistance,
          }))
          .sort((item) => (item.isHome ? -1 : 1))
          .map((item) => ({
            id: item.id,
            type: item.type,
            reachBottomDistance: item.reachBottomDistance,
          })),
      };
    };

    // 设置数据源请求的 loading 及 toast 处理
    const config = {
      ...datasourceConfig,
      beforeDSRequest: (cfg) => {
        if (!cfg.options || !cfg.options.showLoading) return;
        app.showLoading();
      },
      beforeCallFunction: async (params) => {
        if (context.inIdeEditView) {
          return params;
        }
        try {
          const { loginConfigVersion } = app.__internal__.getConfig();
          let skip = false;
          switch (params?.data?.methodName) {
            case 'callWedaApi': {
              if (
                ['GetMiniProgramUserTicket', 'DescribeRuntimeResourceStrategy'].includes(params?.data?.params.action)
              ) {
                skip = true;
              }
              break;
            }
          }
          // 后续做过滤处理
          if (!loginConfigVersion || (params?.data?.mode === 'c' && skip)) {
            return params;
          }
          const { accessToken } = await getAccessToken();
          if (accessToken) {
            params.data.accessToken = accessToken;
          }
        } catch (e) {
          console.error('beforeCallFunction error', e);
        }
        return params;
      },
      afterDSRequest: (cfg, error, result) => {
        if (!cfg.options) return;
        if (cfg.options.showLoading) app.hideLoading();
        if (!cfg.options.showToast) return;
        const isSuccess = !error && result && !result.code;
        app.showToast({ icon: isSuccess ? 'success' : 'error' });
      },
      async afterCallFunction(params, error, res) {
        if (context.inIdeEditView) {
          return;
        }
        if (
          params?.data?.params?.action != 'DescribeRuntimeResourceStrategy' &&
          ['InnerError.AuthFailure', 'InvalidAccessToken'].includes(res?.result?.code)
        ) {
          const { loginConfigVersion } = app.__internal__.getConfig();
          if ((window?._WedaHostConfig as any)?.logout) {
            app.showToast({
              title: '接口无访问权限',
              icon: 'error',
            });
            setTimeout(() => {
              (window._WedaHostConfig as any).logout();
            }, 300);
          } else if (loginConfigVersion) {
            const authConfig = await getAuthConfig();
            const isAnonymous = await checkAnonymous();
            if (isAnonymous && authConfig.RejectStrategy == 'to_login') {
              redirectToLogin();
            } else if (authConfig.RejectStrategy == 'show_warning') {
              app.showToast({
                title: '接口无访问权限',
                icon: 'error',
              });
            }
          }
        }
      },
    };
    setConfig(config);
    if (!context.basic) {
      initTcb();
    }

    loadLowcodes(cals).then(
      (lowcodeModuleMap) => {
        if (isMount) {
          initAppAPI?.({
            common: lowcodeModuleMap.global.common,
            state: lowcodeModuleMap.global.state,
            computedFuncs: lowcodeModuleMap.global.computed,
          });

          // 初始化应用生命周期
          initAppLifeCycle(app, {
            ...lowcodeModuleMap.global.lifecycle,
            beforeCustomLaunch: async (query) => {
              EXTRA_API.setParams('$global', query || {});
              await createStateDataSourceVar('$global', generateParamsParser({ app, $page: {} }));
            },
          });

          setLowcodeModuleMap(lowcodeModuleMap);
          setLowcodeLoaded(true);
          setLowcodeError(null);
          // console.timeEnd(time);
        }
      },
      (e) => {
        if (!context.looseError) {
          setLowcodeError(e);
        } else {
          console.error(e);
        }
        setLowcodeLoaded(true);
      },
    );
    return () => {
      isMount = false;
      removeAppAPI?.();
      cleanLowcodeContext();
      removeAppLifeCycle();
      // 变量先更新， 然后 create 先更新
      // 然后 remove 了
      // 然后 mount 了
      // 然是没有新 create， 这里先不清理了
      // removeHistory();
    };
  }, []);

  React.useEffect(() => {
    // const timeTag = `==========depen ${Date.now()}`;
    // console.time(timeTag);
    if (processedDependiencies.find((item) => !loadedDependienceMapRef.current[item.name])) {
      _loadDependencies(processedDependiencies).then(({ controlMap, componentsMap, componentActionsMap }) => {
        processedDependiencies.forEach((item) => {
          loadedDependienceMapRef.current[item.name] = true;
        });

        setComponentsMap(componentsMap);
        setComponentActionsMap(componentActionsMap);
        setComponentsInfoMap(
          getComponentsInfo(
            processedDependiencies.map((item) => {
              const control = controlMap[`${item.name}@${item.version}`];
              let overwrite = {};
              if (control) {
                if (control.schemaVersion) {
                  overwrite['schemaVersion'] = control.schemaVersion;
                }
                if (control.components) {
                  overwrite['components'] = control.components;
                }
              }
              return {
                ...item,
                ...overwrite,
              };
            }),
          ),
        );

        // console.timeEnd(timeTag);
        setDependenciesLoaded(true);
      });
    } else {
      setDependenciesLoaded(true);
      // if (!componentsMap) {
      //   setComponentsMap({});
      // }
      // if (!componentActionsMap) {
      //   setComponentActionsMap({});
      // }
      // if (!componentsInfoMap) {
      //   setComponentsInfoMap({});
      // }
    }
  }, [processedDependiencies]);

  const basename = historyOption.basename || (cals.extra?.appConfig as any)?.window?.basename || '/';

  return lowcodeLoaded && dependenciesLoaded ? (
    lowcodeError ? (
      <ErrorStatus error={lowcodeError} />
    ) : (
      <>
        <style
          type="text/css"
          dangerouslySetInnerHTML={{
            __html: `${(lowcodeModuleMap.global?.style || []).map((item) => item.code).join('\n')}
              @media screen and (min-width: 1024px) {
                body {
                  height: 100%;
                }
              }
              `,
          }}
        />
        <InjectContext.Provider value={{ ...context, componentsInfoMap }}>
          <WedaRouter
            historyOptions={{ basename, isHash, type: historyOption?.type }}
            routes={cals.items.map((page, index) => ({
              id: page.id,
              isHome: !cals.extra?.rootPath && (cals.main ? page.id === cals.main : index === 0),
              path: cals.extra?.rootPath ? `/${cals.extra?.rootPath}/${page.id}` : `/${page.id}`,
              root: cals.extra?.rootPath || '',
              render: () => {
                return (
                  <PageRender
                    page={page}
                    generateComponentsMap={() => componentsMap}
                    generateActionsMap={() => componentActionsMap}
                    generateLowcodeModule={() => {
                      return lowcodeModuleMap[page.id] || {};
                    }}
                    generateContext={(ctx, page) => {
                      const { widgets, componentSchema, dataBinds } = getComponentSchema(
                        ctx,
                        page,
                        false,
                        componentsInfoMap,
                        undefined,
                        { looseError: context.looseError },
                      );
                      return {
                        widgetContext: widgets,
                        componentSchema,
                        dataBinds,
                      };
                    }}
                    onMounted={onPageMounted}
                    onRender={onRender}
                    initDataContext={context?.defaultDataContext}
                    injectContext={{
                      $page: context.$page,
                      dataContext: context.dataContext,
                      onDataContextChange: context.onDataContextChange,
                    }}
                    options={{
                      looseError: context.looseError,
                      disabledCheckAuth: context.inIdeEditView,
                    }}
                  />
                );
              },
            }))}
            onNotFound={() => {
              const onNotFound = lowcodeModuleMap?.global?.lifecycle?.onAppPageNotFound;
              if (onNotFound) {
                onNotFound();
              }
              console.error('页面 404，可以在生命周期配置 onAppPageNotFound 并跳转到对应页面');
            }}
          />
        </InjectContext.Provider>
      </>
    )
  ) : (
    renderLoading()
  );
}

export default function WedaApp(
  props: {
    ErrorBoundary?: typeof ReactErrorBoundary;
  } & IWedaAppProps,
) {
  const { ErrorBoundary = ReactErrorBoundary, ...restProps } = props;

  return (
    <ErrorBoundary FallbackComponent={ErrorStatus}>
      <_WedaApp {...restProps} />
    </ErrorBoundary>
  );
}

export function WedaRouter({
  historyOptions,
  routes = [],
  onNotFound,
}: {
  routes?: {
    isHome?: boolean;
    root?: string;
    id: string;
    path: string;
    component?: React.FunctionComponent;
    render?: (props) => React.ReactElement;
  }[];
  historyOptions: {
    basename: string;
    isHash: boolean;
    type: string;
  };
  onNotFound?: any;
}) {
  const { basename, isHash, type } = historyOptions;

  const history = React.useMemo(() => {
    return createHistory(
      {
        basename,
      },
      isHash ? 'hash' : type,
    );
  }, [basename, isHash]);

  // let [state, setState] = React.useState({
  //   action: history.action,
  //   location: history.location,
  // });
  // React.useLayoutEffect(() => history.listen(setState), [history]);
  const home = routes.find((route) => !!route.isHome);
  if (runtime['$$global']) {
    runtime['$$global'].homePageId = home?.id || '';
  }

  return (
    <Router history={history}>
      <React.Suspense fallback={<Loading />}>
        <Switch>
          {home && (
            <Route path={`/${home.root || ''}`} exact>
              <Redirect to={home.path} />
            </Route>
          )}
          {routes.map((route) => {
            return <Route key={route.path} path={route.path} render={route.render} />;
          })}
          <Route>
            <NotFound onNotFound={onNotFound} />
          </Route>
        </Switch>
      </React.Suspense>
    </Router>
  );
}

async function editProcess({ controlMap, componentsMap, dependencies }) {
  const componentInfo = getComponentsInfo(
    dependencies.map((item) => {
      const control = controlMap[`${item.name}@${item.version}`];
      let overwrite = {};
      if (control) {
        if (control.schemaVersion) {
          overwrite['schemaVersion'] = control.schemaVersion;
        }
        if (control.components) {
          overwrite['components'] = control.components;
        }
      }
      return {
        ...item,
        ...overwrite,
      };
    }),
  );
  return {
    controlMap,
    componentsMap: Object.entries(componentsMap).reduce(
      (map, [sourceKey, Component]: [string, React.FunctionComponent]) => {
        map[sourceKey] = (props) => {
          const injectContext = React.useContext(InjectContext) as any;
          const { className = '', ...restProps } = props;
          const [module, componentName] = sourceKey.split(':');

          const baseClassName = [];
          const isContainer = componentInfo[sourceKey]?.isContainer;
          const isContainerEmptyChildren = isContainer && props.children?.length == 0;
          if (isContainer) {
            baseClassName.push('_wa-comp-inst-container');
          }
          if (isContainerEmptyChildren) {
            baseClassName.push('_wa-comp-container-empty-children');
          }

          return (
            <Component
              {...restProps}
              className={classnames(props.className || '', {
                '_wa-editor-selected-disable': props.forIndexes?.[0] > 0,
                [`_wa-comp-id-${props.id}`]: true,
                '_wa-comp-inst': true,
                'weda-draggable': true,
                _fe: true,
                [`_wa-comp-name-${componentName}`]: true,
                [`_sk-${sourceKey.replace(/:/, '__')}`]: true,
                '_wa-comp-inst-container': isContainer,
                '_wa-comp-container-empty-children': isContainerEmptyChildren,
                '_wa-comp-selected': injectContext?.selectedComponentSchemaPath === props.id,
              })}
            />
          );
        };
        return map;
      },
      {},
    ),
  };
}
