import * as React from 'react';
import * as ReactDom from 'react-dom';
import ReactDomServer from 'react-dom/server';
import * as MobxReactLite from 'mobx-react-lite';
import * as Mobx from 'mobx';
import { get as LodashGet, set as LodashSet, remove as LodashRemove } from 'lodash-es';
import * as History from 'history';
import * as ReactErrorBoundary from 'react-error-boundary';

import { resolveComponentProps } from '../index';
import { runtime as defaultRuntime } from './index';

function loadUmdModule(jsUrl: string, umdModuleName: string, targetDoc: Document = document, cache: boolean = false) {
  return new Promise<any>((resolve, reject) => {
    const win = targetDoc.defaultView;
    if (cache && Object.prototype.hasOwnProperty.call(win, umdModuleName)) {
      resolve(win[umdModuleName as any]);
    } else {
      const script = targetDoc.createElement('script');
      script.setAttribute('src', jsUrl);
      script.setAttribute('class', umdModuleName);
      script.addEventListener('load', () => {
        const win = targetDoc.defaultView;
        if (Object.prototype.hasOwnProperty.call(win, umdModuleName)) {
          return resolve(win[umdModuleName as any]);
        } else {
          const error = new Error(`Fail to load UMD module ${umdModuleName} from [${jsUrl}].`);
          return reject(error);
        }
      });

      script.addEventListener('error', (e) => {
        const error = new Error(`main bundle [${umdModuleName}] load failed from [${jsUrl}]: ${e?.message || ''}`);
        reject(error);
      });
      targetDoc.body.appendChild(script);
    }
  });
}

function loadCssModule(url: string, bundleName: string, entryDoc: Document = document, cache = false): Promise<void> {
  if (!url) {
    return Promise.resolve();
  }

  return new Promise((resolve) => {
    const className = `@weda-materials-style-${bundleName}`;
    if (cache) {
      let links = Array.from(entryDoc.head.querySelectorAll('link'));
      if (links.find((item) => item?.className === className)) {
        return resolve();
      }
    }

    const link = entryDoc.createElement('link');
    link.type = 'text/css';
    link.rel = 'stylesheet';
    link.className = className;
    link.href = url;
    link.addEventListener('load', () => resolve());
    link.addEventListener('error', (e) => {
      console.warn(`Load [${bundleName}] meta style failed, skip...`, e);
      resolve();
    });
    entryDoc.head.appendChild(link);
  });
}

export function injectExternals(runtime = defaultRuntime) {
  const externals = {
    // 兼容配置
    react: React,
    React: runtime['React'] || React,
    ReactDOM: runtime['ReactDOM'] || ReactDom,
    ReactDom,
    ReactDomServer: ReactDomServer,
    Mobx: runtime['mobx'] || Mobx,
    'mobx-react-lite': MobxReactLite,
    MobxReactLite,
    LodashGet,
    LodashRemove,
    LodashSet,
    ReactErrorBoundary,
    History,
  };

  for (const key in externals) {
    runtime[key] = externals[key];
  }
}

export async function loadDependencies(
  data:
    | {
        dependencies: any[];
        runtime?: Window;
      }
    | any[],
  options?: { cache: boolean } | boolean,
) {
  const controlPromises = [];
  const componentPromises = [];
  const cssPromises = [];

  const dependencies = Array.isArray(data) ? data : data.dependencies;
  const runtime = !Array.isArray(data) && data.runtime ? data.runtime : defaultRuntime;

  const cache = typeof options === 'boolean' ? options : options?.cache || false;

  dependencies.forEach((item) => {
    if (!item.isComposite) {
      controlPromises.push(
        loadUmdModule(
          item.controlJsPath || item.mainJsPath.replace(/\.main\.js/, '.control.js'),
          `@weapps-materials-control-${item.name}`,
          runtime.document,
          cache,
        )
          .then((res) => {
            res.name = item.name;
            res.version = item.version;
            return res;
          })
          .catch((e) => {
            console.warn(e);
            return null;
          }),
      );
    }
    componentPromises.push(
      loadUmdModule(item.mainJsPath, `@weapps-materials-main-${item.name}`, runtime.document, cache).then(
        (res) => {
          res.name = item.name;
          res.isComposite = item.isComposite;
          return res;
        },
        (e) => {
          console.warn(e);
          return null;
        },
      ),
    );
    cssPromises.push(loadCssModule(item.mainCssPath, item.name, runtime.document, cache));
  });
  const [controls, components] = await Promise.all(
    [controlPromises, componentPromises, cssPromises].map((arr) => Promise.all(arr)),
  );

  let res = formatDependiencesMap(components, controls);
  return res;
}

export const loadDependiences = loadDependencies;

export function formatDependiencesMap(components, controls) {
  const componentsMap = {};
  const componentActionsMap = {};

  components
    .filter((item) => !!item)
    .forEach((lib) => {
      let { schemaVersion = '' } = lib;
      let isPlainProps = true;

      if (
        lib.isComposite ||
        isNaN(Number(schemaVersion.split('.')?.[0])) ||
        Number(schemaVersion.split('.')?.[0]) < 3
      ) {
        isPlainProps = false;
      }

      for (const componentName in lib.components || {}) {
        const Field = lib.components[componentName];
        const name = Field.displayName || Field.name || 'Field';
        const processWrapper = (props, ref) => {
          const processedProps = resolveComponentProps(props, isPlainProps);
          return <Field {...processedProps} ref={ref} />;
        };
        processWrapper.displayName = `ForwordRef(${name})`;
        componentsMap[`${lib.name}:${componentName}`] = React.forwardRef(processWrapper);
      }

      for (const actionName in lib.actions) {
        componentActionsMap[`${lib.name}:${actionName}`] = lib.actions[actionName];
      }
    });

  const controlMap = controls
    .filter((item) => !!item)
    .reduce((map, lib) => {
      map[`${lib.name}@${lib.version}`] = lib;
      return map;
    }, {});

  // console.log('controlMap', controlMap);
  // console.log('componentsMap', componentsMap);

  return {
    controlMap,
    componentsMap,
    componentActionsMap,
  };
}
