import path from "node:path";
import fs from "node:fs";
import type { Database as DBType } from "better-sqlite3";

interface ScanStats {
  modules: number;
  symbols: number;
}

/** 递归收集目录下匹配扩展名的所有文件 */
function walkDir(dir: string, exts: string[]): string[] {
  if (!fs.existsSync(dir)) return [];
  const results: string[] = [];
  const stack: string[] = [dir];
  while (stack.length > 0) {
    const current = stack.pop()!;
    let entries: fs.Dirent[];
    try {
      entries = fs.readdirSync(current, { withFileTypes: true });
    } catch {
      continue;
    }
    for (const entry of entries) {
      const fullPath = path.join(current, entry.name);
      if (entry.isDirectory()) {
        // 跳过 node_modules / dist / .git
        if (
          ["node_modules", "dist", ".git", ".next", "build"].includes(
            entry.name,
          )
        )
          continue;
        stack.push(fullPath);
      } else if (entry.isFile()) {
        const ext = path.extname(entry.name);
        if (exts.includes(ext)) results.push(fullPath);
      }
    }
  }
  return results;
}

export function scanTypeScript(db: DBType, root: string): ScanStats {
  const insertModule = db.prepare(
    "INSERT OR IGNORE INTO modules (name, path, language, service, type) VALUES (?, ?, ?, ?, ?)",
  );
  const insertSymbol = db.prepare(
    "INSERT INTO symbols (module_id, name, kind, language, file_path, line_start, line_end, is_exported) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
  );
  const getModuleId = db.prepare("SELECT id FROM modules WHERE name = ?") as {
    get: (name: string) => { id: number } | undefined;
  };

  let modules = 0;
  let symbols = 0;

  // 扫描 services/* 和 apps/* 和 packages/* 下的 TS 服务
  const serviceDirs = ["services", "apps"];
  const moduleList: { name: string; path: string; service: string }[] = [];

  for (const dir of serviceDirs) {
    const baseDir = path.join(root, dir);
    if (!fs.existsSync(baseDir)) continue;
    const entries = fs.readdirSync(baseDir, { withFileTypes: true });
    for (const entry of entries) {
      if (!entry.isDirectory()) continue;
      const serviceName = entry.name;
      const servicePath = path.join(baseDir, serviceName);
      // 确认是 TS 服务（有 src 目录或 package.json）
      const hasSrc = fs.existsSync(path.join(servicePath, "src"));
      const hasPkg = fs.existsSync(path.join(servicePath, "package.json"));
      if (!hasSrc && !hasPkg) continue;

      const moduleName = serviceName;
      insertModule.run(moduleName, servicePath, "ts", serviceName, "service");
      const row = getModuleId.get(moduleName);
      if (row) {
        modules++;
        moduleList.push({
          name: moduleName,
          path: servicePath,
          service: serviceName,
        });
      }
    }
  }

  // packages/* 下的 TS 包
  const packagesDir = path.join(root, "packages");
  if (fs.existsSync(packagesDir)) {
    const entries = fs.readdirSync(packagesDir, { withFileTypes: true });
    for (const entry of entries) {
      if (!entry.isDirectory()) continue;
      const pkgName = entry.name;
      const pkgPath = path.join(packagesDir, pkgName);
      const hasSrc = fs.existsSync(path.join(pkgPath, "src"));
      const hasPkg = fs.existsSync(path.join(pkgPath, "package.json"));
      const hasProto = fs.existsSync(path.join(pkgPath, "proto"));
      if (!hasSrc && !hasPkg && !hasProto) continue;
      const moduleName = pkgName;
      insertModule.run(moduleName, pkgPath, "ts", pkgName, "package");
      const row = getModuleId.get(moduleName);
      if (row) {
        modules++;
        moduleList.push({ name: moduleName, path: pkgPath, service: pkgName });
      }
    }
  }

  // 收集所有 .ts 文件并用 regex 提取符号（ts-morph 对未安装依赖的文件可能解析失败）
  const TS_FUNC_RE = /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/g;
  const TS_CLASS_RE = /(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/g;
  const TS_INTERFACE_RE = /(?:export\s+)?interface\s+(\w+)/g;
  const TS_CONST_RE = /(?:export\s+)?const\s+([A-Z][A-Z0-9_]+)\s*=/g;

  for (const mod of moduleList) {
    const srcDir = path.join(mod.path, "src");
    if (!fs.existsSync(srcDir)) continue;
    const tsFiles = walkDir(srcDir, [".ts", ".tsx"]);
    const modRow = getModuleId.get(mod.name);
    if (!modRow) continue;

    for (const filePath of tsFiles) {
      let content: string;
      try {
        content = fs.readFileSync(filePath, "utf-8");
      } catch {
        continue;
      }

      // 函数
      let match: RegExpExecArray | null;
      const funcRe = new RegExp(TS_FUNC_RE);
      while ((match = funcRe.exec(content)) !== null) {
        const name = match[1];
        const lineNum = content.slice(0, match.index).split("\n").length;
        const isExported = match[0].includes("export") ? 1 : 0;
        insertSymbol.run(
          modRow.id,
          name,
          "function",
          "ts",
          filePath,
          lineNum,
          lineNum,
          isExported,
        );
        symbols++;
      }

      // 类
      const classRe = new RegExp(TS_CLASS_RE);
      while ((match = classRe.exec(content)) !== null) {
        const name = match[1];
        const lineNum = content.slice(0, match.index).split("\n").length;
        const isExported = match[0].includes("export") ? 1 : 0;
        insertSymbol.run(
          modRow.id,
          name,
          "class",
          "ts",
          filePath,
          lineNum,
          lineNum,
          isExported,
        );
        symbols++;
      }

      // 接口
      const ifaceRe = new RegExp(TS_INTERFACE_RE);
      while ((match = ifaceRe.exec(content)) !== null) {
        const name = match[1];
        const lineNum = content.slice(0, match.index).split("\n").length;
        const isExported = match[0].includes("export") ? 1 : 0;
        insertSymbol.run(
          modRow.id,
          name,
          "interface",
          "ts",
          filePath,
          lineNum,
          lineNum,
          isExported,
        );
        symbols++;
      }

      // 常量（UPPER_CASE）
      const constRe = new RegExp(TS_CONST_RE);
      while ((match = constRe.exec(content)) !== null) {
        const name = match[1];
        const lineNum = content.slice(0, match.index).split("\n").length;
        const isExported = match[0].includes("export") ? 1 : 0;
        insertSymbol.run(
          modRow.id,
          name,
          "const",
          "ts",
          filePath,
          lineNum,
          lineNum,
          isExported,
        );
        symbols++;
      }
    }
  }

  return { modules, symbols };
}
