import lg from '@ali/vu-logger';
import { ComponentClass, ComponentType } from 'react';
import Prototype from './prototype';
import { designer } from '../editor';

function basename(name: string) {
  return name ? (/[^\/]+$/.exec(name) || [''])[0] : '';
}

function getCamelName(name: string) {
  const words = basename(name)
    .replace(/^((vc)-)?(.+)/, '$3')
    .split('-');
  return words.reduce((s, word) => s + word[0].toUpperCase() + word.substring(1), '');
}

export interface ComponentProtoBundle {
  // @ali/vc-xxx
  name: string;
  componentName?: string;
  category?: string;
  module: Prototype | Prototype[];
}

export interface ComponentViewBundle {
  // @ali/vc-xxx
  name: string;
  // alias to property name
  componentName?: string;
  category?: string;
  module: ComponentType<any>;
}

export default class Bundle {
  static createPrototype = Prototype.create;
  static addGlobalPropsReducer = Prototype.addGlobalPropsReducer;
  static addGlobalPropsConfigure = Prototype.addGlobalPropsConfigure;
  static addGlobalExtraActions = Prototype.addGlobalExtraActions;
  static removeGlobalPropsConfigure = Prototype.removeGlobalPropsConfigure;
  static overridePropsConfigure = Prototype.overridePropsConfigure;
  static create(protos: ComponentProtoBundle[], views?: ComponentViewBundle[]) {
    return new Bundle(protos, views);
  }

  private viewsMap: { [componentName: string]: ComponentType } = {};
  private registry: { [componentName: string]: Prototype } = {};
  private prototypeList: Prototype[] = [];

  constructor(protos?: ComponentProtoBundle[], views?: ComponentViewBundle[]) {
    // 注册 prototypeView 视图
    if (views && Array.isArray(views)) {
      this.recursivelyRegisterViews(views);
    }
    protos?.forEach((item) => {
      const prototype = item.module;
      if (prototype instanceof Prototype) {
        this.revisePrototype(item, prototype);
        const componentName = item.componentName || prototype.getComponentName()!;
        const matchedView = this.viewsMap[componentName] || null;
        if (!prototype.getView() && matchedView) {
          prototype.setView(matchedView);
        }
        this.registerPrototype(prototype);
      } else if (Array.isArray(prototype)) {
        this.recursivelyRegisterPrototypes(prototype, item);
      }
    });
  }

  getFromMeta(componentName: string): Prototype {
    if (this.registry[componentName]) {
      return this.registry[componentName];
    }
    const meta = designer.getComponentMeta(componentName);
    const prototype = Prototype.create(meta);
    this.prototypeList.push(prototype);
    this.registry[componentName] = prototype;
    return prototype;
  }

  removeComponentBundle(componentName: string) {
    const cIndex = this.prototypeList.findIndex((proto) => proto.getComponentName() === componentName);
    delete this.registry[componentName];
    this.prototypeList.splice(cIndex, 1);
  }

  getList() {
    return this.prototypeList;
  }

  get(componentName: string) {
    return this.registry[componentName];
  }

  replacePrototype(componentName: string, cp: Prototype) {
    const view: any = this.get(componentName).getView();
    this.removeComponentBundle(componentName);
    this.registry[cp.getComponentName()!] = cp;
    this.prototypeList.push(cp);
    cp.setView(view);
  }

  /**
   * TODO dirty fix
   */
  addComponentBundle(bundles: any) {
    /**
     * Normal Component bundle: [ Prototype, PrototypeView ]
     * Component without Prototype.js: [ View ]
     */
    if (bundles.length >= 2) {
      const prototype = bundles[0];
      const prototypeView = bundles[1];
      prototype.setView(prototypeView);
      this.registerPrototype(prototype);
    }
  }

  private recursivelyRegisterViews(list: any[], viewName?: string): void {
    list.forEach((item: any) => {
      if (Array.isArray(item.module)) {
        return this.recursivelyRegisterViews(item.module, item.name);
      } else if (Array.isArray(item)) {
        return this.recursivelyRegisterViews(item, viewName);
      }
      let viewDetail: ComponentClass;
      if (item.module && typeof item.module === 'function') {
        viewDetail = item.module;
      } else {
        viewDetail = item;
      }
      if (!viewDetail.displayName) {
        lg.log('ERROR_NO_PROTOTYPE_VIEW');
        lg.error('WARNING: the package without displayName is', item);
        viewDetail.displayName = getCamelName(viewName || item.name);
      }
      (viewDetail as any)._packageName_ = viewName || item.name;
      this.viewsMap[viewDetail.displayName] = viewDetail;
    });
  }

  private recursivelyRegisterPrototypes(list: any[], cp: ComponentProtoBundle) {
    const propList: ComponentProtoBundle[] = list;
    propList.forEach((proto: ComponentProtoBundle, index: number) => {
      if (Array.isArray(proto)) {
        this.recursivelyRegisterPrototypes(proto, cp);
        return;
      }
      if (proto instanceof Prototype) {
        const componentName = proto.getComponentName()!;
        if (!proto.getView() && this.viewsMap[componentName]) {
          proto.setView(this.viewsMap[componentName]);
        }
        if (cp.name && !proto.getPackageName()) {
          proto.setPackageName(cp.name);
        }
        this.registerPrototype(proto);
      }
    });
  }

  private revisePrototype(item: ComponentProtoBundle, prototype: Prototype) {
    if (item.category) {
      prototype.setCategory(item.category);
    }
    if (item.name && !prototype.getPackageName()) {
      prototype.setPackageName(item.name);
    }
  }

  private registerPrototype(prototype: Prototype) {
    const componentName = prototype.getComponentName()!;
    if (this.registry[componentName]) {
      lg.warn('WARN: override prototype', prototype, componentName);
      const idx = this.prototypeList.findIndex((proto) => componentName === proto.getComponentName());
      this.prototypeList[idx] = prototype;
    } else {
      this.prototypeList.push(prototype);
    }
    this.registry[componentName] = prototype;
  }
}
