import CodexBarCore
import Foundation

enum CLIErrorKind: String, Encodable, Sendable {
    case args
    case config
    case provider
    case runtime
}

struct ProviderErrorPayload: Encodable, Sendable {
    let code: Int32
    let message: String
    let kind: CLIErrorKind?
}

extension CodexBarCLI {
    static func makeErrorPayload(_ error: Error, kind: CLIErrorKind? = nil) -> ProviderErrorPayload {
        ProviderErrorPayload(
            code: self.mapError(error).rawValue,
            message: error.localizedDescription,
            kind: kind)
    }

    static func makeErrorPayload(code: ExitCode, message: String, kind: CLIErrorKind? = nil) -> ProviderErrorPayload {
        ProviderErrorPayload(code: code.rawValue, message: message, kind: kind)
    }

    static func makeCLIErrorPayload(
        message: String,
        code: ExitCode,
        kind: CLIErrorKind,
        pretty: Bool) -> String?
    {
        let payload = ProviderPayload(
            providerID: "cli",
            account: nil,
            version: nil,
            source: "cli",
            status: nil,
            usage: nil,
            credits: nil,
            antigravityPlanInfo: nil,
            openaiDashboard: nil,
            error: ProviderErrorPayload(code: code.rawValue, message: message, kind: kind))
        return self.encodeJSON([payload], pretty: pretty)
    }

    static func makeProviderErrorPayload(
        provider: UsageProvider,
        account: String?,
        cacheAccountKey: String? = nil,
        source: String,
        status: ProviderStatusPayload?,
        error: Error,
        kind: CLIErrorKind = .provider) -> ProviderPayload
    {
        ProviderPayload(
            provider: provider,
            account: account,
            cacheAccountKey: cacheAccountKey,
            version: nil,
            source: source,
            status: status,
            usage: nil,
            credits: nil,
            antigravityPlanInfo: nil,
            openaiDashboard: nil,
            error: self.makeErrorPayload(error, kind: kind))
    }

    static func encodeJSON(_ payload: some Encodable, pretty: Bool) -> String? {
        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = .iso8601
        encoder.outputFormatting = pretty ? [.prettyPrinted, .sortedKeys] : []
        guard let data = try? encoder.encode(payload) else { return nil }
        return String(data: data, encoding: .utf8)
    }

    static func printJSON(_ payload: some Encodable, pretty: Bool) {
        if let output = self.encodeJSON(payload, pretty: pretty) {
            print(output)
        }
    }

    static func exit(
        code: ExitCode,
        message: String? = nil,
        output: CLIOutputPreferences? = nil,
        kind: CLIErrorKind = .runtime) -> Never
    {
        if self.shouldPrintExitError(code: code, message: message) {
            if let output, output.usesJSONOutput {
                let payload = self.makeCLIErrorPayload(
                    message: message ?? "",
                    code: code,
                    kind: kind,
                    pretty: output.pretty)
                if let payload {
                    print(payload)
                }
            } else if let message {
                self.writeStderr("\(message)\n")
            }
        }
        platformExit(code.rawValue)
    }

    static func shouldPrintExitError(code: ExitCode, message: String?) -> Bool {
        code != .success && message != nil
    }

    static func printError(_ error: Error, output: CLIOutputPreferences, kind: CLIErrorKind = .runtime) {
        if output.usesJSONOutput {
            let payload = ProviderPayload(
                providerID: "cli",
                account: nil,
                version: nil,
                source: "cli",
                status: nil,
                usage: nil,
                credits: nil,
                antigravityPlanInfo: nil,
                openaiDashboard: nil,
                error: self.makeErrorPayload(error, kind: kind))
            self.printJSON([payload], pretty: output.pretty)
        } else {
            self.writeStderr("Error: \(error.localizedDescription)\n")
        }
    }
}
