import Foundation
import NucleicProtocol

/// Repo-shape facts that make a project ineligible for session transfer (mesh P5). Each maps to
/// a typed `TransferRejectReason` so the user sees why a session can't move.
public struct RepoTransferShape: Sendable, Equatable {
    public var isShallow: Bool
    public var isPartialClone: Bool
    public var hasSubmodules: Bool
    public var usesLFS: Bool

    public init(isShallow: Bool = false, isPartialClone: Bool = false,
                hasSubmodules: Bool = false, usesLFS: Bool = false) {
        self.isShallow = isShallow
        self.isPartialClone = isPartialClone
        self.hasSubmodules = hasSubmodules
        self.usesLFS = usesLFS
    }

    /// The reject reason for this shape, or nil if the repo is transferable.
    public var rejectReason: TransferRejectReason? {
        if isShallow { return .shallowRepo }
        if isPartialClone { return .partialClone }
        if hasSubmodules { return .submoduleRepo }
        if usesLFS { return .lfsRepo }
        return nil
    }
}

/// Read-only git probes for transfer eligibility + destination project matching. Uses the same
/// `GitRunner` as `GitWorktreeManager`; these are all cheap queries, so they don't need the
/// structural lock.
public struct TransferGitProbe: Sendable {
    private let git: GitRunner
    public init(git: GitRunner = GitRunner()) { self.git = git }

    /// Probe a repo's shape for transfer-blocking features.
    public func repoShape(rootPath: String) async throws -> RepoTransferShape {
        var shape = RepoTransferShape()

        let shallow = try? await git.checked(["rev-parse", "--is-shallow-repository"], in: rootPath)
        shape.isShallow = (shallow == "true")

        // Partial (filtered/promisor) clone: a filter spec or a promisor remote.
        if let filter = try? await git.run(
            ["config", "--get", "remote.origin.partialclonefilter"], in: rootPath), filter.ok,
           !filter.stdout.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
            shape.isPartialClone = true
        }
        if !shape.isPartialClone,
           let promisor = try? await git.run(
            ["config", "--get-regexp", #"remote\..*\.promisor"#], in: rootPath), promisor.ok,
           !promisor.stdout.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
            shape.isPartialClone = true
        }

        // Submodules: a tracked .gitmodules at the repo root.
        shape.hasSubmodules = FileManager.default.fileExists(
            atPath: (rootPath as NSString).appendingPathComponent(".gitmodules"))

        // Git LFS: a root .gitattributes wiring the lfs filter.
        let attrs = (rootPath as NSString).appendingPathComponent(".gitattributes")
        if let contents = try? String(contentsOfFile: attrs, encoding: .utf8) {
            shape.usesLFS = contents.contains("filter=lfs")
        }
        return shape
    }

    /// The repo's identity for destination project matching: the root (parentless) commit SHA
    /// and the normalized `origin` remote. Either may be nil (a repo with no remote, or a shallow
    /// repo whose root isn't present).
    public func repoIdentity(rootPath: String) async throws -> (rootCommitSHA: String?, normalizedRemote: String?) {
        let root = try? await git.checked(
            ["rev-list", "--max-parents=0", "HEAD"], in: rootPath)
        // Multiple root commits (merged histories) — take the first, deterministically.
        let rootSHA = root?.split(whereSeparator: \.isNewline).map(String.init).sorted().first

        var remote: String?
        if let url = try? await git.run(["remote", "get-url", "origin"], in: rootPath), url.ok {
            let trimmed = url.stdout.trimmingCharacters(in: .whitespacesAndNewlines)
            if !trimmed.isEmpty { remote = Self.normalizeRemote(trimmed) }
        }
        return (rootSHA, remote)
    }

    /// Normalize a git remote URL to a scheme/credential-free `host/path` form so the same repo
    /// cloned over https vs ssh compares equal: strips the scheme, any `user@`, the `.git`
    /// suffix and a trailing slash, and lowercases. `git@github.com:me/Proj.git` and
    /// `https://github.com/me/proj/` both become `github.com/me/proj`.
    public static func normalizeRemote(_ raw: String) -> String {
        var s = raw.trimmingCharacters(in: .whitespacesAndNewlines)
        // scp-like syntax: git@host:path → host/path
        if !s.contains("://"), let colon = s.firstIndex(of: ":"),
           let at = s.firstIndex(of: "@"), at < colon {
            let host = s[s.index(after: at)..<colon]
            let path = s[s.index(after: colon)...]
            s = "\(host)/\(path)"
        } else {
            // scheme://[user@]host/path → host/path
            if let range = s.range(of: "://") { s = String(s[range.upperBound...]) }
            if let at = s.firstIndex(of: "@") { s = String(s[s.index(after: at)...]) }
        }
        if s.hasSuffix(".git") { s = String(s.dropLast(4)) }
        while s.hasSuffix("/") { s = String(s.dropLast()) }
        return s.lowercased()
    }
}
