/**
 * SF 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 { GitService, writeIntegrationBranch } from "./git-service.js";
import { loadEffectiveSFPreferences } from "./preferences.js";
import { sfHome } from "./sf-home.js";
import { detectWorktreeName, findWorktreeSegment } from "./worktree-detect.js";

export { MergeConflictError } from "./git-service.js";
// Re-export for consumers that import detectWorktreeName from ./worktree.js
export { detectWorktreeName } from "./worktree-detect.js";

// ─── Lazy GitService Cache ─────────────────────────────────────────────
let cachedService = null;
let cachedBasePath = null;
/**
 * Get or create a GitService 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 = loadEffectiveSFPreferences();
		const gitPrefs = loaded?.preferences?.git ?? {};
		cachedService = new GitService(basePath, gitPrefs);
		cachedBasePath = basePath;
	}
	return cachedService;
}
/**
 * Clear the cached GitService. For testing only — forces the next
 * getService() call to re-read preferences and create a fresh instance.
 * @internal
 */
export function _resetServiceCache() {
	cachedService = null;
	cachedBasePath = null;
}
/**
 * Set the active milestone ID on the cached GitService.
 * 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 autonomous 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 SF 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) ────────────────────────────────────
/**
 * Resolve the project root from a path that may be inside a worktree.
 * If the path contains a worktrees segment, returns the portion before
 * `/.sf/`. Otherwise returns the input unchanged.
 *
 * When the worker was spawned with SF_PROJECT_ROOT set, use that directly —
 * the coordinator already knows the real project root unambiguously.
 *
 * When `/.sf/` in the resolved path is actually the user-level `~/.sf/`
 * (common when `.sf` is a symlink into `~/.sf/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.SF_PROJECT_ROOT) {
		return process.env.SF_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("\\") ? "\\" : "/";
	const sfMarker = `${sepChar}.sf${sepChar}`;
	const sfIdx = basePath.indexOf(sfMarker);
	const candidate =
		sfIdx !== -1 ? basePath.slice(0, sfIdx) : basePath.slice(0, seg.sfIdx);
	// Layer 2: Guard against resolving to the user's home directory.
	// When .sf is a symlink into ~/.sf/projects/<hash>, the resolved path
	// contains /.sf/ at the user-level boundary. Slicing there yields ~ — wrong.
	const sfHomePath = normalizePathForCompare(sfHome());
	const candidateSfPath = normalizePathForCompare(join(candidate, ".sf"));
	if (
		candidateSfPath === sfHomePath ||
		candidateSfPath.startsWith(sfHomePath + "/")
	) {
		// The candidate is the home directory (or within it in a way that .sf
		// maps to the user-level SF 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:     sf/<milestoneId>/<sliceId>
 * In a worktree:        sf/<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 `sf/${worktreeName}/${milestoneId}/${sliceId}`;
	}
	return `sf/${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 `sf/M001/S01` and `sf/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 GitService) ───────────────────
/**
 * Get the "main" branch for SF 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,
	sessionId,
) {
	return getService(basePath).autoCommit(
		unitType,
		unitId,
		[],
		taskContext,
		sessionId,
	);
}
// ─── 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
		}
	}
}
