import Foundation

public enum ClaudeDesktopProjectsLocator {
    private static let sessionDirectoryNames = [
        "local-agent-mode-sessions",
        "claude-code-sessions",
    ]

    private static let skippedDirectoryNames = Set([
        ".build",
        ".git",
        "build",
        "DerivedData",
        "node_modules",
        "outputs",
        "target",
    ])

    public static func roots(
        homeDirectory: URL = FileManager.default.homeDirectoryForCurrentUser,
        fileManager: FileManager = .default) -> [URL]
    {
        let claudeApplicationSupport = homeDirectory
            .appendingPathComponent("Library", isDirectory: true)
            .appendingPathComponent("Application Support", isDirectory: true)
            .appendingPathComponent("Claude", isDirectory: true)
        let sessionRoots = self.sessionDirectoryNames.map {
            claudeApplicationSupport.appendingPathComponent($0, isDirectory: true)
        }

        var roots: [URL] = []
        var queue = sessionRoots.map { (url: $0, depth: 0) }
        var visited = Set(sessionRoots.map(\.standardizedFileURL.path))
        var nextIndex = 0
        // Current Desktop entries under claude-code-sessions are metadata whose cliSessionId maps to the
        // shared ~/.claude/projects logs. Seed that root anyway so embedded project stores are found when present.
        // Covers observed Desktop layouts through account/workspace, session, agent, and local_agent
        // without descending into arbitrary checked-out workspaces.
        let maxDepth = 4

        while nextIndex < queue.count {
            let current = queue[nextIndex]
            nextIndex += 1
            if let projects = self.projectsRoot(under: current.url, fileManager: fileManager) {
                roots.append(projects)
            }

            guard current.depth < maxDepth else { continue }
            for child in self.childDirectories(at: current.url, fileManager: fileManager) {
                let standardized = child.standardizedFileURL
                guard visited.insert(standardized.path).inserted else { continue }
                queue.append((standardized, current.depth + 1))
            }
        }
        return roots
    }

    static func roots(
        homeDirectory: URL,
        fileManager: FileManager,
        budget: inout DirectoryMetadataScanBudget) -> [URL]
    {
        let claudeApplicationSupport = homeDirectory
            .appendingPathComponent("Library", isDirectory: true)
            .appendingPathComponent("Application Support", isDirectory: true)
            .appendingPathComponent("Claude", isDirectory: true)
        let sessionRoots = self.sessionDirectoryNames.map {
            claudeApplicationSupport.appendingPathComponent($0, isDirectory: true)
        }

        var roots: [URL] = []
        var queue = sessionRoots.map { (url: $0, depth: 0) }
        var visited = Set(sessionRoots.map(\.standardizedFileURL.path))
        var nextIndex = 0
        while nextIndex < queue.count {
            let current = queue[nextIndex]
            nextIndex += 1
            if let projects = self.projectsRoot(under: current.url, fileManager: fileManager) {
                roots.append(projects)
            }

            guard current.depth < budget.maxDepth else { continue }
            for child in budget.childDirectories(in: current.url, fileManager: fileManager) {
                guard !self.skippedDirectoryNames.contains(child.lastPathComponent) else { continue }
                let standardized = child.standardizedFileURL
                guard visited.insert(standardized.path).inserted else { continue }
                queue.append((standardized, current.depth + 1))
            }
        }
        return roots
    }

    private static func projectsRoot(under base: URL, fileManager: FileManager) -> URL? {
        let projects = base
            .appendingPathComponent(".claude", isDirectory: true)
            .appendingPathComponent("projects", isDirectory: true)
        var isDirectory: ObjCBool = false
        guard fileManager.fileExists(atPath: projects.path, isDirectory: &isDirectory),
              isDirectory.boolValue
        else { return nil }
        return projects.standardizedFileURL
    }

    private static func childDirectories(at url: URL, fileManager: FileManager) -> [URL] {
        guard let children = try? fileManager.contentsOfDirectory(
            at: url,
            includingPropertiesForKeys: [.isDirectoryKey, .isSymbolicLinkKey],
            options: [.skipsHiddenFiles, .skipsPackageDescendants])
        else {
            return []
        }

        return children.compactMap { child in
            guard !self.skippedDirectoryNames.contains(child.lastPathComponent) else { return nil }
            guard let values = try? child.resourceValues(forKeys: [.isDirectoryKey, .isSymbolicLinkKey]),
                  values.isSymbolicLink != true,
                  values.isDirectory == true
            else {
                return nil
            }
            return child
        }
    }
}
