import { existsSync, readdirSync, readFileSync } from "node:fs";
import { resolve, join, dirname, basename, relative } from "node:path";
import { discoverSubmodules } from "./git-tracker.js";
const NESTED_SCAN_SKIP = /* @__PURE__ */ new Set([
  "node_modules",
  ".git",
  "dist",
  "build",
  ".cache",
  "coverage",
  "vendor",
  ".mapx",
  "__pycache__",
  ".next",
  ".nuxt",
  ".output",
  "target",
  "bin",
  "obj",
  ".svn",
  ".hg",
  ".tox",
  "venv",
  ".venv",
  ".yarn",
  ".pnpm-store",
  ".turbo",
  "out",
  ".parcel-cache"
]);
const COMMON_MONO_DIRS = [
  "apps",
  "packages",
  "libs",
  "services",
  "modules",
  "clients",
  "backend",
  "frontend"
];
const PACKAGE_MANIFESTS = [
  "package.json",
  "Cargo.toml",
  "go.mod",
  "pyproject.toml",
  "composer.json",
  "build.gradle",
  "build.gradle.kts",
  "pom.xml"
];
class WorkspaceManager {
  static discoverSubmodules(repoRoot) {
    return discoverSubmodules(repoRoot);
  }
  /**
   * Recursively scan `rootDir` up to `maxDepth` levels deep looking for
   * directories that contain a `.git` entry (file or folder).  The root
   * directory itself is excluded; each discovered repo is returned only once
   * and recursion stops at that boundary.
   *
   * Common noise directories (node_modules, dist, .cache, …) are skipped
   * automatically.
   */
  static discoverNestedGitRepos(rootDir, maxDepth = 3) {
    const results = [];
    const absRoot = resolve(rootDir);
    function walk(dir, depth) {
      if (depth > maxDepth) return;
      let entries;
      try {
        entries = readdirSync(dir, { withFileTypes: true });
      } catch {
        return;
      }
      for (const entry of entries) {
        if (!entry.isDirectory()) continue;
        if (NESTED_SCAN_SKIP.has(entry.name)) continue;
        const absEntry = join(dir, entry.name);
        const gitPath = join(absEntry, ".git");
        if (existsSync(gitPath)) {
          results.push({
            name: entry.name,
            path: relative(absRoot, absEntry),
            url: "",
            isInitialized: true
          });
          continue;
        }
        walk(absEntry, depth + 1);
      }
    }
    walk(absRoot, 1);
    return results;
  }
  /**
   * Discover monorepo packages/apps within `rootDir`.
   *
   * Detection strategy (in priority order):
   *  1. `pnpm-workspace.yaml` → `packages:` list
   *  2. `package.json` `workspaces` field (npm / yarn)
   *  3. `lerna.json` `packages` field (defaults to `packages/*`)
   *  4. `rush.json` `projects[].projectFolder`
   *  5. `Cargo.toml` `[workspace] members`
   *  6. `go.work` `use` block
   *  7. `turbo.json` / `nx.json` — uses common dirs as patterns
   *  8. Fallback: scan `apps/`, `packages/`, `libs/`, `services/`, … for
   *     directories that contain a package manifest.
   *
   * A directory is only included when it contains at least one recognised
   * package manifest (`package.json`, `Cargo.toml`, `go.mod`, …) and does
   * **not** have its own `.git` entry (those are handled by
   * `discoverNestedGitRepos`).
   */
  static discoverMonorepoPackages(rootDir) {
    const absRoot = resolve(rootDir);
    const results = [];
    const seen = /* @__PURE__ */ new Set();
    const globs = [];
    let detectedManager = "inferred";
    const pnpmWsPath = join(absRoot, "pnpm-workspace.yaml");
    if (existsSync(pnpmWsPath)) {
      try {
        const content = readFileSync(pnpmWsPath, "utf-8");
        const block = content.match(/^packages:\s*\n((?:[ \t]*-[^\n]*\n?)*)/m);
        if (block) {
          const items = block[1].split("\n").map((l) => l.replace(/^[ \t]*-\s*['"]?/, "").replace(/['"]?\s*$/, "").trim()).filter(Boolean);
          if (items.length > 0) {
            globs.push(...items);
            detectedManager = "pnpm";
          }
        }
      } catch {
      }
    }
    const pkgJsonPath = join(absRoot, "package.json");
    if (globs.length === 0 && existsSync(pkgJsonPath)) {
      try {
        const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
        const ws = pkg.workspaces;
        if (Array.isArray(ws) && ws.length > 0) {
          globs.push(...ws);
          detectedManager = "npm";
        } else if (ws && Array.isArray(ws.packages) && ws.packages.length > 0) {
          globs.push(...ws.packages);
          detectedManager = "yarn";
        }
      } catch {
      }
    }
    const lernaPath = join(absRoot, "lerna.json");
    if (globs.length === 0 && existsSync(lernaPath)) {
      try {
        const lerna = JSON.parse(readFileSync(lernaPath, "utf-8"));
        const lernaGlobs = Array.isArray(lerna.packages) ? lerna.packages : ["packages/*"];
        globs.push(...lernaGlobs);
        detectedManager = "npm";
      } catch {
      }
    }
    const rushPath = join(absRoot, "rush.json");
    if (globs.length === 0 && existsSync(rushPath)) {
      try {
        const raw = readFileSync(rushPath, "utf-8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*/g, "");
        const rush = JSON.parse(raw);
        if (Array.isArray(rush.projects)) {
          for (const proj of rush.projects) {
            if (typeof proj.projectFolder === "string") {
              globs.push(proj.projectFolder);
            }
          }
          if (globs.length > 0) detectedManager = "rush";
        }
      } catch {
      }
    }
    const cargoPath = join(absRoot, "Cargo.toml");
    if (globs.length === 0 && existsSync(cargoPath)) {
      try {
        const content = readFileSync(cargoPath, "utf-8");
        const wsBlock = content.match(/\[workspace\][^[]*?members\s*=\s*\[([\s\S]*?)\]/);
        if (wsBlock) {
          const rawMembers = wsBlock[1].match(/"([^"]+)"/g);
          if (rawMembers) {
            globs.push(...rawMembers.map((m) => m.replace(/"/g, "")));
            detectedManager = "cargo";
          }
        }
      } catch {
      }
    }
    const goWorkPath = join(absRoot, "go.work");
    if (globs.length === 0 && existsSync(goWorkPath)) {
      try {
        const content = readFileSync(goWorkPath, "utf-8");
        const blockMatch = content.match(/^use\s*\(\n([\s\S]*?)\n\)/m);
        const inlineMatch = content.match(/^use\s+(.+)/m);
        const useLines = blockMatch ? blockMatch[1].split("\n").map((l) => l.trim()).filter(Boolean) : inlineMatch ? inlineMatch[1].split(/\s+/).filter(Boolean) : [];
        for (const line of useLines) {
          const p = line.replace(/^\.\//, "").replace(/^\//, "").trim();
          if (p && p !== ".") globs.push(p);
        }
        if (globs.length > 0) detectedManager = "go";
      } catch {
      }
    }
    const hasTurbo = existsSync(join(absRoot, "turbo.json"));
    const hasNx = existsSync(join(absRoot, "nx.json"));
    if ((hasTurbo || hasNx) && globs.length === 0) {
      globs.push("apps/*", "packages/*", "libs/*");
      detectedManager = hasTurbo ? "turborepo" : "nx";
    }
    const expandGlob = (pattern) => {
      const norm = pattern.replace(/\\/g, "/").replace(/^\.\//, "");
      if (norm.endsWith("/*") || norm.endsWith("/**")) {
        const dir = norm.replace(/\/\*\*?$/, "");
        const absDir = join(absRoot, dir);
        if (!existsSync(absDir)) return [];
        try {
          return readdirSync(absDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !NESTED_SCAN_SKIP.has(e.name)).map((e) => `${dir}/${e.name}`);
        } catch {
          return [];
        }
      }
      if (!norm.includes("*")) return [norm];
      return [];
    };
    const expandedDirs = /* @__PURE__ */ new Set();
    for (const pattern of globs) {
      for (const d of expandGlob(pattern)) expandedDirs.add(d);
    }
    const resolvePkgName = (absDir) => {
      const dirName = basename(absDir);
      const pkgPath = join(absDir, "package.json");
      if (existsSync(pkgPath)) {
        try {
          const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
          if (typeof pkg.name === "string" && pkg.name) {
            return pkg.name.replace(/^@[^/]+\//, "");
          }
        } catch {
        }
      }
      const cargoManifest = join(absDir, "Cargo.toml");
      if (existsSync(cargoManifest)) {
        try {
          const m = readFileSync(cargoManifest, "utf-8").match(/\[package\][^[]*?name\s*=\s*"([^"]+)"/s);
          if (m) return m[1];
        } catch {
        }
      }
      return dirName;
    };
    const tryAdd = (relPath) => {
      const norm = relPath.replace(/\\/g, "/");
      const absDir = join(absRoot, norm);
      if (!existsSync(absDir)) return;
      if (seen.has(absDir)) return;
      seen.add(absDir);
      if (existsSync(join(absDir, ".git"))) return;
      if (!PACKAGE_MANIFESTS.some((m) => existsSync(join(absDir, m)))) return;
      results.push({ name: resolvePkgName(absDir), path: norm, packageManager: detectedManager });
    };
    for (const relDir of expandedDirs) tryAdd(relDir);
    if (results.length === 0) {
      for (const dir of COMMON_MONO_DIRS) {
        const absDir = join(absRoot, dir);
        if (!existsSync(absDir)) continue;
        try {
          const entries = readdirSync(absDir, { withFileTypes: true });
          for (const entry of entries) {
            if (!entry.isDirectory() || NESTED_SCAN_SKIP.has(entry.name)) continue;
            tryAdd(`${dir}/${entry.name}`);
          }
        } catch {
        }
      }
    }
    return results;
  }
  static discoverPeerRepos(workspaceRoot) {
    const parentDir = dirname(resolve(workspaceRoot));
    if (!existsSync(parentDir)) return [];
    const peers = [];
    try {
      const entries = readdirSync(parentDir, { withFileTypes: true });
      for (const entry of entries) {
        if (!entry.isDirectory()) continue;
        if (entry.name === basename(workspaceRoot)) continue;
        const siblingPath = join(parentDir, entry.name);
        const gitPath = join(siblingPath, ".git");
        if (existsSync(gitPath)) {
          peers.push({
            name: entry.name,
            path: `../${entry.name}`,
            url: "",
            isInitialized: true
          });
        }
      }
    } catch {
    }
    return peers;
  }
  static discoverVSCodeWorkspace(workspaceFile, workspaceRoot) {
    if (!existsSync(workspaceFile)) return [];
    const repos = [];
    try {
      const content = readFileSync(workspaceFile, "utf-8");
      const ws = JSON.parse(content);
      const wsDir = dirname(resolve(workspaceFile));
      if (ws && Array.isArray(ws.folders)) {
        for (const folder of ws.folders) {
          if (typeof folder.path === "string") {
            const absFolder = resolve(wsDir, folder.path);
            if (absFolder === resolve(workspaceRoot)) continue;
            const gitPath = join(absFolder, ".git");
            if (existsSync(gitPath)) {
              const relPath = relative(resolve(workspaceRoot), absFolder);
              repos.push({
                name: folder.name || basename(absFolder),
                path: relPath,
                url: "",
                isInitialized: true
              });
            }
          }
        }
      }
    } catch {
    }
    return repos;
  }
}
export {
  WorkspaceManager
};
