import { join } from "node:path";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { homedir } from "node:os";

import type { ResolvedNode } from "@/types/index.js";

/**
 * Detect the required Node version for a repo and find the best matching
 * nvm-managed binary. Returns null if no version requirement is found or
 * no compatible nvm version is installed.
 */
export function resolveNodeBinPath(repoPath: string): ResolvedNode | null {
  const requested = detectRequiredVersion(repoPath);
  if (!requested) return null;

  const nvmDir = process.env.NVM_DIR || join(homedir(), ".nvm");
  const versionsDir = join(nvmDir, "versions", "node");
  if (!existsSync(versionsDir)) return null;

  const installed = readdirSync(versionsDir)
    .filter((d) => d.startsWith("v"))
    .map((d) => ({ dir: d, parsed: parseVersion(d.slice(1)) }))
    .filter((d) => d.parsed !== null)
    .sort((a, b) => compareVersions(b.parsed!, a.parsed!));

  const reqParsed = parseVersion(requested);
  if (!reqParsed) return null;

  const match =
    installed.find((v) => v.dir === `v${requested}`) ??
    installed.find(
      (v) => v.parsed!.major === reqParsed.major && v.parsed!.minor === reqParsed.minor,
    ) ??
    installed.find((v) => v.parsed!.major === reqParsed.major);

  if (!match) return null;

  const binPath = join(versionsDir, match.dir, "bin");
  if (!existsSync(binPath)) return null;

  return { binPath, requested, resolved: match.dir.slice(1) };
}

function detectRequiredVersion(repoPath: string): string | null {
  const nvmrcPath = join(repoPath, ".nvmrc");
  if (existsSync(nvmrcPath)) {
    const raw = readFileSync(nvmrcPath, "utf-8").trim();
    if (raw) return raw.replace(/^v/i, "");
  }

  const pkgPath = join(repoPath, "package.json");
  if (existsSync(pkgPath)) {
    try {
      const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
      const engine = pkg.engines?.node;
      if (typeof engine === "string") {
        const exact = engine.replace(/^[>=<~^]+/, "").trim();
        if (/^\d+/.test(exact)) return exact;
      }
    } catch {
      /* ignore malformed package.json */
    }
  }

  return null;
}

interface SemVer {
  major: number;
  minor: number;
  patch: number;
}

function parseVersion(v: string): SemVer | null {
  const m = v.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
  if (!m) return null;
  return {
    major: Number(m[1]),
    minor: Number(m[2] ?? 0),
    patch: Number(m[3] ?? 0),
  };
}

function compareVersions(a: SemVer, b: SemVer): number {
  return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
}
