import Foundation

/// Resolves the on-disk git directories a working tree depends on, so a sandbox that bind-mounts
/// only the worktree subtree can *also* mount the shared git data the worktree references.
///
/// A **linked** git worktree (what `GitWorktreeManager` creates via `git worktree add`) does not
/// carry its own object database. Its `.git` is a *file* — `gitdir: <abs path>` — pointing at the
/// per-worktree git dir `<repo>/.git/worktrees/<name>`, whose `commondir` in turn points at the
/// shared common dir `<repo>/.git` (the object store, config, and packed refs). Both live in the
/// repo's `.git`, a **sibling** of the worktree, not underneath it — so a mount that shares only
/// the worktree directory leaves git unable to follow the `gitdir:` pointer, and every command
/// fails with `fatal: not a git repository: <repo>/.git/worktrees/<name>`.
///
/// Mounting the common dir at its identical host path (so the worktree's absolute pointers resolve
/// with no rewriting) fixes that, and because the per-worktree git dir lives *under* the common
/// dir, the single mount covers both. A normal (non-linked) checkout keeps its `.git` inside the
/// worktree, already covered by the worktree mount — `commonDir` returns that inner path and the
/// caller sees it's already contained, so no extra mount is added.
public enum GitDirResolver {
    /// The absolute git **common** directory (object store + config + packed refs) for the working
    /// tree at `worktreePath`, or `nil` when there's no resolvable git dir (not a repo, or an
    /// unreadable/garbled `.git`). Pure filesystem resolution — no subprocess — so it's cheap and
    /// deterministic on the container-create hot path.
    public static func commonDir(forWorktree worktreePath: String) -> String? {
        let fm = FileManager.default
        let dotGit = (worktreePath as NSString).appendingPathComponent(".git")
        var isDir: ObjCBool = false
        guard fm.fileExists(atPath: dotGit, isDirectory: &isDir) else { return nil }

        // Normal checkout: `.git` is a real directory living inside the worktree.
        if isDir.boolValue { return standardized(dotGit) }

        // Linked worktree: `.git` is a file "gitdir: <path to per-worktree git dir>".
        guard let contents = try? String(contentsOfFile: dotGit, encoding: .utf8),
            let gitDir = parseGitdirPointer(contents, relativeTo: worktreePath)
        else { return nil }

        // The per-worktree git dir names the shared common dir in its `commondir` file (usually
        // `../..`). Resolve it against the git dir; if that file is missing/blank, the git dir is
        // itself the common dir (a plain, non-worktree gitdir pointer).
        let commonDirFile = (gitDir as NSString).appendingPathComponent("commondir")
        guard let raw = try? String(contentsOfFile: commonDirFile, encoding: .utf8) else {
            return standardized(gitDir)
        }
        let rel = raw.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !rel.isEmpty else { return standardized(gitDir) }
        if (rel as NSString).isAbsolutePath { return standardized(rel) }
        return standardized((gitDir as NSString).appendingPathComponent(rel))
    }

    /// The mount the sandbox should add so git resolves for the worktree at `worktreePath`: the
    /// common git dir, at its **identical** host path, but only when it lies OUTSIDE the worktree
    /// subtree (the linked-worktree case). Returns `nil` for a normal checkout — where `.git` is
    /// already inside the worktree and thus covered by the worktree's own mount — and when there's
    /// no resolvable git dir. `readOnly` lets a caller choose whether the sandbox may mutate git
    /// history (commit/merge); defaults to writable, matching the worktree mount.
    public static func commonDirMount(
        forWorktree worktreePath: String, readOnly: Bool = false
    ) -> ContainerSpec.Mount? {
        guard let common = commonDir(forWorktree: worktreePath) else { return nil }
        let worktree = standardized(worktreePath)
        // Inside the worktree subtree (normal checkout) → the worktree mount already covers it.
        if common == worktree || common.hasPrefix(worktree + "/") { return nil }
        return ContainerSpec.Mount(host: common, container: common, readOnly: readOnly)
    }

    /// Parse a `.git`-file `gitdir: <path>` pointer, resolving a relative target against `base`.
    /// Internal for unit testing.
    static func parseGitdirPointer(_ contents: String, relativeTo base: String) -> String? {
        for line in contents.split(whereSeparator: \.isNewline) {
            let trimmed = line.trimmingCharacters(in: .whitespaces)
            guard trimmed.hasPrefix("gitdir:") else { continue }
            let path = trimmed.dropFirst("gitdir:".count).trimmingCharacters(in: .whitespaces)
            guard !path.isEmpty else { return nil }
            if (path as NSString).isAbsolutePath { return standardized(path) }
            return standardized((base as NSString).appendingPathComponent(path))
        }
        return nil
    }

    /// Collapse `.`/`..` components to a canonical absolute path. `standardizingPath` resolves the
    /// `../..` a `commondir` file carries; it leaves the path lexical (no symlink chasing needed).
    private static func standardized(_ path: String) -> String {
        (path as NSString).standardizingPath
    }
}
