import * as React from 'react';
import { Router, Route, Redirect, Switch } from 'react-router-dom';
import PageRender from '../pages/index';
import { IAppAPI, app, $w as baseAPI } 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, generateDatasetQuery, IComponentInfo } from '../utils/adapter/generate';
import {
  loadDependencies as _loadDependencies,
  injectExternals,
  loadExternalResources as _loadExternalResources,
  loadRuntimeDependencies,
  upsertFavicon,
  loadMockDependencies,
  WrapComponentFieldWithPropsHook,
} from '../utils/adapter/dependencies';
import {
  createHistory,
  getAuthConfig,
  HistoryOptions,
  redirectToLogin,
  dealCache,
  checkAnonymous,
  defaultGenerateLoginUrl,
  GenerateLoginUrlType,
  CALC_RPX_ROOT_VAL_NAME,
} from '../utils/index';
import { initGlobalVar, removeGlobalVar } from '../utils/initGlobalVar';
import { initAppLifeCycle, removeAppLifeCycle } from '../utils/lifecycle';
import { ICodeResource, IPlatformApp, IApp, getUsedComps, parseVersion, OFFICIAL_LIB_KEY } 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 { start } from 'qiankun';
import './index.css';
import {
  isEnableWecom,
  setEnableObserverRef,
  setEnablePageRoot,
  setEnableWecom,
  simpleCheckAdminPortal,
  getNetworkType,
} from '../utils/common';
import { OwnerComponentContext, useHistoryMemo } from '../utils/hooks';
import { debounce } from 'lodash-es';
import { generateCompositeComponent } from '../pages/comositeComponent';
import { APP_TEMPLATE_KEY } from '../utils/constants';
const { initTcb, setConfig, createStateDataSourceVar, generateParamsParser, EXTRA_API } = WEDA_CLOUD_SDK;

const { getAccessToken } = auth;

const STATUS_STYLE = `.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); }
}`;

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

  return (
    <>
      <style
        type="text/css"
        dangerouslySetInnerHTML={{
          __html: STATUS_STYLE,
        }}
      />
      <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">
          当前页面 {path} 无法访问，可能由于网址已删除或尚未生效
        </div>
      </div>
    </>
  );
}

function Loading({ message = '正在加载组件、编译代码...' }: { message?: string }) {
  const [visible, setVisible] = React.useState(false);
  React.useEffect(() => {
    setTimeout(() => {
      setVisible(true);
    }, 100);
  }, []);
  return (
    visible && (
      <>
        <style
          type="text/css"
          dangerouslySetInnerHTML={{
            __html: STATUS_STYLE,
          }}
        />
        <div id="loading" className="weda-loading-container">
          <div className="logo">
            <div className="sp sp-circle"></div>
          </div>
          <div className="text" id="message">
            {message}
          </div>
        </div>
      </>
    )
  );
}

function ErrorStatus({ error }: { error: Error }) {
  return (
    <>
      <style
        type="text/css"
        dangerouslySetInnerHTML={{
          __html: STATUS_STYLE,
        }}
      />
      <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: IComponentInfo[];
    };
  };
  componentsMap: {
    /**
     * libname:componentName
     */
    [componentSourceKey: string]: React.FunctionComponent;
  };
  componentActionsMap: {
    /**
     * libname:componentName
     */
    [actionSourceKey: string]: Function;
  };
}>;

async function defaultloadDependencies(dependencies, cache?: boolean) {
  injectExternals();
  return _loadDependencies({ dependencies }, { cache });
}

export interface IWedaAppProps {
  cals: IApp | IPlatformApp;
  dependencies: any[];
  isHash?: boolean;
  historyOption?: HistoryOptions & { type?: string; _indexPage?: boolean };
  /**
   * @private 勿用
   */
  _processDependencies?: (dependencies, cals) => any[];
  loadDependencies?: TLoadDependencies;
  /**
   * @deprecated
   * use loadDependencies
   */
  loadDependiences?: TLoadDependencies;
  loadLowcodes?: (cals) => Promise<{ [key: string]: any }>;
  loadExternalResources?: (
    data: { externalResource?: IPlatformApp['externalResources'] },
    options?: { cache: boolean },
  ) => Promise<any>;
  renderLoading?: (props: { message?: string }) => React.ReactElement;
  onRender?: () => void;
  onPageMounted?: () => void;
  context?: {
    basic?: boolean;
    looseError?: boolean;
    checkVisible?: () => boolean;
    checkDisplay?: () => boolean;
    defaultDataContext?: any;
    dataContext?: any;
    onDataContextChange?: (id, data) => void;
    $page?: any;
    initAppAPI?: (app: IAppAPI) => void;
    removeAppAPI?: () => void;
    getDatasourceConfig?: (cals, deployOptions?: any) => any;
    checkPageAuth?: (app, appId, $page) => boolean;
    reuseWidget?: boolean;
    generateLoginUrl?: GenerateLoginUrlType;
    SlotRender?: any;
    VisiblePlaceHolder?: any;
  };
  ErrorBoundary?: typeof ReactErrorBoundary;
}
const _WedaApp = React.forwardRef((props: IWedaAppProps, ref) => {
  const {
    cals,
    loadExternalResources = _loadExternalResources,
    renderLoading = ({ message }) => <Loading message={message} />,
  } = props;
  const [externalResourceLoaded, setExternalResourceLoaded] = React.useState(false);
  React.useEffect(() => {
    loadExternalResources({ externalResources: (cals as IPlatformApp).externalResources || [] }, { cache: true }).then(
      () => {
        setExternalResourceLoaded(true);
      },
    );
  }, []);

  return externalResourceLoaded ? (
    <WedaAppContent {...props} ref={ref}></WedaAppContent>
  ) : (
    renderLoading({ message: '加载外部资源...' })
  );
});
_WedaApp.displayName = '_WedaApp';

const WedaAppContent = React.forwardRef(
  (
    {
      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 FEATURE_MAP = parseVersion(cals.schemaVersion, []);
        const processCssUnit: 'rem' | 'rpx' = FEATURE_MAP.defaultDynamicCssUnit ? 'rem' : 'rpx';
        const codeModulesMap = { global: cals.resources as ICodeResource[] };
        const styleModulesMap = { global: { resources: codeModulesMap.global, processCssUnit } };
        cals.items.forEach((page) => {
          if (page.id) {
            codeModulesMap[page.id] = page.resources || [];
            styleModulesMap[page.id] = {
              resources: codeModulesMap[page.id],
              processCssUnit,
            };
          }
        });

        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 = ({ message }) => <Loading message={message} />,
      onPageMounted,
      onRender = () => {},
      context = {
        basic: false,
        looseError: false,
        reuseWidget: false,
        // 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,
    ref,
  ) => {
    const [lowcodeLoaded, setLowcodeLoaded] = React.useState(false);
    const [lowcodeError, setLowcodeError] = React.useState(null);
    const [dependenciesLoaded, setDependenciesLoaded] = React.useState(false);
    const [beforeAppLoadDone, setBeforeAppLoadDone] = 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<Record<string, IComponentInfo>>({});
    const processedDependiencies = React.useMemo(() => _processDependencies(dependencies, cals), [dependencies, cals]);
    const loadedDependienceMapRef = React.useRef({});
    const _loadDependencies = loadDependencies || loadDependiences || defaultloadDependencies;
    const isMountRef = React.useRef(false);
    const {
      looseError = false,
      initAppAPI = (app) => {},
      removeAppAPI = removeGlobalVar,
      getDatasourceConfig = _getConfig,
      generateLoginUrl = defaultGenerateLoginUrl,
    } = context || {};

    /**
     * 在 page lifecycle 之前就创建 history 对象
     */
    const history = useHistoryMemo({
      basename: historyOption.basename || cals.extra?.appConfig?.window?.basename || '/',
      isHash,
      type: historyOption.type,
    });

    const applyLowcodes = React.useCallback(
      (cals, patch = false) => {
        return loadLowcodes(cals).then(
          (lowcodeModuleMap) => {
            if (isMountRef.current) {
              removeAppAPI?.();
              cleanLowcodeContext();
              removeAppLifeCycle();

              if (lowcodeModuleMap.global) {
                initGlobalVar(
                  {
                    common: lowcodeModuleMap.global.common,
                    state: lowcodeModuleMap.global.state,
                    computedFuncs: lowcodeModuleMap.global.computed,
                  },
                  initAppAPI,
                );

                app.dataset.query = generateDatasetQuery(
                  cals.dataset?.query || {},
                  { $w: app.__internal__.$w, $app: app, $page: null },
                  { isComposite: false, useScopeWidgetData: true, looseError },
                );

                // 初始化应用生命周期
                initAppLifeCycle(
                  app,
                  {
                    ...lowcodeModuleMap.global.lifecycle,
                    beforeCustomLaunch: async (query) => {
                      try {
                        EXTRA_API.setParams('$global', query || {});
                        /**
                         * 全局模型变量的初始化
                         * 阻塞流程
                         */
                        await createStateDataSourceVar(
                          '$global',
                          generateParamsParser({ app, $page: {}, $w: app?.__internal__?.$w } as any),
                        );

                        // eslint-disable-next-line no-restricted-syntax
                        for (const queryId in cals.dataset?.query || {}) {
                          if (cals.dataset.query[queryId]?.trigger === 'auto') {
                            try {
                              app.dataset?.query?.[queryId]?.trigger?.();
                            } catch (e) {
                              console.error(`query ${queryId} 初始化失败：`, e);
                            }
                          }
                        }
                      } catch (e) {
                        throw e;
                      } finally {
                        setBeforeAppLoadDone(true);
                      }
                    },
                  },
                  !!patch,
                );
              } else {
                setBeforeAppLoadDone(true);
              }

              if (patch) {
                setLowcodeModuleMap((current) => {
                  return {
                    ...current,
                    ...lowcodeModuleMap,
                  };
                });
              } else {
                setLowcodeModuleMap(lowcodeModuleMap);
              }
              setLowcodeLoaded(true);
              setLowcodeError(null);
              // console.timeEnd(time);
            }
          },
          (e) => {
            if (!context.looseError) {
              setLowcodeError(e);
            } else {
              console.error(e);
            }
            setBeforeAppLoadDone(true);
            setLowcodeLoaded(true);
          },
        );
      },
      [loadLowcodes, initAppAPI, removeAppAPI],
    );

    React.useImperativeHandle(
      ref,
      () => {
        return {
          applyLowcodes,
        };
      },
      [applyLowcodes],
    );

    React.useEffect(() => {
      const adminPortal = simpleCheckAdminPortal();
      if (!adminPortal) {
        start();
      }
      if (!(window as any)?.__WEDA_LOG_LEVEL__) {
        try {
          (window as any).__WEDA_LOG_LEVEL__ = localStorage.getItem('__WEDA_LOG_LEVEL__');
        } catch (e) {}
      }
    }, []);

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

      const datasourceConfig = getDatasourceConfig(cals, { looseError: context.looseError });

      /**
       * 初始化 feature map
       */
      if (isEnableWecom(datasourceConfig.envID)) {
        setEnableWecom(true);
      } else {
        setEnableWecom(false);
      }

      // const time = `==========lowcode ${Date.now()}`;
      app.id = datasourceConfig.appID;
      app.domain = cals.extra?.domain || '';
      app.label = app.label || cals?.label;
      app.version = app.version || cals?.version || '';

      // 初始化系统变量
      const isMobile = app?.__internal__?.isMobile;
      // 设备信息
      baseAPI.device.viewport = {
        width: runtime.innerWidth,
        height: runtime.innerHeight,
      };
      if (isMobile?.()) {
        baseAPI.device.networkType = getNetworkType(runtime);
      } else {
        baseAPI.device.networkType = '';
      }

      const { _deployType } = runtime as any;
      if (datasourceConfig.isPrivate && ['hybrid', 'hybrid-private'].includes(_deployType)) {
        app.domain = location.hostname;
      }

      app.__internal__.getConfig = () => {
        return {
          id: app.id,
          label: app.label,
          version: app.version,
          envId: datasourceConfig.envID,
          clientId: datasourceConfig.tcbClientId,
          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.basic) {
            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({ title: '', icon: isSuccess ? 'success' : 'error' });
        },
        async afterCallFunction(params, error, res) {
          if (context.basic) {
            return;
          }

          const oauthError =
            error?.message?.includes('PERMISSION_DENIED') ||
            error?.code === 'unauthenticated' ||
            error?.error === 'unauthenticated' ||
            error?.code === 'invalid_grant' ||
            error?.error === 'invalid_grant' ||
            error?.code === 'INVALID_ACCESS_TOKEN' ||
            (error?.code === 'OPERATION_FAIL' && /^\[INVALID_ACCESS_TOKEN\]/.test(error?.message || ''));

          if (
            params?.data?.params?.action !== 'DescribeRuntimeResourceStrategy' &&
            (['InnerError.AuthFailure', 'InvalidAccessToken'].includes(res?.result?.code) || oauthError)
          ) {
            const { loginConfigVersion } = app.__internal__.getConfig();
            if ((window?._WedaHostConfig as any)?.logout) {
              dealCache({ key: 'RENDER_ACCESS', type: 'remove' });
              app.showToast({
                title: '接口无访问权限',
                icon: 'error',
              });
              if (['InvalidAccessToken'].includes(res?.result?.code) || oauthError) {
                setTimeout(() => {
                  (window._WedaHostConfig as any).logout();
                }, 300);
              }
            } else if (loginConfigVersion) {
              const authConfig = await getAuthConfig();
              if (authConfig.RejectStrategy === 'to_login') {
                if (['InnerError.AuthFailure'].includes(res?.result?.code)) {
                  const isAnonymous = await checkAnonymous();
                  if (!isAnonymous) return;
                  // 匿名用户越权去登录
                }
                redirectToLogin(generateLoginUrl);
              } else if (authConfig.RejectStrategy === 'show_warning') {
                if (oauthError) {
                  const isAnonymous = await checkAnonymous();
                  if (!isAnonymous) {
                    redirectToLogin(generateLoginUrl);
                    return;
                  }
                }
                app.showToast({
                  title: '接口无访问权限',
                  icon: 'error',
                });
              }
            }
          }
        },
      };
      setConfig(config);
      /**
       * debug 编辑器 无envId 需要跳过 tcb 初始化
       * 理想做法应该debug编辑器主动声明是不初始化资源的
       */
      if (!context.basic && config.envID) {
        initTcb();
      } else if (!context.checkPageAuth) {
        context.checkPageAuth = () => true;
      }

      applyLowcodes(cals);
      upsertFavicon(cals?.extra?.favicon, app, runtime.document);
      return () => {
        isMountRef.current = false;
        removeAppAPI?.();
        cleanLowcodeContext();
        removeAppLifeCycle();
        // 变量先更新， 然后 create 先更新
        // 然后 remove 了
        // 然后 mount 了
        // 然是没有新 create， 这里先不清理了
        // removeHistory();
      };
    }, []);

    const FEATURE_MAP = React.useMemo(() => {
      return parseVersion(cals.schemaVersion, []);
    }, [cals.schemaVersion]);

    React.useEffect(() => {
      // const timeTag = `==========depen ${Date.now()}`;
      // console.time(timeTag);
      if (processedDependiencies.find((item) => !loadedDependienceMapRef.current[item.name])) {
        Promise.all([
          _loadDependencies(processedDependiencies),
          loadRuntimeDependencies(),
          loadMockDependencies(),
        ]).then(async ([dependenceMap = {}, runtimeDependenceMap = {}, mockDependencies = {}]) => {
          processedDependiencies.forEach((item) => {
            loadedDependienceMapRef.current[item.name] = true;
          });

          function mergeDependencies(list) {
            const keys = ['controlMap', 'componentsMap', 'componentActionsMap'];
            return list.reduce(
              (map, dependenceMap) => {
                for (const key of keys) {
                  map[key] = {
                    ...map[key],
                    ...dependenceMap[key],
                  };
                }
                return map;
              },
              {
                controlMap: {},
                componentsMap: {},
                componentActionsMap: {},
              },
            );
          }

          const { enableObserverRef, enablePageRoot } = parseVersion(
            cals?.schemaVersion,
            processedDependiencies as any,
          );
          setEnableObserverRef(enableObserverRef);
          setEnablePageRoot(enablePageRoot);

          const { controlMap, componentsMap, componentActionsMap } = mergeDependencies([
            runtimeDependenceMap,
            dependenceMap,
            mockDependencies,
          ]);

          const componentsInfoMap = getComponentsInfo(
            processedDependiencies.map((item) => {
              const control = controlMap[`${item.name}@${item.version}`];
              let overwrite: any = {};
              if (control) {
                if (control.schemaVersion) {
                  overwrite.schemaVersion = control.schemaVersion;
                }
                if (control.components) {
                  overwrite.components = control.components;
                }
              }
              return {
                ...item,
                ...overwrite,
              };
            }),
          );

          const templateComponents = (
            await Promise.all(
              (cals as IPlatformApp).templates?.map?.(async (item) => {
                try {
                  const sourceKey = `${APP_TEMPLATE_KEY}:${item.name}`;
                  const component = WrapComponentFieldWithPropsHook({
                    Field: await generateCompositeComponent({
                      comp: item,
                      module: OFFICIAL_LIB_KEY,
                      componentsMap,
                      componentActionsMap,
                      componentsInfoMap,
                      options: {
                        looseError: true,
                        processCssUnit: (FEATURE_MAP.defaultDynamicCssUnit ? 'rem' : 'px') as 'rem' | 'px',
                      },
                    }),
                    isPlainProps: false,
                    sourceKey,
                  });
                  const componentInfo = {
                    name: item.name,
                    title: item.name,
                    compConfig: item.compConfig,
                    dataForm: (item as any).dataForm || item.data?.properties || {},
                    emitEvents:
                      (item as any).emitEvents ||
                      item.events?.map((item) => {
                        return { eventName: item.name, name: item.title };
                      }),
                    isComposite: true,
                    /**
                     * default value
                     */
                    isContainer: false,
                    platform: [],
                    inputProps: undefined,
                    shortcut: undefined,
                    properties: undefined,
                  };
                  return { sourceKey, component, componentInfo };
                } catch (e) {}
              }) || [],
            )
          ).filter((item) => !!item);

          templateComponents?.forEach(({ sourceKey, component, componentInfo }) => {
            componentsMap[sourceKey] = component;
            componentsInfoMap[sourceKey] = componentInfo;
          });

          setComponentsMap(componentsMap);
          setComponentActionsMap(componentActionsMap);
          setComponentsInfoMap(componentsInfoMap);

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

    const { options: pageOptions } = React.useMemo(() => {
      return {
        options: {
          looseError: context.looseError,
          reuseWidget: context.reuseWidget,
          processCssUnit: (FEATURE_MAP.defaultDynamicCssUnit ? 'rem' : 'px') as 'rem' | 'px',
        },
      };
    }, [context.$page, context.dataContext, context.onDataContextChange, context.looseError, context.reuseWidget]);

    const ratio = React.useMemo(() => {
      function flex() {
        try {
          let htmlDom = runtime.document.documentElement;
          let width = runtime.innerWidth || htmlDom.clientWidth;
          let fontSize = width / (375 / 14);
          if (
            width > 768 &&
            !navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|IEMobile)/i)
          ) {
            return 2;
            // htmlDom.style.setProperty(CALC_RPX_ROOT_VAL_NAME, '2');
          } else {
            return 28 / fontSize;
            // htmlDom.style.setProperty(CALC_RPX_ROOT_VAL_NAME, `${28 / fontSize}`);
          }
        } catch (e) {
          console.error(e);
        }
        return 2;
      }

      return flex();
      // runtime.addEventListener('resize', flex);
      // return () => {
      //   runtime.removeEventListener('resize', flex);
      // };
    }, []);

    return lowcodeLoaded && dependenciesLoaded && beforeAppLoadDone ? (
      lowcodeError ? (
        <ErrorStatus error={lowcodeError} />
      ) : (
        <>
          <style
            type="text/css"
            dangerouslySetInnerHTML={{
              __html: `body {
                ${CALC_RPX_ROOT_VAL_NAME}: ${ratio}
              }
              `,
            }}
          />

          <style
            id="wd-app-style-tag"
            type="text/css"
            dangerouslySetInnerHTML={{
              __html: `${(lowcodeModuleMap.global?.style || []).map((item) => item.code).join('\n')}
              @media screen and (min-width: 768px) {
                body {
                  height: 100%;
                }
              }
              `,
            }}
          />
          <InjectContext.Provider value={{ processCssUnit: pageOptions.processCssUnit, ...context, componentsInfoMap }}>
            <WedaRouter
              history={history}
              routes={cals.items.map((page, index) => {
                let path = cals.extra?.rootPath ? `/${cals.extra?.rootPath}/${page.id}` : `/${page.id}`;
                if (historyOption?._indexPage) {
                  path += '/';
                }
                return {
                  id: page.id,
                  isHome: !cals.extra?.rootPath && (cals.main ? page.id === cals.main : index === 0),
                  path,
                  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,
                            componentsInfoMap,
                            undefined,
                            {
                              isComposite: false,
                              looseError: pageOptions.looseError,
                              processCssUnit: pageOptions.processCssUnit,
                            },
                          );
                          return {
                            widgetContext: widgets,
                            componentSchema,
                            dataBinds,
                          };
                        }}
                        onMounted={onPageMounted}
                        onRender={onRender}
                        initDataContext={context?.defaultDataContext}
                        options={pageOptions}
                        renderLoading={renderLoading}
                      />
                    );
                  },
                };
              })}
              onNotFound={() => {
                const onNotFound = lowcodeModuleMap?.global?.lifecycle?.onAppPageNotFound;
                if (onNotFound) {
                  onNotFound();
                }
                console.error('页面 404，可以在生命周期配置 onAppPageNotFound 并跳转到对应页面');
              }}
            />
          </InjectContext.Provider>
        </>
      )
    ) : (
      renderLoading({ message: '正在加载组件、编译代码...' })
    );
  },
);
WedaAppContent.displayName = 'WedaAppContent';

const WedaApp = React.forwardRef(
  (
    props: {
      ErrorBoundary?: typeof ReactErrorBoundary;
    } & IWedaAppProps,
    ref,
  ) => {
    const { ErrorBoundary = ReactErrorBoundary, cals, ...restProps } = props;
    const resetErrorBoundaryRef = React.useRef(() => {});
    const errorRef = React.useRef(null);
    const debounceRestError = React.useCallback(
      debounce(() => {
        if (errorRef.current) {
          resetErrorBoundaryRef.current?.();
        }
      }),
      [],
    );

    React.useEffect(() => {
      debounceRestError();
    }, [cals]);

    return (
      <ErrorBoundary
        FallbackComponent={({ error, resetErrorBoundary }) => {
          errorRef.current = error;
          resetErrorBoundaryRef.current = React.useMemo(() => resetErrorBoundary, [resetErrorBoundary]);
          return <ErrorStatus error={error} />;
        }}
      >
        <_WedaApp cals={cals} {...restProps} ref={ref} />
      </ErrorBoundary>
    );
  },
);
WedaApp.displayName = 'WedaApp';

export default WedaApp;

export function WedaRouter({
  history,
  routes = [],
  onNotFound,
}: {
  routes?: {
    isHome?: boolean;
    root?: string;
    id: string;
    path: string;
    component?: React.FunctionComponent;
    render?: (props) => React.ReactElement;
  }[];
  history: any;
  onNotFound?: any;
}) {
  // 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 || '';
  }

  const context = React.useMemo(() => ({ attached: true }), []);
  return (
    <OwnerComponentContext.Provider value={context}>
      <Router history={history}>
        <React.Suspense fallback={<Loading />}>
          <Switch>
            {home && (
              <Route
                path={`/${home.root || ''}`}
                exact
                render={({ location }) => {
                  return (
                    <Redirect
                      to={{
                        ...location,
                        pathname: home.path,
                      }}
                    />
                  );
                }}
              />
            )}
            {routes.map((route) => {
              // 采用 render 的方式 企业工作台内路由会有两次变化
              // 从而两次 render page 导致 widgets 引用异常
              // __POWERED_BY_QIANKUN__
              const Component = route.render;
              const adminPortal = simpleCheckAdminPortal();
              return (
                <Route key={route.path} path={route.path} render={adminPortal ? undefined : Component}>
                  {adminPortal ? <Component /> : null}
                </Route>
              );
            })}
            <Route>{({ location }) => <NotFound path={location?.pathname} onNotFound={onNotFound} />}</Route>
          </Switch>
        </React.Suspense>
      </Router>
    </OwnerComponentContext.Provider>
  );
}

async function editProcess({ controlMap, componentsMap, dependencies }) {
  const componentInfo = getComponentsInfo(
    dependencies.map((item) => {
      const control = controlMap[`${item.name}@${item.version}`];
      let overwrite: any = {};
      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: any = React.useContext(InjectContext);
          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;
      },
      {},
    ),
  };
}
