import ArgumentParser
import Foundation
import Logging
import LoggingModels
import SampleIndex
import Services
import ServicesModels
import SharedConstants
// MARK: - List Samples Command

/// CLI command for listing sample code projects - mirrors MCP tool functionality.
@available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
extension CLIImpl.Command {
    struct ListSamples: AsyncParsableCommand {
        static let configuration = CommandConfiguration(
            commandName: "list-samples",
            abstract: "List indexed Apple sample code projects"
        )

        @Option(
            name: .shortAndLong,
            help: "Filter by framework (e.g., swiftui, uikit, appkit)"
        )
        var framework: String?

        @Option(
            name: .long,
            help: "Maximum number of results to return"
        )
        var limit: Int = 50

        @Option(
            name: .long,
            help: "Output format: text (default), json, markdown"
        )
        var format: OutputFormat = .text

        @Option(
            name: .long,
            help: "Path to sample index database"
        )
        var sampleDb: String?

        mutating func run() async throws {
            // Resolve database path
            let dbPath = resolveSampleDbPath()

            // GoF Factory Method (1994 p. 107): construct the concrete
            // Creator at the command's composition sub-root.
            let sampleDatabaseFactory: any Sample.Index.DatabaseFactory = LiveSampleIndexDatabaseFactory()

            // Use Services.ServiceContainer for managed lifecycle
            let (projects, totalProjects, totalFiles) = try await Services.ServiceContainer
                .withSampleService(samplesDB: dbPath, sampleDatabaseFactory: sampleDatabaseFactory) { service in
                    let projects = try await service.listProjects(framework: framework, limit: limit)
                    let totalProjects = try await service.projectCount()
                    let totalFiles = try await service.fileCount()
                    return (projects, totalProjects, totalFiles)
                }

            // Output results
            switch format {
            case .text:
                outputText(projects, totalProjects: totalProjects, totalFiles: totalFiles)
            case .json:
                outputJSON(projects, totalProjects: totalProjects, totalFiles: totalFiles)
            case .markdown:
                outputMarkdown(projects, totalProjects: totalProjects, totalFiles: totalFiles)
            }
        }

        // MARK: - Path Resolution

        private func resolveSampleDbPath() -> URL {
            if let sampleDb {
                return URL(fileURLWithPath: sampleDb).expandingTildeInPath
            }
            // Path-DI composition sub-root (#535).
            return Sample.Index.databasePath(baseDirectory: Shared.Paths.live().baseDirectory)
        }

        // MARK: - Output Formatting

        private func outputText(_ projects: [Sample.Index.Project], totalProjects: Int, totalFiles: Int) {
            Cupertino.Context.composition.logging.recording.output("Sample Code Projects")
            Cupertino.Context.composition.logging.recording.output("Total: \(totalProjects) projects, \(totalFiles) files")

            if let framework {
                Cupertino.Context.composition.logging.recording.output("Filtered by: \(framework)")
            }

            Cupertino.Context.composition.logging.recording.output("")

            if projects.isEmpty {
                Cupertino.Context.composition.logging.recording.output("No projects found. Run 'cupertino save --samples' to index sample code.")
                return
            }

            for (index, project) in projects.enumerated() {
                Cupertino.Context.composition.logging.recording.output("[\(index + 1)] \(project.title)")
                Cupertino.Context.composition.logging.recording.output("    ID: \(project.id)")
                Cupertino.Context.composition.logging.recording.output("    Frameworks: \(project.frameworks.joined(separator: ", "))")
                Cupertino.Context.composition.logging.recording.output("    Files: \(project.fileCount)")
                Cupertino.Context.composition.logging.recording.output("")
            }
        }

        private func outputJSON(_ projects: [Sample.Index.Project], totalProjects: Int, totalFiles: Int) {
            struct Output: Encodable {
                let totalProjects: Int
                let totalFiles: Int
                let framework: String?
                let projects: [ProjectOutput]
            }

            struct ProjectOutput: Encodable {
                let id: String
                let title: String
                let description: String
                let frameworks: [String]
                let fileCount: Int
            }

            let output = Output(
                totalProjects: totalProjects,
                totalFiles: totalFiles,
                framework: framework,
                projects: projects.map {
                    ProjectOutput(
                        id: $0.id,
                        title: $0.title,
                        description: $0.description,
                        frameworks: $0.frameworks,
                        fileCount: $0.fileCount
                    )
                }
            )

            let encoder = JSONEncoder()
            encoder.outputFormatting = [.prettyPrinted, .sortedKeys]

            do {
                let data = try encoder.encode(output)
                if let jsonString = String(data: data, encoding: .utf8) {
                    Cupertino.Context.composition.logging.recording.output(jsonString)
                }
            } catch {
                Cupertino.Context.composition.logging.recording.error("Error encoding JSON: \(error)")
            }
        }

        private func outputMarkdown(_ projects: [Sample.Index.Project], totalProjects: Int, totalFiles: Int) {
            Cupertino.Context.composition.logging.recording.output("# Sample Code Projects\n")
            Cupertino.Context.composition.logging.recording.output("Total: **\(totalProjects)** projects, **\(totalFiles)** files\n")

            if let framework {
                Cupertino.Context.composition.logging.recording.output("_Filtered to framework: **\(framework)**_\n")
            }

            if projects.isEmpty {
                Cupertino.Context.composition.logging.recording.output("_No projects found. Run `cupertino save --samples` to index sample code._")
                return
            }

            Cupertino.Context.composition.logging.recording.output("| Project | Frameworks | Files |")
            Cupertino.Context.composition.logging.recording.output("|---------|-----------|------:|")

            for project in projects {
                let frameworks = project.frameworks.joined(separator: ", ")
                Cupertino.Context.composition.logging.recording.output("| `\(project.id)` | \(frameworks) | \(project.fileCount) |")
            }
        }
    }
}

// MARK: - Output Format

extension CLIImpl.Command.ListSamples {
    enum OutputFormat: String, ExpressibleByArgument, CaseIterable {
        case text
        case json
        case markdown
    }
}
