/* eslint-disable react/display-name */
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 { IPlatformApp, EExternalResourceType } from '@cloudbase/cals';

import { useComponentProps } from '../hooks';
import { runtime as defaultRuntime } from './index';
import { REPEATER } from '../constants';
import * as WedaClientSDK from '@cloudbase/weda-client';
import { IComponentControl, IDataFrom } from './generate';

function loadUmdModule(
  jsUrl: string,
  umdModuleName: string,
  targetDoc: Document = document,
  cache = false,
  module = true,
) {
  return new Promise<any>((resolve, reject) => {
    const win = targetDoc.defaultView;
    if (cache) {
      if (module && Object.prototype.hasOwnProperty.call(win, umdModuleName)) {
        return resolve(win[umdModuleName as any]);
      }
      if (!module) {
        let scripts = Array.from(targetDoc.body.querySelectorAll('script'));
        if (scripts.find((item) => item?.className === umdModuleName)) {
          return resolve(null);
        }
      }
    }

    const script = targetDoc.createElement('script');
    script.setAttribute('src', jsUrl);
    script.setAttribute('class', umdModuleName);
    script.addEventListener('load', () => {
      if (module) {
        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);
        }
      } else {
        return resolve(null);
      }
    });

    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,
  module = true,
): Promise<void> {
  if (!url) {
    return Promise.resolve();
  }

  return new Promise((resolve) => {
    const className = module ? `@weda-materials-style-${bundleName}` : 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 upsertFavicon(favicon, app, entryDoc: Document = document) {
  if (!favicon) {
    return;
  }
  const { __internal__ = {} } = app || {};
  const { resolveStaticResourceUrl } = __internal__ || ({} as any);
  const href = resolveStaticResourceUrl(favicon);
  let links = Array.from(entryDoc.head.querySelectorAll('link[rel~="icon"]'));
  if (links.length) {
    links.forEach((link) => {
      (link as any).href = href;
    });
  } else {
    const link = entryDoc.createElement('link');
    link.type = 'image/x-icon';
    link.rel = 'shortcut icon';
    link.href = href;
    entryDoc.head.appendChild(link);
  }
}

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

  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 sourceKey = `${lib.name}:${componentName}`;
        componentsMap[sourceKey] = WrapComponentFieldWithPropsHook({
          Field: lib.components[componentName],
          isPlainProps,
          sourceKey,
        });
      }

      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,
        components:
          lib?.components?.map?.((item): IComponentControl => {
            /**
             * 序列化的 pick 修改需要同步修改 generator
             */
            const {
              name,
              dataForm,
              properties,
              emitEvents,
              events,
              meta,
              compConfig,
              inputProps,
              syncProps,
              isContainer,
            } = item;
            return {
              name,
              dataForm: Object.entries(dataForm || {}).reduce((map, [key, config]: [string, any]) => {
                const {
                  type,
                  ['x-slot-scope']: xSlotScope,
                  ['x-slot-directives']: xSlotDirectives,
                  syncProp,
                  inputProp,
                } = config;
                const filteredDataForm: IDataFrom = {
                  type,
                  'x-slot-scope': xSlotScope,
                  'x-slot-directives': xSlotDirectives,
                  syncProp,
                  inputProp,
                };
                map[key] = filteredDataForm;
                return map;
              }, {}),
              properties: properties
                ? {
                    ...properties,
                    properties: Object.entries(properties.properties || {}).reduce(
                      (map, [key, config]: [string, any]) => {
                        const { type, title } = config;

                        map[key] = {
                          type,
                          title,
                        };
                        return map;
                      },
                      {},
                    ),
                  }
                : undefined,
              emitEvents,
              events,
              meta: componentMetaFilter(meta),
              compConfig,
              inputProps,
              syncProps,
              isContainer,
            };
          }) || [],
      };
      return map;
    }, {});

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

  return {
    controlMap,
    componentsMap,
    componentActionsMap,
  };
}

export function componentMetaFilter(meta) {
  const { name: metaName, title, platform, shortcut } = meta || {};
  return meta ? { name: metaName, title, platform, shortcut } : undefined;
}

export function WrapComponentFieldWithPropsHook({ Field, isPlainProps, sourceKey }) {
  const name = Field.displayName || Field.name || 'Field';
  const processWrapper = (props, ref) => {
    const processedProps = useComponentProps(props, isPlainProps);
    return <Field {...processedProps} ref={ref} />;
  };
  processWrapper.displayName = `ForwordRef(${name})`;
  return React.forwardRef(processWrapper);
}

export async function loadExternalResources(
  {
    externalResources = [],
    runtime = defaultRuntime,
  }: { externalResources?: IPlatformApp['externalResources']; runtime?: Window },
  options?: { cache?: boolean },
) {
  const scriptPromises = [];
  const cssPromises = [];
  const cache = options?.cache;
  for (let externalResource of externalResources) {
    switch (externalResource.type) {
      case EExternalResourceType.JSUrl: {
        scriptPromises.push(
          loadUmdModule(externalResource.url, externalResource.url, runtime.document, cache, false).catch((e) => {
            console.warn(e);
            return null;
          }),
        );
        break;
      }
      case EExternalResourceType.CSSUrl: {
        cssPromises.push(loadCssModule(externalResource.url, externalResource.url, runtime.document, cache, false));
        break;
      }
    }
  }

  await Promise.all([...scriptPromises, ...cssPromises]);
}

export async function loadRuntimeDependencies() {
  return {
    controlMap: {},
    componentsMap: {
      [`${REPEATER.MODULE_NAME}:${REPEATER.REPEATER_NAME}`]: Repeater,
      [`${REPEATER.MODULE_NAME}:${REPEATER.REPEATER_ITEM_NAME}`]: Block,
    },
    componentActionsMap: {},
  };
}

export async function loadMockDependencies() {
  return {
    controlMap: {},
    componentsMap: {
      // ['weda_adamsyu:Slot']: MobxReactLite.observer(
      //   (props: any) => {
      //     console.log('slot component props>>>>>>>>>>>>>>>>>>', props);
      //     const { id, text, events, compositeParent, data } = props;
      //     const { name } = data;
      //     if (!compositeParent) {
      //       console.warn('Slot组件只能用于实现复合组件');
      //       // 兼容编辑器点击，可选中 Slot 组件
      //       return <div>{`Slot - ${name}`}</div>;
      //     }
      //     return compositeParent.props.data[name] || null;
      //   },
      //   { forwardRef: true },
      // ),
      // eslint-disable-next-line react/display-name
      // [`${OFFICIAL_LIB_KEY}:WdText`]: React.forwardRef((props: React.PropsWithChildren<{}>, ref) => {
      //   // eslint-disable-next-line react/prop-types
      //   const processedProps = useComponentProps(props as any, true);
      //   return <DebugText {...processedProps} ref={ref} />;
      // }),
    },
    componentActionsMap: {},
  };
}

const Repeater = React.forwardRef((props: React.PropsWithChildren<{}>, ref) => {
  const { data, children, $node } = useComponentProps(props as any, true);
  React.useImperativeHandle(
    ref,
    () => {
      return {
        data,
        items: $node.items,
      };
    },
    [data, $node.children],
  );
  return typeof children === 'function' ? children() : children;
});

Repeater.displayName = REPEATER.REPEATER_NAME;

const Block = React.forwardRef((props: React.PropsWithChildren<{}>, ref) => {
  // eslint-disable-next-line react/prop-types
  const { children } = props;
  return typeof children === 'function' ? children() : children;
});

Block.displayName = 'Block';

// eslint-disable-next-line react/display-name
const DebugText = React.forwardRef(
  (props: React.PropsWithChildren<any>, ref) => {
    // eslint-disable-next-line react/prop-types
    const { id, text, events } = props;
    const [value, setValue] = React.useState([]);

    // React.useImperativeHandle(
    //   ref,
    //   () => {
    //     return {
    //       test: value,
    //       text: '123',
    //       console: (...arg) => {
    //         console.log('id', id, ...arg);
    //       },
    //     };
    //   },
    //   [],
    // );

    // React.useEffect(() => {
    //   setInterval(() => {
    //     setValue(String(Math.random()));
    //     console.log('>>>>>>>>>>>>>>.');
    //     (ref as any).current.text = String(Math.random());
    //   }, 4000);
    // }, []);

    // const dispose = React.useMemo(() => {
    //   return Mobx.autorun((r) => {
    //     // r.trace();
    //     console.log('----------------');
    //     setValue(window?.$w?.wedaContext?.editorPlatforms);
    //   });
    // }, []);

    // React.useEffect(() => {
    //   return () => {
    //     dispose();
    //   };
    // }, []);

    return (
      <p id={id} onClick={(e) => events.tap({}, { originEvent: e })} data-testid="text">
        {String(value?.join(',') || '-')}
      </p>
    );
  },
  // { forwardRef: true },
);
