import ArgumentParser
import Foundation
import Logging
import LoggingModels
import Search
import SearchModels
import SharedConstants

// MARK: - Package Search Command (hidden)

/// Hidden packages-only smart query. Invoked as:
///     cupertino package-search "how do I write a log handler in swift-log"
///
/// Thin wrapper over `SearchModule.SmartQuery` configured with a single
/// `PackageFTSCandidateFetcher` (#192 E6). Output format is identical to
/// `cupertino search` (default fan-out mode, #239), so a user already
/// fluent with `search` can substitute `package-search` whenever they
/// specifically want packages-only results without typing `--skip-docs
/// --skip-samples`.
@available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
extension CLIImpl.Command {
    struct PackageSearch: AsyncParsableCommand {
        static let configuration = CommandConfiguration(
            commandName: "package-search",
            abstract: "Smart query over the packages index (packages source only)",
            shouldDisplay: false
        )

        @Argument(help: "Plain-text question")
        var question: String

        @Option(name: .long, help: "Max number of chunks to return")
        var limit: Int = 3

        @Option(name: .long, help: "Override packages.db path")
        var db: String?

        @Option(
            name: .long,
            help: """
            Restrict results to packages whose declared deployment target is \
            compatible with the named platform. Values: iOS, macOS, \
            tvOS, watchOS, visionOS (case-insensitive). Requires \
            --min-version. Packages with no annotation source are dropped.
            """
        )
        var platform: String?

        @Option(
            name: .long,
            help: """
            Minimum version for --platform, e.g. 16.0 / 13.0 / 10.15. \
            Lexicographic compare in SQL — works correctly for current Apple \
            platform versions (iOS 13+, macOS 11+ etc.).
            """
        )
        var minVersion: String?

        @Option(
            name: .long,
            help: """
            Restrict results to packages whose authored `// swift-tools-version: X.Y` \
            declaration in `Package.swift` is greater than or equal to the given \
            value (e.g. 5.7, 6.0). Orthogonal to --platform / --min-version: filters \
            on the Swift compiler floor, not the platform deployment target. \
            Packages with no swift-tools declaration are dropped when the filter is active.
            """
        )
        var swiftTools: String?

        mutating func run() async throws {
            // Path-DI composition sub-root (#535).
            let paths = Shared.Paths.live()
            let dbURL = db.map { URL(fileURLWithPath: $0).expandingTildeInPath }
                ?? paths.packagesDatabase

            guard FileManager.default.fileExists(atPath: dbURL.path) else {
                Cupertino.Context.composition.logging.recording.error("❌ packages.db not found at \(dbURL.path)")
                Cupertino.Context.composition.logging.recording.error("   Run `cupertino fetch --type packages` then `cupertino save --packages` first.")
                throw ExitCode.failure
            }

            let trimmed = question.trimmingCharacters(in: .whitespacesAndNewlines)
            guard !trimmed.isEmpty else {
                Cupertino.Context.composition.logging.recording.error("❌ Question cannot be empty.")
                throw ExitCode.failure
            }

            // E6: single-fetcher SmartQuery. RRF over one input degenerates to
            // "preserve the fetcher's own ordering", so behaviour is equivalent
            // to calling `PackageQuery.answer` directly — but it goes through the
            // exact same code path as `cupertino ask`, which means future ranking
            // tweaks land in one place.
            let availabilityFilter: SearchModels.Search.AvailabilityFilter?
            switch (platform, minVersion) {
            case let (platform?, minVersion?):
                availabilityFilter = SearchModels.Search.AvailabilityFilter(
                    platform: platform,
                    minVersion: minVersion
                )
            case (.some, nil), (nil, .some):
                Cupertino.Context.composition.logging.recording.error(
                    "❌ --platform and --min-version must be used together."
                )
                throw ExitCode.failure
            case (nil, nil):
                availabilityFilter = nil
            }
            let swiftToolsFilter = swiftTools.map { SearchModels.Search.SwiftToolsFilter(minVersion: $0) }
            let fetcher = SearchModule.PackageFTSCandidateFetcher(
                dbPath: dbURL,
                availability: availabilityFilter,
                swiftTools: swiftToolsFilter
            )
            let smart = SearchModule.SmartQuery(fetchers: [fetcher])
            let result = await smart.answer(question: trimmed, limit: limit, perFetcherLimit: max(20, limit))

            if result.candidates.isEmpty {
                Cupertino.Context.composition.logging.recording.info("No matches for: \(trimmed)")
                return
            }

            for (idx, fused) in result.candidates.enumerated() {
                let cand = fused.candidate
                let owner = cand.metadata["owner"] ?? ""
                let repo = cand.metadata["repo"] ?? ""
                let relpath = cand.metadata["relpath"] ?? cand.identifier
                let module = cand.metadata["module"] ?? ""
                print("══════════════════════════════════════════════════════════════════════")
                print("[\(idx + 1)] \(owner)/\(repo) — \(relpath)")
                let kindLabel = cand.kind ?? "?"
                if !module.isEmpty {
                    print("    module: \(module)  •  kind: \(kindLabel)  •  score: \(String(format: "%.4f", fused.score))")
                } else {
                    print("    kind: \(kindLabel)  •  score: \(String(format: "%.4f", fused.score))")
                }
                print("──────────────────────────────────────────────────────────────────────")
                print(cand.chunk)
                print("")
            }
        }
    }
}
