import Foundation
import Synchronization

// MARK: - GitPathHistory

/// **One load's answer to "how early did this path enter history"** — the object behind
/// `HistoryStore.identityHistoryRanker`, and the git implementation of the seam
/// `BoardLoader.IdentityHistoryRanker` describes.
///
/// ### Lazy, because the question is usually never asked
///
/// The loader consults the ranker **only when it has already found a duplicate identity**
/// (`BoardLoader.dedupeIdentities` gates on a collision before it builds a single occurrence), which
/// on a healthy board is never. So nothing here walks a repository at construction: the map is built
/// on the first `rank(of:)` call and reused for the rest of that load, which means the ordinary case
/// costs one allocation and no libgit2 at all.
///
/// ### Sendable, because the load runs off the main actor
///
/// `BoardStore.startReload` walks the tree in a detached task, so the ranker crosses into it and the
/// closure `BoardLoader` calls is `@Sendable`. The cache is therefore a `Mutex` rather than a plain
/// `var` — one lock, held across the walk itself, which is correct rather than merely safe: two
/// concurrent first-callers would otherwise each walk the whole ancestry to compute the same map.
final class GitPathHistory: Sendable {

    private let boardRoot: URL

    /// `nil` until the first ask — see the type's note. The distinction between "not computed" and
    /// "computed, and the repository had nothing to say" is what keeps an empty history from being
    /// recomputed on every occurrence in a colliding board.
    private let ranks = Mutex<[String: Int]?>(nil)

    init(boardRoot: URL) {
        self.boardRoot = boardRoot
    }

    /// The seam value the loader takes: lower is earlier, `nil` is untracked or no history.
    var ranker: BoardLoader.IdentityHistoryRanker {
        BoardLoader.IdentityHistoryRanker { [self] path in rank(of: path) }
    }

    /// The rank of one board-root-relative path.
    func rank(of path: String) -> Int? {
        ranks.withLock { cache in
            if cache == nil {
                cache = GitRepository.pathFirstAppearanceRanks(at: boardRoot)
            }
            return cache?[path]
        }
    }
}
