import Foundation

// MARK: - Protocol

protocol Describable {
    var summary: String { get }
}

// MARK: - Model

/// A weather observation that conforms to Codable for JSON serialization
/// and Describable for human-readable output.
struct WeatherObservation: Codable, Identifiable, Describable {
    let id: UUID
    let stationName: String
    let temperatureCelsius: Double
    let humidity: Double
    let windSpeedKmh: Double
    let condition: Condition
    let recordedAt: Date

    enum Condition: String, Codable, CaseIterable {
        case sunny, cloudy, rainy, snowy, stormy, foggy, windy
    }

    // MARK: Computed properties

    var temperatureFahrenheit: Double {
        temperatureCelsius * 9.0 / 5.0 + 32.0
    }

    var windBeaufort: Int {
        switch windSpeedKmh {
        case ..<1:    return 0
        case ..<6:    return 1
        case ..<12:   return 2
        case ..<20:   return 3
        case ..<29:   return 4
        case ..<39:   return 5
        case ..<50:   return 6
        case ..<62:   return 7
        case ..<75:   return 8
        case ..<89:   return 9
        case ..<103:  return 10
        case ..<118:  return 11
        default:      return 12
        }
    }

    var heatIndex: Double? {
        guard temperatureCelsius >= 27.0 && humidity >= 40.0 else { return nil }
        let t = temperatureFahrenheit
        let r = humidity
        let hi = -42.379 + 2.04901523 * t + 10.14333127 * r
                 - 0.22475541 * t * r - 0.00683783 * t * t
                 - 0.05481717 * r * r + 0.00122874 * t * t * r
                 + 0.00085282 * t * r * r - 0.00000199 * t * t * r * r
        return (hi - 32.0) * 5.0 / 9.0
    }

    // MARK: Describable

    var summary: String {
        let temp = String(format: "%.1f", temperatureCelsius)
        return "\(stationName): \(temp) C, \(condition.rawValue), wind \(windSpeedKmh) km/h"
    }

    // MARK: Factory

    static func random(station: String = "Station-\(Int.random(in: 1...999))") -> WeatherObservation {
        WeatherObservation(
            id: UUID(),
            stationName: station,
            temperatureCelsius: Double.random(in: -20...45),
            humidity: Double.random(in: 10...100),
            windSpeedKmh: Double.random(in: 0...130),
            condition: Condition.allCases.randomElement()!,
            recordedAt: Date()
        )
    }
}

// MARK: - Extension: Statistics

extension Array where Element == WeatherObservation {
    var averageTemperature: Double? {
        guard !isEmpty else { return nil }
        return map(\.temperatureCelsius).reduce(0, +) / Double(count)
    }

    var hottestStation: WeatherObservation? {
        self.max(by: { $0.temperatureCelsius < $1.temperatureCelsius })
    }

    var coldestStation: WeatherObservation? {
        self.min(by: { $0.temperatureCelsius < $1.temperatureCelsius })
    }

    func filtered(by condition: WeatherObservation.Condition) -> [WeatherObservation] {
        filter { $0.condition == condition }
    }

    func groupedByCondition() -> [WeatherObservation.Condition: [WeatherObservation]] {
        Dictionary(grouping: self, by: \.condition)
    }
}

// MARK: - Comparable conformance

extension WeatherObservation: Comparable {
    static func < (lhs: WeatherObservation, rhs: WeatherObservation) -> Bool {
        lhs.temperatureCelsius < rhs.temperatureCelsius
    }
}

// MARK: - Equatable

extension WeatherObservation: Equatable {
    static func == (lhs: WeatherObservation, rhs: WeatherObservation) -> Bool {
        lhs.id == rhs.id
    }
}