import { Project, SyntaxKind, ts, Node } from "ts-morph";
import path from "path";
import fs from "fs-extra";

type Content = {
  name: string;
  content: string;
};

export type Options = {
  /** 导出组件的入口文件 */
  exportFile: string;
  /** 生成的文件目录 */
  generateDir: string;
  /** 生成的文件类型, 默认为ts */
  filetype?: "ts" | "js";
  /** 转换引入的包名称 */
  transform?: (libraryName: string) => string;
};

/** 根据语法类型过滤节点，返回数组 */
function filterNodeBySyntaxKind(nodes: Node<ts.Node>[], kind: ts.SyntaxKind) {
  return nodes
    .filter((node) => node.getKind() === kind)
    .map((node) => node.getText());
}

/** 根据语法类型过滤节点，返回对象 */
function findNodeBySyntaxKind(nodes: Node<ts.Node>[], kind: ts.SyntaxKind) {
  return filterNodeBySyntaxKind(nodes, kind)?.[0];
}

/** 根据导出index文件获取导出内容 */
export const getExportContent = (
  path: string,
  transform?: Options["transform"]
) => {
  const project = new Project();

  // 添加源代码
  const source = project.addSourceFileAtPath(path);

  const content: Content[] = [];

  source.forEachChild((item) => {
    const text = item.getText();

    // 不处理非导出类型语法
    if (item.getKind() !== SyntaxKind.ExportDeclaration) {
      return;
    }

    // 不处理导出类型
    if (text.startsWith("export type")) {
      return;
    }

    const nodes = item.forEachDescendantAsArray();

    // 参考 https://github.com/estree/estree/blob/master/es2015.md#exports
    // export { get } from 'module' => ['get']
    // export { get as myGet } from 'module' => ['get as myGet']
    // export { get, pick as myPick } from 'module' => ['get', 'pick as myPick']
    // export { default as Module, get } from 'module' => ['default as Module', 'get']
    // export * as module from 'module' => undefined
    const exportSpecifier = filterNodeBySyntaxKind(
      nodes,
      SyntaxKind.ExportSpecifier
    );

    // export { get as myGet } from 'module' => ['get', 'myGet']
    // export { get, pick } from 'module' => ['get', 'pick']
    // export { get, pick as myPick } from 'module' => ['get', 'pick', 'myPick']
    // export { default as Module, get } from 'module' => ['default', 'Module', 'get']
    // export * as module from 'module' => []
    const identifiers = filterNodeBySyntaxKind(nodes, SyntaxKind.Identifier);

    // 如果是*号导出时有值，此时identifiers返回为空数组
    // export * as module from 'module' => '* as module'
    // export { get } from 'module' => undefined
    const namespaceExport = findNodeBySyntaxKind(
      nodes,
      SyntaxKind.NamespaceExport
    );

    // export { get } from 'module' => 'module'
    const stringLiteral = findNodeBySyntaxKind(nodes, SyntaxKind.StringLiteral);

    const libraryName = transform?.(stringLiteral) || stringLiteral;

    // 跳过此次循环的下标
    let breakIndex = -1;

    for (let i = 0; i < identifiers.length; i++) {
      if (breakIndex === i) {
        continue;
      }

      const identifier = identifiers[i];

      const reNamedIdentifier =
        identifier === "default" ? "default" : `${identifier} as default`;

      // 是否有重命名导出，如get as myGet, default as Utils
      const isRenamedExport = exportSpecifier?.[i]?.includes(" as ");

      // 如果有默认导出, 则跳过下一次循环
      if (isRenamedExport) {
        breakIndex = i + 1;

        content.push({
          name: identifiers[i + 1],
          content: `export { ${reNamedIdentifier} } from ${libraryName};`,
        });
        continue;
      }

      // 如果包含*号导出
      if (namespaceExport?.includes("* as")) {
        content.push({
          name: identifier,
          content: `export * as default from ${libraryName};`,
        });
        continue;
      }

      content.push({
        name: identifier,
        content: `export { ${reNamedIdentifier} } from ${libraryName};`,
      });
    }
  });

  return content;
};

/** 生成导出文件 */
async function generateExposesFile(
  dir: string,
  content: Content[],
  filetype: string
) {
  // 生成目录时先删除旧目录数据
  await fs.remove(dir);
  // 删除后重新创建目录
  await fs.mkdir(dir);

  for (const item of content) {
    const filepath = path.resolve(dir, `${item.name}.${filetype}`);
    await fs.writeFile(filepath, item.content);
  }
}

/** 生成模块联邦exposes JSON配置 */
async function generateExposesJson(dir: string, content: Content[]) {
  const filename = path.resolve(dir, "index.json");
  // 目录名称
  const dirName = path.basename(dir);
  const map: Record<string, string> = {};

  content.forEach((item) => {
    map[`./${item.name}`] = `./${dirName}/${item.name}`;
  });

  await fs.writeFile(filename, JSON.stringify(map, null, 2));
}

export async function generateExposes(options: Options) {
  const { exportFile, generateDir, filetype = "ts", transform } = options;
  const content = getExportContent(exportFile, transform);
  await generateExposesFile(generateDir, content, filetype);
  await generateExposesJson(generateDir, content);
}
