// Import Libs and Handlers
import * as React from 'react';
import { autorun, observable, reaction } from 'mobx';
import { get, isEqual } from 'lodash-es';
import { app, createPageApi, IPageAPI } from '../app/global-api'; // 取对应子包app
import { AppRender } from '../handlers/RenderWrapper';
import { _WEDA_CLOUD_SDK as WEDA_CLOUD_SDK } from '@cloudbase/weda-client';
import { generateEmit } from '../utils';
import { bindFuncObj, checkAuth, createComputed, createWidgets, retryDataBinds, toCssText } from '../utils/index';
import { usePageLifeCycle } from '../utils/lifecycle';
import { useScrollTop } from '../utils/hooks';
import { IComponent, IPageComponent, processCommonStyle2CSSProperties } from '@cloudbase/cals';
import { runtime } from '../utils/adapter';
import { loadMicroApp } from 'qiankun';
import { InjectContext } from '../router';

type TPage = IPageComponent | IComponent;

interface ILowcodeModuleMap {
  state?: any;
  computed?: { [key: string]: Function };
  lifecycle: { [key: string]: Function };
  handler: { [key: string]: Function };
  renderer: { [key: string]: Function };
  style?: { code: string }[];
}

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

// Import Actions

interface IAppProps {
  page: TPage;
  generateLowcodeModule: () => ILowcodeModuleMap;
  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: TPage; default: Function }) => void;
  onRender?: () => void;
  initDataContext?: any;
  injectContext?: {
    $page?: any;
    dataContext?: any;
    onDataContextChange?: (id, data) => void;
  };
  options?: {
    reuseWidget?: boolean;
    looseError?: boolean;
  };
}

// Init
// eslint-disable-next-line react/display-name
export default React.memo(App, (pre, next) => {
  const reuseWidget = next.options?.reuseWidget;
  const deepEqualKey = reuseWidget ? 'page' : '';
  for (let key in next) {
    if (key !== deepEqualKey && next[key] !== pre[key]) {
      return false;
    }
  }
  if (next[deepEqualKey] && !isEqual(next[deepEqualKey], pre[deepEqualKey])) {
    return false;
  }
  return true;
});

function App({
  page,
  generateComponentsMap,
  generateActionsMap,
  generateLowcodeModule,
  generateWidgetsContext,
  generateDataBinds,
  generateComponentSchema,
  generateContext,
  onRender,
  onMounted,
  injectContext = {},
  initDataContext = {},
  options = { reuseWidget: false, looseError: false },
}: IAppProps) {
  const [pageContextInited, setPageContextInited] = React.useState(false);
  const [_, setFourceUpdate] = React.useState(Date.now());
  // 数据容器的上下文数据
  const dataContextRef = React.useRef(observable(injectContext?.dataContext || initDataContext));
  const dataContext = dataContextRef.current;
  const widgetsContextRef = React.useRef<any>({});

  React.useEffect(() => {
    if (injectContext?.dataContext) {
      if (dataContextRef.current) {
        for (const key in dataContextRef.current) {
          if (!injectContext.dataContext[key]) {
            dataContextRef.current[key] = null;
          }
        }
      }

      for (const key in injectContext.dataContext) {
        dataContextRef.current[key] = injectContext.dataContext[key];
      }

      console.log('<<<<<<<<<<<<<<<<<<<<<<<< dataContext change');
      setFourceUpdate(Date.now());
    }
  }, [injectContext?.dataContext]);

  // 兼容 this.state / $page.state 两种模式
  const pageCodeContextRef = React.useRef(createPageApi());
  const $page = pageCodeContextRef.current;
  const lowcode = generateLowcodeModule();

  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,
        computed: createComputed(lowcode.computed || {}, pageCodeContextRef.current),
        handler: bindFuncObj(lowcode.handler || {}, pageCodeContextRef.current),
        randerer: lowcode.renderer,
        dataset,
      });
    } else {
      Object.assign($page, {
        id: page.id,
        state: injectContext.$page.state,
        computed: injectContext.$page.computed,
        handler: injectContext.$page.handler,
        randerer: injectContext.$page.randerer,
        dataset: injectContext.$page.dataset,
      });
    }
    setPageContextInited(true);
  }, []);

  const { componentSchema, widgetsContext } = React.useMemo(() => {
    if (pageContextInited) {
      const globalCtx = {
        $page,
        app,
        $app: app,
        actionsMap: generateActionsMap?.() || {},
      };
      let widgetsContext, databinds, 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 };
    }
  }, [pageContextInited, page]);

  React.useMemo(() => {
    if (pageContextInited) {
      const { widgets, nodeMap } = createWidgets(
        options.reuseWidget
          ? {
              widgets: widgetsContextRef.current.widgets,
              widgetProps: widgetsContextRef.current.widgetProps,
              nodeMap: widgetsContextRef.current.nodeMap,
            }
          : {},
        widgetsContext,
        $page._dataBinds || {},
        dataContext,
      );
      $page.widgets = widgets;
      widgetsContextRef.current = { widgetProps: widgetsContext, nodeMap, widgets };
      retryDataBinds();
    }
  }, [widgetsContext, dataContext]);

  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) => {
    // eslint-disable-next-line react/prop-types
    props?.default?.();
  },
  onRender = () => {},
  dataContext,
  updateContext,
  options,
}: {
  page: TPage;
  lowcode: ILowcodeModuleMap;
  componentSchema: any;
  pageCodeContext: IPageAPI;
  virtualFields: {
    [key: string]: React.FunctionComponent;
  };
  onMounted?: (props: { page: TPage; default: Function }) => void;
  onRender?: () => void;
  dataContext?;
  updateContext?;
  options?: {
    looseError?: 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;
  const { checkPageAuth = checkAuth } = React.useContext(InjectContext) as any;

  // Web 环境页面级别生命周期
  usePageLifeCycle(
    {
      ...lowcode?.lifecycle,
      events: getPageEvents(componentSchema),
      // eslint-disable-next-line @typescript-eslint/no-misused-promises
      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 = await checkPageAuth(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,
            },
            {
              fetch: runtime.fetch,
            },
          );
        } else {
          microApp.current.mount?.();
        }
      } else if (microApp.current) {
        microApp.current.unmount();
      }
    }
  }, [weDaHasLogin]);

  React.useEffect(() => {
    (app.__internal__ as any).activePage = pageCodeContext;
    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 || processCommonStyle2CSSProperties(page?.extra?.commonStyle || {}),
            'body',
          )}${(lowcode.style || []).map((item) => item.code).join('\n')}`,
        }}
      />

      {weDaHasLogin &&
        !isPure &&
        (lowcode.lifecycle?.onPageRender ? (
          lowcode.lifecycle?.onPageRender()
        ) : (
          <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 };
}

function getPageEvents(componentSchema) {
  const emit = generateEmit();

  return new Proxy(
    {},
    {
      get: (target, eventName) => {
        return (eventData) => emit(eventName, componentSchema.listenerInstances, eventData, {}, {}, {}, {});
      },
    },
  );
}
