// Import Libs and Handlers
import * as React from 'react';
import { observable, autorun, reaction } from 'mobx';
import { get } from 'lodash-es';
import { app, IPageAPI } from '../app/global-api'; // 取对应子包app
import { AppRender } from '../handlers/RenderWrapper';
import { _WEDA_CLOUD_SDK as WEDA_CLOUD_SDK } from '@cloudbase/weda-client';

import {
  createComputed,
  createWidgets,
  retryDataBinds,
  bindFuncObj,
  checkAuth,
  toCssText,
} from '../utils/index';
import { toCssStyle } from '../utils/adapter/generate';
import { usePageLifeCycle } from '../utils/lifecycle';
import { useScrollTop } from '../utils/hooks';
import { createPageApi } from '../app/global-api'; // 取主包app
import { IPageComponent, IComponent } from '@cloudbase/cals';
import { runtime } from '../utils/adapter/index';
import { loadMicroApp } from 'qiankun';

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

// Import Actions

// Init
export default function App({
  page,
  generateComponentsMap,
  generateActionsMap,
  generateLowcodeModule,
  generateWidgetsContext,
  generateDataBinds,
  generateComponentSchema,
  generateContext,
  onRender,
  onMounted,
  injectContext = {},
  initDataContext = {},
  options = { looseError: false, disabledCheckAuth: false },
}: {
  page: IComponent | IPageComponent;
  generateLowcodeModule: () => {
    state?: any;
    computed?: { [key: string]: Function };
    lifecycle: { [key: string]: Function };
    handler: { [key: string]: Function };
    style?: { code: string }[];
  };
  generateComponentsMap: () => { [key: string]: React.FunctionComponent };
  generateActionsMap: () => { [key: string]: Function };
  generateWidgetsContext?: (page) => any;
  generateDataBinds?: (ctx, page) => any;
  generateComponentSchema?: (ctx, page) => any;
  generateContext?: (
    ctx,
    page
  ) => { widgetContext; dataBinds; componentSchema };
  onMounted?: (props: { page; default: Function }) => void;
  onRender?: () => void;
  initDataContext?: any;
  injectContext?: {
    $page?: any;
    dataContext?: any;
    onDataContextChange?: (id, data) => void;
  };
  options?: {
    looseError?: boolean;
    disabledCheckAuth?: boolean;
  };
}) {
  const [pageContextInited, setPageContextInited] = React.useState(false);
  const [_, setFourceUpdate] = React.useState(Date.now());
  // 数据容器的上下文数据
  const dataContextRef = React.useRef(
    observable(injectContext?.dataContext || initDataContext)
  );
  const dataContext = dataContextRef.current;

  React.useEffect(() => {
    if (injectContext?.dataContext) {
      dataContextRef.current = observable(injectContext?.dataContext);
      setFourceUpdate(Date.now());
    }
  }, [injectContext?.dataContext]);

  // 兼容 this.state / $page.state 两种模式
  const pageCodeContextRef = React.useRef(
    injectContext?.$page || createPageApi()
  );
  const $page = pageCodeContextRef.current;
  const lowcode = generateLowcodeModule();
  let componentSchema, widgetsContext, databinds;

  React.useEffect(() => {
    if (!injectContext?.$page) {
      const dataset = createDataset(page.id);
      const state = observable(lowcode.state || {});
      state.dataset = dataset;
      Object.assign($page, {
        id: page.id,
        state: state,
        computed: createComputed(
          lowcode.computed || {},
          pageCodeContextRef.current
        ),
        handler: bindFuncObj(lowcode.handler || {}, pageCodeContextRef.current),
        dataset,
      });
    }
    setPageContextInited(true);
  }, []);

  if (pageContextInited) {
    const globalCtx = {
      $page,
      app,
      $app: app,
      actionsMap: generateActionsMap?.() || {},
    };
    if (generateContext) {
      let context = generateContext(globalCtx, { items: page.items });
      widgetsContext = context.widgetContext;
      databinds = context.dataBinds;
      componentSchema = context.componentSchema;
      // componentSchema = generateComponentSchema(globalCtx, page);
    } else {
      widgetsContext = generateWidgetsContext(page);
      databinds = generateDataBinds(globalCtx, page);
      componentSchema = generateComponentSchema(globalCtx, page);
    }

    // widgets 内的 dataBinds 可能需要关联 widgets，需要重新执行 dataBinds
    $page._dataBinds = databinds;
    $page.widgets = createWidgets(
      widgetsContext,
      $page._dataBinds || {},
      dataContext
    );
    retryDataBinds();
  }

  return (
    pageContextInited && (
      <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}
      />
    )
  );
}

function AppContent({
  page,
  lowcode,
  componentSchema,
  pageCodeContext,
  virtualFields,
  onMounted = (props) => {
    props?.default?.();
  },
  onRender = () => {},
  dataContext,
  updateContext,
  options,
}: {
  page: IPageComponent | IComponent;
  lowcode;
  componentSchema;
  pageCodeContext: IPageAPI;
  virtualFields: {
    [key: string]: React.FunctionComponent;
  };
  onMounted?: (props: { page; default: Function }) => void;
  onRender?: () => void;
  dataContext?;
  updateContext?;
  options?: {
    looseError?: boolean;
    disabledCheckAuth?: boolean;
  };
}) {
  // 检查权限
  const [weDaHasLogin, setWeDaHasLogin] = React.useState(false);
  const containerRef = React.useRef(null);
  const microApp = React.useRef(null);
  const pureSrc = (page.attributes as any)?.src;
  const isPure = !!pureSrc;

  // Web 环境页面级别生命周期
  usePageLifeCycle(
    {
      ...lowcode?.lifecycle,
      beforePageCustomLaunch: async (query) => {
        onMounted({
          page,
          default: () => {
            const document = runtime?.document || window.document;
            document.title = (page.attributes as any)?.title || page.id;
          },
        });
        EXTRA_API.setParams(page.id, query || {});
        initWatchMethods(pageCodeContext);
        let checkAuthResult = options?.disabledCheckAuth || await checkAuth(app, app.id, pageCodeContext);
        setWeDaHasLogin(checkAuthResult);
        createStateDataSourceVar(
          page.id,
          generateParamsParser({ app, $page: pageCodeContext })
        );
        onRender?.();
      },
    },
    { app, page: pageCodeContext, options: { looseError: options?.looseError } }
  );

  // 切换页面滚动到顶部
  useScrollTop();

  React.useEffect(() => {
    if (isPure) {
      if (weDaHasLogin) {
        if (!microApp.current) {
          microApp.current = loadMicroApp({
            name: page.id,
            entry: pureSrc,
            container: containerRef.current,
          });
        } else {
          microApp.current.mount?.();
        }
      } else if (microApp.current) {
        microApp.current.unmount();
      }
    }
  }, [weDaHasLogin]);

  React.useEffect(() => {
    return () => {
      if (microApp.current) {
        microApp.current.unmount();
      }
    };
  }, []);

  return (
    <div ref={containerRef} className={`weapps-page weapps-page-${page.id}`}>
      <style
        type="text/css"
        dangerouslySetInnerHTML={{
          __html: `${toCssText(
            page.attributes?.style ||
              toCssStyle(page?.extra?.commonStyle || {}),
            'body'
          )}${(lowcode.style || []).map((item) => item.code).join('\n')}`,
        }}
      />
      {weDaHasLogin && !isPure && (
        <AppRender
          virtualFields={virtualFields}
          componentSchema={componentSchema}
          codeContext={pageCodeContext}
          dataContext={dataContext}
          updateContext={updateContext}
        />
      )}
    </div>
  );
}

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 } = {} as any, 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 };
}
