import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
import { join, resolve } from "node:path";
import { sfHome } from "./sf-home.js";
export function normalizeWorktreePathForCompare(path) {
	let normalized;
	try {
		normalized = realpathSync(path);
	} catch {
		normalized = resolve(path);
	}
	const slashed = normalized.replaceAll("\\", "/");
	const trimmed = slashed.replace(/\/+$/, "");
	return process.platform === "win32"
		? (trimmed || "/").toLowerCase()
		: trimmed || "/";
}
/**
 * Find the SF worktree segment in both direct project layout and the
 * symlink-resolved external-state layout used by ~/.sf/projects/<hash>.
 */
export function findWorktreeSegment(normalizedPath) {
	const directMarker = "/.sf/worktrees/";
	const directIdx = normalizedPath.indexOf(directMarker);
	if (directIdx !== -1) {
		return {
			sfIdx: directIdx,
			afterWorktrees: directIdx + directMarker.length,
		};
	}
	const externalRe = /\/\.sf\/projects\/[^/]+\/worktrees\//;
	const externalMatch = normalizedPath.match(externalRe);
	if (externalMatch && externalMatch.index !== undefined) {
		return {
			sfIdx: externalMatch.index,
			afterWorktrees: externalMatch.index + externalMatch[0].length,
		};
	}
	return null;
}
export function isSfWorktreePath(path) {
	return findWorktreeSegment(path.replaceAll("\\", "/")) !== null;
}
/**
 * Resolve the canonical project root for worktree operations.
 *
 * `originalBasePath` wins when available because session state already knows the
 * root. `SF_PROJECT_ROOT` is the next strongest signal for worker processes.
 * Otherwise, derive the root from direct `.sf/worktrees` paths, or recover it
 * from the worktree `.git` file for symlink-resolved ~/.sf/project paths.
 */
export function resolveWorktreeProjectRoot(basePath, originalBasePath) {
	const preferred =
		originalBasePath?.trim() || process.env.SF_PROJECT_ROOT?.trim() || basePath;
	return resolveProjectRootFromPath(preferred);
}
function resolveProjectRootFromPath(path) {
	const normalizedPath = path.replaceAll("\\", "/");
	const segment = findWorktreeSegment(normalizedPath);
	if (!segment) {
		return (
			resolveNearestBootstrappedSfRoot(path) ??
			resolveGitWorkingTreeRoot(path) ??
			path
		);
	}
	const sepChar = path.includes("\\") ? "\\" : "/";
	const sfMarker = `${sepChar}.sf${sepChar}`;
	const markerIdx = path.indexOf(sfMarker);
	const candidate =
		markerIdx !== -1 ? path.slice(0, markerIdx) : path.slice(0, segment.sfIdx);
	const sfHomeNorm = normalizeWorktreePathForCompare(sfHome());
	const candidateSfPath = normalizeWorktreePathForCompare(
		join(candidate, ".sf"),
	);
	if (
		candidateSfPath === sfHomeNorm ||
		candidateSfPath.startsWith(`${sfHomeNorm}/`)
	) {
		const realRoot = resolveProjectRootFromGitFile(path);
		return realRoot ?? path;
	}
	return candidate;
}
function resolveNearestBootstrappedSfRoot(path) {
	try {
		let dir =
			existsSync(path) && !statSync(path).isDirectory()
				? resolve(path, "..")
				: path;
		for (let i = 0; i < 30; i++) {
			if (hasSfBootstrapArtifacts(join(dir, ".sf"))) return dir;
			const gitPath = join(dir, ".git");
			if (existsSync(gitPath)) return null;
			const parent = resolve(dir, "..");
			if (parent === dir) break;
			dir = parent;
		}
	} catch {
		// Non-fatal: callers fall back to git root resolution.
	}
	return null;
}
function hasSfBootstrapArtifacts(sfPath) {
	return (
		existsSync(sfPath) &&
		(existsSync(join(sfPath, "preferences.yaml")) ||
			existsSync(join(sfPath, "milestones")))
	);
}
function resolveGitWorkingTreeRoot(path) {
	try {
		let dir =
			existsSync(path) && !statSync(path).isDirectory()
				? resolve(path, "..")
				: path;
		for (let i = 0; i < 30; i++) {
			const gitPath = join(dir, ".git");
			if (existsSync(gitPath)) return dir;
			const parent = resolve(dir, "..");
			if (parent === dir) break;
			dir = parent;
		}
	} catch {
		// Non-fatal: callers either keep the original path or fail closed.
	}
	return null;
}
function resolveProjectRootFromGitFile(worktreePath) {
	try {
		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: ")) {
					const gitDir = resolve(dir, content.slice(8));
					const dotGitDir = resolve(gitDir, "..", "..");
					if (
						dotGitDir.endsWith(".git") ||
						dotGitDir.endsWith(".git/") ||
						dotGitDir.endsWith(".git\\")
					) {
						return resolve(dotGitDir, "..");
					}
					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: callers either keep the original path or fail closed.
	}
	return null;
}
