/**
 * 解析器是对输入的固定格式数据做拆解，使其符合引擎后续步骤预期，完成统一处理逻辑的步骤。
 * 本解析器面向的是标准 schema 协议。
 */

import { SUPPORT_SCHEMA_VERSION_LIST } from '../const';

import { handleChildren } from '../utils/children';

import {
  ChildNodeType,
  CodeGeneratorError,
  CompatibilityError,
  DependencyType,
  IBasicSchema,
  IComponentNodeItem,
  IContainerInfo,
  IContainerNodeItem,
  IExternalDependency,
  IInternalDependency,
  InternalDependencyType,
  IPageMeta,
  IParseResult,
  IProjectSchema,
  ISchemaParser,
  IUtilItem,
} from '../types';

const defaultContainer: IContainerInfo = {
  containerType: 'Component',
  componentName: 'Index',
  fileName: 'Index',
  css: '',
  props: {},
};

class SchemaParser implements ISchemaParser {
  public validate(schema: IBasicSchema): boolean {
    if (SUPPORT_SCHEMA_VERSION_LIST.indexOf(schema.version) < 0) {
      throw new CompatibilityError(
        `Not support schema with version [${schema.version}]`,
      );
    }

    return true;
  }

  public parse(schema: IProjectSchema): IParseResult {
    // TODO: collect utils depends in JSExpression
    const compDeps: Record<string, IExternalDependency> = {};
    const internalDeps: Record<string, IInternalDependency> = {};
    let utilsDeps: IExternalDependency[] = [];

    // 解析三方组件依赖
    schema.componentsMap.forEach(info => {
      info.dependencyType = DependencyType.External;
      info.importName = info.componentName;
      compDeps[info.componentName] = info;
    });

    let containers: IContainerInfo[];
    // Test if this is a lowcode component without container
    if (schema.componentsTree.length > 0) {
      const firstRoot: IContainerNodeItem = schema
        .componentsTree[0] as IContainerNodeItem;

      if (!firstRoot.fileName) {
        // 整个 schema 描述一个容器，且无根节点定义
        const container: IContainerInfo = {
          ...defaultContainer,
          children: schema.componentsTree as IComponentNodeItem[],
        };
        containers = [container];
      } else {
        // 普通带 1 到多个容器的 schema
        containers = schema.componentsTree.map(n => {
          const subRoot = n as IContainerNodeItem;
          const container: IContainerInfo = {
            ...subRoot,
            containerType: subRoot.componentName,
            componentName: subRoot.fileName,
          };
          return container;
        });
      }
    } else {
      throw new CodeGeneratorError(`Can't find anything to generate.`);
    }

    // 建立所有容器的内部依赖索引
    containers.forEach(container => {
      let type;
      switch (container.containerType) {
        case 'Page':
          type = InternalDependencyType.PAGE;
          break;
        case 'Block':
          type = InternalDependencyType.BLOCK;
          break;
        default:
          type = InternalDependencyType.COMPONENT;
          break;
      }

      const dep: IInternalDependency = {
        type,
        moduleName: container.componentName,
        destructuring: false,
        exportName: container.componentName,
        dependencyType: DependencyType.Internal,
      };

      internalDeps[dep.moduleName] = dep;
    });

    // 分析容器内部组件依赖
    containers.forEach(container => {
      if (container.children) {
        // const depNames = this.getComponentNames(container.children);
        // container.deps = uniqueArray<string>(depNames)
        //   .map(depName => internalDeps[depName] || compDeps[depName])
        //   .filter(dep => !!dep);
        container.deps = Object.keys(compDeps).map(
          depName => compDeps[depName],
        );
      }
    });

    // 分析路由配置
    const routes = containers
      .filter(container => container.containerType === 'Page')
      .map(page => {
        const meta = page.meta as IPageMeta;
        return {
          path: meta.router,
          componentName: page.componentName,
        };
      });

    const routerDeps = routes
      .map(r => internalDeps[r.componentName] || compDeps[r.componentName])
      .filter(dep => !!dep);

    // 分析 Utils 依赖
    let utils: IUtilItem[];
    if (schema.utils) {
      utils = schema.utils;
      utilsDeps = schema.utils
        .filter(u => u.type !== 'function')
        .map(u => u.content as IExternalDependency);
    } else {
      utils = [];
    }

    return {
      containers,
      globalUtils: {
        utils,
        deps: utilsDeps,
      },
      globalI18n: schema.i18n,
      globalRouter: {
        routes,
        deps: routerDeps,
      },
      project: {
        meta: schema.meta,
        config: schema.config,
        css: schema.css,
        constants: schema.constants,
        i18n: schema.i18n,
      },
    };
  }

  public getComponentNames(children: ChildNodeType): string[] {
    return handleChildren<string>(children, {
      node: (i: IComponentNodeItem) => [i.componentName],
    });
  }
}

export default SchemaParser;
