import { execSync } from "node:child_process";
import crypto from "node:crypto";
import path from "node:path";

export interface Scope {
  key: string;
  kind: "personal" | "project" | "org";
  projectName: string;
}

/** v2: v1 `user` scope is renamed to `personal` (§19). */
export const PERSONAL_SCOPE: Scope = {
  key: "personal",
  kind: "personal",
  projectName: "personal",
};

function normalizeRemote(url: string): string {
  return url
    .trim()
    .replace(/\.git$/i, "")
    .replace(/^git@([^:]+):/, "https://$1/")
    .replace(/^ssh:\/\/git@/, "https://")
    .toLowerCase();
}

function tryGitRemote(cwd: string): string | null {
  try {
    const url = execSync("git config --get remote.origin.url", {
      cwd,
      stdio: ["ignore", "pipe", "ignore"],
      encoding: "utf8",
    }).trim();
    return url || null;
  } catch {
    return null;
  }
}

function sanitize(name: string): string {
  const cleaned = name.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 40);
  return cleaned.length > 0 ? cleaned : "project";
}

function scopeFromSeed(seed: string, projectName: string): Scope {
  const hash = crypto.createHash("sha256").update(seed).digest("hex").slice(0, 12);
  return {
    key: `project__${sanitize(projectName)}__${hash}`,
    kind: "project",
    projectName,
  };
}

/** Scope keyed purely on the absolute cwd. Never consults git. Used both as
 *  the fallback for `resolveProjectScope` when no remote is set, and as the
 *  "legacy" scope detector for migration when a remote is added later. */
export function resolveCwdScope(worktree: string): Scope {
  const target = worktree && worktree.length > 0 ? worktree : ".";
  const resolved = path.resolve(target);
  const seed = resolved.toLowerCase();
  // basename can return "" on Windows drive roots (e.g. "C:\\"); walk the path
  // segments and pick the last non-empty, non-drive-letter component.
  const parts = resolved
    .split(/[\\/]+/)
    .filter((p) => p && !/^[A-Za-z]:$/.test(p));
  let projectName = parts[parts.length - 1] ?? path.basename(resolved) ?? "workspace";
  if (!projectName) projectName = "workspace";
  return scopeFromSeed(seed, projectName);
}

/** Pick the directory that seeds scope resolution from a host's candidate
 *  roots. OpenCode v1 hands the plugin both `worktree` (the repo root — but
 *  the filesystem root, e.g. `C:\` or `/`, when the opened folder is not a
 *  git repo) and `directory` (the folder actually opened). Seeding on a
 *  filesystem root collapses every non-git folder on a drive into one shared
 *  `project__workspace__…` scope (D71), so prefer `worktree` only when it is
 *  a real directory below the filesystem root. */
export function pickScopeRoot(
  worktree?: string | null,
  directory?: string | null,
): string {
  const isFsRoot = (p: string) => path.parse(p).root === p;
  if (worktree && !isFsRoot(worktree)) return worktree;
  if (directory) return directory;
  if (worktree) return worktree;
  return process.cwd();
}

export function resolveProjectScope(worktree: string): Scope {
  const target = worktree && worktree.length > 0 ? worktree : ".";
  const remote = tryGitRemote(target);
  if (remote) {
    const norm = normalizeRemote(remote);
    const m = norm.match(/\/([^/]+)$/);
    const projectName = m?.[1] ?? "workspace";
    return scopeFromSeed(norm, projectName);
  }
  return resolveCwdScope(target);
}
