// Copyright © 2024 Ryan Booker. All rights reserved.

import ArgumentParser
import Domain
import Foundation
import Toolbox

public struct Summarize: AsyncParsableCommand {
    public static let configuration = CommandConfiguration(
        commandName: "summarize",
        abstract: "Summarize something.",
        usage: "kg summarize \"Some text or a url to summarize.\""
    )

    @Argument(help: "Some text or a url to summarize") 
    public var query: Either<URL, String>
    
    @Option(help: Engine.help) 
    public var engine: Engine?

    @Option(help: SummaryType.help) 
    public var summaryType: SummaryType?

    var client = Client.liveValue

    enum CodingKeys: CodingKey {
        case query, engine, summaryType
    }

    public init() {
        // Intentionally left empty
    }

    public mutating func run() async throws {
        do {
            let response = try await Client.liveValue.run(query: query, engine: engine, summaryType: summaryType)
            print(response)
        } catch {
            throw ValidationError(error.localizedDescription)
        }
    }

}

// MARK: - Processing

extension Summarize {
    struct Client {
        var run: @Sendable (
            _ query: Either<URL, String>,
            _ engine: Engine?,
            _ summaryType: SummaryType?
        ) async throws -> Response<Summarization>

        @inlinable
        func run(
            query: Either<URL, String>,
            engine: Engine?,
            summaryType: SummaryType?
        ) async throws -> Response<Summarization> {
            try await run(query, engine, summaryType)
        }
    }
}

extension Summarize.Client {
    static let liveValue = Self(
        run: { query, engine, summaryType in
            var body: [String: Any] = query.reduce(first: { ["url": $0.absoluteString] }, second: { ["text": $0] })

            if let engine {
                body["engine"] = engine.rawValue
            }

            if let summaryType {
                body["summary_type"] = summaryType.rawValue
            }

            var request = try Endpoint.summarize.authorizedRequest
            request.httpBody = try JSONSerialization.data(withJSONObject: body)

            let (data, response) = try await URLSession.shared.data(for: request)

            do {
                return try JSONDecoder().decode(Response.self, from: data)
            } catch {
                #if DEBUG
                print(response)
                print(error)
                print("-----")
                #endif
                throw error
            }
        }
    )
}

// MARK: - Response

extension Summarize {
    public enum Engine: String, CaseIterable, CustomStringConvertible, ExpressibleByArgument {
        case cecil
        case agnes
        case daphne
        case muriel

        public var description: String {
            switch self {
            case .cecil:
                "Friendly, descriptive, fast summary (default)"
            case .agnes:
                "Formal, technical, analytical summary"
            case .daphne:
                "Informal, creative, friendly summary"
            case .muriel:
                "Best-in-class summary using our enterprise-grade model"
            }
        }

        public static var help: ArgumentHelp? {
            .init(
                allCases.map { "- \($0.rawValue): \($0.description)" }.joined(separator: "\n") + "\n"
            )
        }

        public var defaultValueDescription: String {
            Self.cecil.rawValue
        }
    }

    public enum SummaryType: String, CaseIterable, CustomStringConvertible, ExpressibleByArgument {
        case summary
        case takeaway

        public var description: String {
            switch self {
            case .summary:
                "Paragraph(s) of summary prose (default)"
            case .takeaway:
                "Bulleted list of key points"
            }
        }

        public static var help: ArgumentHelp? {
            .init(
                allCases.map { "- \($0.rawValue): \($0.description)" }.joined(separator: "\n") + "\n"
            )
        }

        public var defaultValueDescription: String {
            Self.summary.rawValue
        }
    }

    public struct Summarization: CustomStringConvertible, Decodable {
        public var output: String
        public var tokens: Int

        public var description: String {
            """
            Summary:

            \(output)
            """
        }
    }
}