/**
 * NERF Worktree Utilities
 *
 * Pure utility functions for worktree name detection, legacy branch name
 * parsing, and integration branch capture.
 *
 * Pure utility functions (detectWorktreeName, getSliceBranchName, parseSliceBranch,
 * SLICE_BRANCH_RE) remain standalone for backwards compatibility.
 *
 * Branchless architecture: all work commits sequentially on the milestone branch.
 * Pure utility functions (detectWorktreeName, getSliceBranchName, parseSliceBranch,
 * SLICE_BRANCH_RE) remain for backwards compatibility with legacy branches.
 */
import { existsSync, readFileSync, realpathSync, utimesSync } from "node:fs";
import { join, resolve } from "node:path";
import { homedir } from "node:os";
import { GitServiceImpl, writeIntegrationBranch } from "./git-service.js";
import { loadEffectiveNerfPreferences } from "./preferences.js";
export { MergeConflictError } from "./git-service.js";
// ─── Lazy GitServiceImpl Cache ─────────────────────────────────────────────
let cachedService = null;
let cachedBasePath = null;
/**
 * Get or create a GitServiceImpl for the given basePath.
 * Resets the cache if basePath changes between calls.
 * Lazy construction: only instantiated at call-time, never at module-evaluation.
 */
function getService(basePath) {
    if (cachedService === null || cachedBasePath !== basePath) {
        const loaded = loadEffectiveNerfPreferences();
        const gitPrefs = loaded?.preferences?.git ?? {};
        cachedService = new GitServiceImpl(basePath, gitPrefs);
        cachedBasePath = basePath;
    }
    return cachedService;
}
/**
 * Set the active milestone ID on the cached GitServiceImpl.
 * This enables integration branch resolution in getMainBranch().
 */
export function setActiveMilestoneId(basePath, milestoneId) {
    getService(basePath).setMilestoneId(milestoneId);
}
/**
 * Record the current branch as the integration branch for a milestone.
 * Called once when auto-mode starts — captures where slice branches should
 * merge back to. No-op if the same branch is already recorded. Updates the
 * record when the user starts from a different branch (#300). Always a no-op
 * if on a NERF slice branch.
 */
export function captureIntegrationBranch(basePath, milestoneId) {
    // In a worktree, the base branch is implicit (worktree/<name>).
    // Writing it to META.json would leave stale metadata after merge back to main.
    if (detectWorktreeName(basePath))
        return;
    const svc = getService(basePath);
    const current = svc.getCurrentBranch();
    writeIntegrationBranch(basePath, milestoneId, current);
}
// ─── Pure Utility Functions (unchanged) ────────────────────────────────────
/**
 * Find the worktrees segment in a path, supporting both direct
 * (`/.nerf/worktrees/` or legacy `/.nerf/worktrees/`) and symlink-resolved layouts.
 * When `.nerf` (or legacy `.nerf`) is a symlink to `~/.nerf/projects/<hash>`, resolved
 * paths contain the intermediate `projects/<hash>/` segment that the old
 * single-marker check missed.
 */
function findWorktreeSegment(normalizedPath) {
    // Direct layout: /.nerf/worktrees/<name> or legacy /.nerf/worktrees/<name>
    for (const directMarker of ["/.nerf/worktrees/", "/.nerf/worktrees/"]) {
        const idx = normalizedPath.indexOf(directMarker);
        if (idx !== -1) {
            return { nerfIdx: idx, afterWorktrees: idx + directMarker.length };
        }
    }
    // Symlink-resolved layout: /.nerf/projects/<hash>/worktrees/<name> or legacy /.nerf/...
    const symlinkRe = /\/\.(?:nerf)\/projects\/[a-f0-9]+\/worktrees\//;
    const match = normalizedPath.match(symlinkRe);
    if (match && match.index !== undefined) {
        return { nerfIdx: match.index, afterWorktrees: match.index + match[0].length };
    }
    return null;
}
/**
 * Detect the active worktree name from the current working directory.
 * Returns null if not inside a NERF worktree (.nerf/worktrees/<name>/).
 */
export function detectWorktreeName(basePath) {
    const normalizedPath = basePath.replaceAll("\\", "/");
    const seg = findWorktreeSegment(normalizedPath);
    if (!seg)
        return null;
    const afterMarker = normalizedPath.slice(seg.afterWorktrees);
    const name = afterMarker.split("/")[0];
    return name || null;
}
/**
 * Resolve the project root from a path that may be inside a worktree.
 * If the path contains a worktrees segment, returns the portion before
 * `/.nerf/` (or legacy `/.nerf/`). Otherwise returns the input unchanged.
 *
 * When the worker was spawned with NERF_PROJECT_ROOT set, use that directly —
 * the coordinator already knows the real project root unambiguously.
 *
 * When `/.nerf/` (or legacy `/.nerf/`) in the resolved path is actually the user-level `~/.nerf/`
 * (common when `.nerf` is a symlink into `~/.nerf/projects/<hash>`), the
 * string-slice heuristic would return `~` — which is catastrophically wrong.
 * In that case, fall back to reading the worktree's `.git` file, which
 * contains a `gitdir:` pointer to the real project's `.git/worktrees/<name>`,
 * giving the real project root unambiguously.
 *
 * Use this in commands that call `process.cwd()` to ensure they always
 * operate against the real project root, not a worktree subdirectory.
 */
export function resolveProjectRoot(basePath) {
    // Layer 1: If the coordinator passed the real project root, use it.
    if (process.env.NERF_PROJECT_ROOT) {
        return process.env.NERF_PROJECT_ROOT;
    }
    const normalizedPath = basePath.replaceAll("\\", "/");
    const seg = findWorktreeSegment(normalizedPath);
    if (!seg)
        return basePath;
    // Candidate root via the string-slice heuristic
    const sepChar = basePath.includes("\\") ? "\\" : "/";
    // Try .nerf first, then legacy .nerf
    let nerfIdx = basePath.indexOf(`${sepChar}.nerf${sepChar}`);
    if (nerfIdx === -1)
        nerfIdx = basePath.indexOf(`${sepChar}.nerf${sepChar}`);
    const candidate = nerfIdx !== -1
        ? basePath.slice(0, nerfIdx)
        : basePath.slice(0, seg.nerfIdx);
    // Layer 2: Guard against resolving to the user's home directory.
    // When .nerf is a symlink into ~/.nerf/projects/<hash>, the resolved path
    // contains /.nerf/ at the user-level boundary. Slicing there yields ~ — wrong.
    const nerfHome = normalizePathForCompare(process.env.NERF_HOME || join(homedir(), ".nerf"));
    const candidateNerfPath = normalizePathForCompare(join(candidate, ".nerf"));
    if (candidateNerfPath === nerfHome || candidateNerfPath.startsWith(nerfHome + "/")) {
        // The candidate is the home directory (or within it in a way that .nerf
        // maps to the user-level NERF dir). Try to recover the real project root
        // from the worktree's .git file.
        const realRoot = resolveProjectRootFromGitFile(basePath);
        if (realRoot)
            return realRoot;
        // If git file resolution failed, return basePath unchanged rather than ~
        return basePath;
    }
    return candidate;
}
/**
 * Recover the real project root from a worktree's .git file.
 *
 * Each git worktree has a `.git` file (not directory) containing:
 *   gitdir: /real/project/.git/worktrees/<name>
 *
 * Walking up from that gitdir gives us `/real/project/.git`, and its
 * parent is the real project root.
 */
function resolveProjectRootFromGitFile(worktreePath) {
    try {
        // Walk up from the worktree path to find the .git file
        let dir = worktreePath;
        for (let i = 0; i < 30; i++) {
            const gitPath = join(dir, ".git");
            if (existsSync(gitPath)) {
                const content = readFileSync(gitPath, "utf8").trim();
                if (content.startsWith("gitdir: ")) {
                    // gitdir points to: <real-project>/.git/worktrees/<name>
                    const gitDir = resolve(dir, content.slice(8));
                    // Walk up: .git/worktrees/<name> → .git/worktrees → .git → project root
                    const dotGitDir = resolve(gitDir, "..", "..");
                    // Verify this looks like a .git directory
                    if (dotGitDir.endsWith(".git") || dotGitDir.endsWith(".git/") || dotGitDir.endsWith(".git\\")) {
                        return resolve(dotGitDir, "..");
                    }
                    // Alternative: the commondir file inside the worktree gitdir
                    // points to the main .git directory
                    const commonDirPath = join(gitDir, "commondir");
                    if (existsSync(commonDirPath)) {
                        const commonDir = readFileSync(commonDirPath, "utf8").trim();
                        const resolvedCommonDir = resolve(gitDir, commonDir);
                        return resolve(resolvedCommonDir, "..");
                    }
                }
                break;
            }
            const parent = resolve(dir, "..");
            if (parent === dir)
                break;
            dir = parent;
        }
    }
    catch {
        // Non-fatal — caller will use fallback
    }
    return null;
}
function normalizePathForCompare(path) {
    let normalized;
    try {
        normalized = realpathSync(path);
    }
    catch {
        normalized = resolve(path);
    }
    const slashed = normalized.replaceAll("\\", "/");
    const trimmed = slashed.replace(/\/+$/, "");
    return trimmed || "/";
}
/**
 * Get the slice branch name, namespaced by worktree when inside one.
 *
 * In the main tree:     bnerf/<milestoneId>/<sliceId>
 * In a worktree:        bnerf/<worktreeName>/<milestoneId>/<sliceId>
 *
 * This prevents branch conflicts when multiple worktrees work on the
 * same milestone/slice IDs — git doesn't allow a branch to be checked
 * out in more than one worktree simultaneously.
 */
export function getSliceBranchName(milestoneId, sliceId, worktreeName) {
    if (worktreeName) {
        return `bnerf/${worktreeName}/${milestoneId}/${sliceId}`;
    }
    return `bnerf/${milestoneId}/${sliceId}`;
}
/** Re-export for backward compatibility — canonical definition in branch-patterns.ts */
export { SLICE_BRANCH_RE } from "./branch-patterns.js";
import { SLICE_BRANCH_RE } from "./branch-patterns.js";
/**
 * Parse a slice branch name into its components.
 * Handles both `bnerf/M001/S01` and `bnerf/myworktree/M001/S01`.
 */
export function parseSliceBranch(branchName) {
    const match = branchName.match(SLICE_BRANCH_RE);
    if (!match)
        return null;
    return {
        worktreeName: match[1] ?? null,
        milestoneId: match[2],
        sliceId: match[3],
    };
}
// ─── Git-Mutation Functions (delegate to GitServiceImpl) ───────────────────
/**
 * Get the "main" branch for NERF slice operations.
 *
 * In the main working tree: returns main/master (the repo's default branch).
 * In a worktree: returns worktree/<name> — the worktree's own base branch.
 *
 * This is critical because git doesn't allow a branch to be checked out
 * in more than one worktree. Slice branches merge into the worktree's base
 * branch, and the worktree branch later merges into the real main via
 * /worktree merge.
 */
export function getMainBranch(basePath) {
    return getService(basePath).getMainBranch();
}
export function getCurrentBranch(basePath) {
    return getService(basePath).getCurrentBranch();
}
/**
 * Auto-commit any dirty files in the current working tree.
 *
 * When `taskContext` is provided, generates a meaningful conventional commit
 * message from the task summary (one-liner, inferred type, key files).
 * Falls back to a generic `chore()` message for non-task commits.
 *
 * Returns the commit message used, or null if already clean.
 */
export function autoCommitCurrentBranch(basePath, unitType, unitId, taskContext) {
    return getService(basePath).autoCommit(unitType, unitId, [], taskContext);
}
/**
 * Async auto-commit on the current branch.
 * Non-blocking version for auto-mode event-loop health.
 * Returns the commit message used, or null if already clean.
 */
export async function autoCommitCurrentBranchAsync(basePath, unitType, unitId, taskContext) {
    return getService(basePath).autoCommitAsync(unitType, unitId, [], taskContext);
}
// ─── Git HEAD Resolution ────────────────────────────────────────────────────
/**
 * Resolve the git HEAD file path for a given directory.
 * Handles both normal repos (.git is a directory) and worktrees (.git is a file
 * containing a `gitdir:` pointer to the real gitdir).
 */
export function resolveGitHeadPath(dir) {
    const gitPath = join(dir, ".git");
    if (!existsSync(gitPath))
        return null;
    try {
        const content = readFileSync(gitPath, "utf8").trim();
        if (content.startsWith("gitdir: ")) {
            const gitDir = resolve(dir, content.slice(8));
            const headPath = join(gitDir, "HEAD");
            return existsSync(headPath) ? headPath : null;
        }
        const headPath = join(dir, ".git", "HEAD");
        return existsSync(headPath) ? headPath : null;
    }
    catch {
        return null;
    }
}
/**
 * Nudge pi's FooterDataProvider to re-read the git branch after chdir.
 * Touches HEAD in both old and new cwd to fire the fs watcher.
 */
export function nudgeGitBranchCache(previousCwd) {
    const now = new Date();
    for (const dir of [previousCwd, process.cwd()]) {
        try {
            const headPath = resolveGitHeadPath(dir);
            if (headPath)
                utimesSync(headPath, now, now);
        }
        catch {
            // Best-effort
        }
    }
}
