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

import Foundation
import Toolbox

public struct Response<T>: CustomStringConvertible, Decodable where T: CustomStringConvertible & Decodable {
    public let meta: Metadata
    public let data: Result<T, Failure>

    public var description: String {
        data.reduce(success: \.description, failure: \.description)
    }

    public init(meta: Metadata, data: Result<T, Failure>) {
        self.meta = meta
        self.data = data
    }

    public enum CodingKeys: CodingKey {
        case meta, data, error
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.meta = try container.decode(Metadata.self, forKey: .meta)
        do {
            let data = try container.decode(T.self, forKey: .data)
            self.data = .success(data)
        } catch {
            let reasons = try container.decode([Failure.Reason].self, forKey: .error)
            self.data = .failure(.init(reasons: reasons))
        }
    }
}

// MARK: - Repsonse types

extension Response {
    public struct Failure: CustomStringConvertible, Decodable, Error {
        public struct Reason: CustomStringConvertible, Decodable {
            public let code: Int
            public let msg: String

            public var description: String {
                "\(code): \(msg)"
            }
        }

        public let reasons: [Reason]

        public var description: String {
        """
        Whoops!

        \(reasons.map(\.description).joined(separator: "\n\n"))
        """
        }
    }

    public struct Metadata: Decodable {
        public let id: String
        public let node: String
        public let ms: Int
    }
}