import { RuntimeError } from '@ibiz-template/core';
import {
  IPluginFactory,
  IPluginItem,
  RemotePluginConfig,
  RemotePluginItem,
} from '@ibiz-template/runtime';
import { IPSAppPFPluginRef, IPSSysPFPlugin } from '@ibiz/dynamic-model-api';
import path from 'path-browserify';
import Vue, { VueConstructor, PluginObject } from 'vue';

const { join } = path;

/**
 * 插件工具类
 *
 * @author chitanda
 * @date 2022-10-21 16:10:29
 * @export
 * @class PluginFactory
 */
export class PluginFactory implements IPluginFactory {
  /**
   * 是否为 http || https 开头
   *
   * @author chitanda
   * @date 2022-11-07 14:11:28
   * @protected
   */
  protected urlReg = /^http[s]?:\/\/[^\s]*/;

  /**
   * 是否已经加载过文件缓存
   *
   * @author chitanda
   * @date 2022-10-31 14:10:17
   * @protected
   * @type {Map<string, boolean>}
   */
  protected cache: Map<string, boolean> = new Map();

  /**
   * 本地开发测试包,只在本地开发生效
   *
   * @author chitanda
   * @date 2022-11-02 21:11:41
   * @protected
   */
  protected devPackages: Map<string, () => Promise<unknown>> = new Map();

  /**
   * 插件缓存
   *
   * @author chitanda
   * @date 2022-10-31 14:10:28
   * @protected
   * @type {Map<string, RemotePluginItem>}
   */
  protected pluginCache: Map<string, RemotePluginItem> = new Map();

  /**
   * 预定义插件集合
   *
   * @author chitanda
   * @date 2023-03-09 17:03:46
   * @protected
   * @type {Map<string, IPluginItem>}
   */
  protected predefinedPlugins: Map<string, IPluginItem> = new Map();

  /**
   * 在预置加载清单的插件
   *
   * @author chitanda
   * @date 2023-04-02 23:04:40
   * @type {string[]}
   */
  readonly appPlugins: string[] = [];

  /**
   * 注册视图默认插件
   *
   * @author chitanda
   * @date 2023-02-06 21:02:10
   * @param {IPluginItem} plugin
   */
  registerPredefinedPlugin(plugin: IPluginItem): void {
    this.predefinedPlugins.set(plugin.name, plugin);
  }

  /**
   * 给入应用实例,将已经加载的过插件注入。主要用于多实例的情况
   *
   * @author chitanda
   * @date 2023-02-02 16:02:51
   * @param {App} app
   */
  register(_app: VueConstructor): void {}

  /**
   * 加载预置插件
   *
   * @author chitanda
   * @date 2023-03-09 18:03:48
   * @param {string} name
   * @return {*}  {Promise<void>}
   */
  async loadPredefinedPlugin(name: string): Promise<void> {
    if (this.predefinedPlugins.has(name)) {
      const plugin = this.predefinedPlugins.get(name)!;
      if (plugin) {
        await this.loadPluginRef(plugin.name, plugin.path);
      }
    }
  }

  /**
   * 插件刚加载完成回来,设置到目前所有的 vue 实例当中
   *
   * @author chitanda
   * @date 2023-02-02 17:02:38
   * @protected
   * @param {PluginObject<unknown>} code
   */
  protected setPluginCode(code: PluginObject<unknown>): void {
    Vue.use(code);
  }

  /**
   * 设置开发插件,用于本地调试
   *
   * @author chitanda
   * @date 2022-11-02 21:11:56
   * @param {string} name
   * @param {() => Promise<unknown>} fn
   */
  setDevPlugin(name: string, fn: () => Promise<unknown>): void {
    this.devPackages.set(name, fn);
  }

  /**
   * 加载插件
   *
   * @author chitanda
   * @date 2022-10-31 14:10:13
   * @param {IPSSysPFPlugin} plugin
   * @return {*}  {Promise<boolean>}
   */
  async loadPlugin(plugin: IPSSysPFPlugin): Promise<boolean> {
    if (plugin.runtimeObject === true) {
      const pluginRef = plugin as unknown as IPSAppPFPluginRef;
      if (pluginRef) {
        try {
          await this.loadPluginRef(
            pluginRef.rTObjectName,
            pluginRef.rTObjectRepo,
          );
        } catch (error) {
          ibiz.log.error(error);
        }
      }
    }
    return false;
  }

  /**
   * 加载应用插件
   *
   * @author chitanda
   * @date 2022-10-31 16:10:57
   * @param {IPSAppPFPluginRef} pluginRef
   * @return {*}  {Promise<boolean>}
   */
  async loadPluginRef(
    rtObjectName: string,
    rtObjectRepo: string,
  ): Promise<boolean> {
    if (this.pluginCache.has(rtObjectName)) {
      return true;
    }
    let configData: unknown = null;
    const fn = this.devPackages.get(rtObjectName);
    if (fn) {
      configData = await fn();
    } else {
      const pluginPath: string = rtObjectRepo;
      const configUrl = this.urlReg.test(pluginPath)
        ? `${pluginPath}/package.json`
        : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`;
      const res = await ibiz.net.axios({
        method: 'get',
        headers: { 'Access-Control-Allow-Origin': '*' },
        url: configUrl,
      });
      if (res.status !== 200) {
        throw new Error(`配置加载失败`);
      }
      configData = res.data;
    }
    const remotePlugin = new RemotePluginItem(
      rtObjectName,
      rtObjectRepo,
      configData as RemotePluginConfig,
    );
    if (remotePlugin) {
      try {
        await this.loadScript(remotePlugin);
        this.pluginCache.set(rtObjectName, remotePlugin);
        return true;
      } catch (error) {
        ibiz.log.error(error);
      }
    }
    return false;
  }

  /**
   * 加载插件
   *
   * @author chitanda
   * @date 2022-11-02 14:11:31
   * @protected
   * @param {RemotePluginItem} remotePlugin
   * @return {*}  {Promise<void>}
   */
  protected async loadScript(remotePlugin: RemotePluginItem): Promise<void> {
    const pluginPath: string = remotePlugin.repo;
    const { name, version, system, module } = remotePlugin.config;
    let scriptUrl = '';
    if (this.devPackages.has(name)) {
      scriptUrl = join(`${name}@${version}`, module);
    } else if (ibiz.env.dev) {
      scriptUrl = this.urlReg.test(pluginPath)
        ? `${pluginPath}/${join(module)}`
        : `${ibiz.env.pluginBaseUrl}/${pluginPath}/${join(module)}`;
    } else {
      scriptUrl = `${pluginPath}${pluginPath.endsWith('/') ? '' : '/'}${join(
        system,
      )}`;
    }
    if (scriptUrl) {
      if (this.cache.has(scriptUrl)) {
        return;
      }
      let data: IParams | null = null;
      const url = this.parseUrl(scriptUrl);
      // 本地开发模式下的包引用加载
      if (this.devPackages.has(name)) {
        const fn = this.devPackages.get(name)!;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const pluginModule = (await fn()) as any;
        this.setPluginCode(pluginModule.default);
        this.cache.set(scriptUrl, true);
      } else if (ibiz.env.dev) {
        data = await import(scriptUrl /* @vite-ignore */);
      } else {
        System.addImportMap({
          imports: {
            [name]: url,
          },
        });
        data = await System.import(name);
      }
      if (data) {
        if (data.default) {
          this.setPluginCode(data.default);
        } else {
          throw new RuntimeError(
            `远程插件加载失败, 远程插件未找到[default]默认导出`,
          );
        }
        this.cache.set(scriptUrl, true);
      } else {
        throw new RuntimeError(
          `远程插件加载失败, 未找到文件或文件内容格式不正确`,
        );
      }
    }
  }

  /**
   * 编译请求文件地址
   *
   * @author chitanda
   * @date 2022-10-31 14:10:19
   * @protected
   * @param {string} script
   * @return {*}  {string}
   */
  protected parseUrl(script: string): string {
    if (this.urlReg.test(script)) {
      return script;
    }
    let url: string = '';
    if (this.urlReg.test(ibiz.env.pluginBaseUrl)) {
      if (script.startsWith('/')) {
        url = ibiz.env.pluginBaseUrl + script;
      } else {
        url = `${ibiz.env.pluginBaseUrl}/${script}`;
      }
    } else {
      url = `${join(ibiz.env.pluginBaseUrl, script)}`;
    }
    const { origin, pathname } = window.location;
    if (pathname.endsWith('/') && url.startsWith('/')) {
      url = url.substring(1);
    }
    url = `${origin}${pathname}${url}`;
    return url;
  }
}