public enum HealthColorBucket: Int, CaseIterable, Equatable, Comparable, Sendable {
    case white = 0
    case yellow = 1
    case red = 3

    public static func < (lhs: HealthColorBucket, rhs: HealthColorBucket) -> Bool {
        lhs.rawValue < rhs.rawValue
    }
}

public enum GaugeDomain: Int, CaseIterable, Equatable, Sendable {
    case cpu = 0
    case gpu = 1
    case memory = 2

    var tieBreakPriority: Int {
        switch self {
        case .memory:
            3
        case .gpu:
            2
        case .cpu:
            1
        }
    }
}

public struct GaugeModel: Equatable, Sendable {
    public var domain: GaugeDomain
    public var bucket: HealthColorBucket
    public var fillFraction: Double?
    public var displayValue: String

    public init(
        domain: GaugeDomain,
        bucket: HealthColorBucket,
        fillFraction: Double?,
        displayValue: String)
    {
        self.domain = domain
        self.bucket = bucket
        self.fillFraction = fillFraction
        self.displayValue = displayValue
    }
}

public struct SystemHealthModel: Equatable, Sendable {
    public var cpu: GaugeModel
    public var gpu: GaugeModel
    public var memory: GaugeModel
    public var menubar: GaugeModel

    public init(cpu: GaugeModel, gpu: GaugeModel, memory: GaugeModel, menubar: GaugeModel) {
        self.cpu = cpu
        self.gpu = gpu
        self.memory = memory
        self.menubar = menubar
    }
}

public enum HealthModel {
    private static let temperatureFillMaximumCelsius = 110.0
    private static let gpuHighCelsius = 75.0
    private static let gpuExtremeCelsius = 88.0
    private static let cpuHighCelsius = 90.0
    private static let cpuExtremeCelsius = 105.0

    public static func model(for snapshot: SystemSnapshot) -> SystemHealthModel {
        let cpu = self.temperatureGauge(
            domain: .cpu,
            celsius: snapshot.cpuTemperatureCelsius,
            bucket: self.cpuBucket(for: snapshot.cpuTemperatureCelsius))
        let gpu = self.temperatureGauge(
            domain: .gpu,
            celsius: snapshot.gpuTemperatureCelsius,
            bucket: self.gpuBucket(for: snapshot.gpuTemperatureCelsius))
        let memory = GaugeModel(
            domain: .memory,
            bucket: self.memoryBucket(for: snapshot.memoryPressureLevel),
            fillFraction: self.clamp(snapshot.memoryPressureFraction),
            displayValue: "\(Int((self.clamp(snapshot.memoryPressureFraction) * 100).rounded()))%")

        let menubar = [gpu, memory].max { lhs, rhs in
            if lhs.bucket == rhs.bucket {
                return lhs.domain.tieBreakPriority < rhs.domain.tieBreakPriority
            }
            return lhs.bucket < rhs.bucket
        } ?? memory

        return SystemHealthModel(cpu: cpu, gpu: gpu, memory: memory, menubar: menubar)
    }

    public static func thermalBucket(for state: SystemThermalState) -> HealthColorBucket {
        switch state {
        case .nominal:
            .white
        case .fair:
            .yellow
        case .serious:
            .yellow
        case .critical:
            .red
        }
    }

    public static func gpuBucket(for celsius: Double?) -> HealthColorBucket {
        guard let celsius else { return .white }
        if celsius >= Self.gpuExtremeCelsius {
            return .red
        }
        if celsius >= Self.gpuHighCelsius {
            return .yellow
        }
        return .white
    }

    public static func cpuBucket(for celsius: Double?) -> HealthColorBucket {
        guard let celsius else { return .white }
        if celsius >= Self.cpuExtremeCelsius {
            return .red
        }
        if celsius >= Self.cpuHighCelsius {
            return .yellow
        }
        return .white
    }

    public static func memoryBucket(for level: MemoryPressureLevel) -> HealthColorBucket {
        switch level {
        case .normal:
            .white
        case .warning:
            .yellow
        case .critical:
            .red
        }
    }

    private static func temperatureGauge(
        domain: GaugeDomain,
        celsius: Double?,
        bucket: HealthColorBucket)
        -> GaugeModel
    {
        guard let celsius else {
            return GaugeModel(
                domain: domain,
                bucket: bucket,
                fillFraction: nil,
                displayValue: "No data")
        }

        return GaugeModel(
            domain: domain,
            bucket: bucket,
            fillFraction: self.clamp(celsius / self.temperatureFillMaximumCelsius),
            displayValue: "\(Int(celsius.rounded())) C")
    }

    private static func clamp(_ value: Double) -> Double {
        min(max(value, 0), 1)
    }
}
