
struct RouteSample {

    /// The x-coordinate in the map image (left to right) in the range [0,1]
    var x: Double

    /// The y-coordinate in the map image (top to bottom) in the range [0,1]
    var y: Double

    /// The timestamp of the sample in the range [0,1]
    var time: Double

    /// The distance of the sample in the range [0,1]
    var distance: Double

    /// The elevation of the sample in the range [0,1]
    var elevation: Double

    /// The speed of the sample in the range [0,1]
    var speed: Double

    /// The pace of the sample in the range [0,1]
    var pace: Double

    /// The heart rate of the sample in the range [0,1]
    var hr: Double

    /// The active energy rate of the sample in the range [0,1]
    var energy: Double?
}

extension RouteSample: Codable {

    func encode(to encoder: any Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(x.rounded(decimals: 4), forKey: .x)
        try container.encode(y.rounded(decimals: 4), forKey: .y)
        try container.encode(time.rounded(decimals: 4), forKey: .time)
        try container.encode(distance.rounded(decimals: 4), forKey: .distance)
        try container.encode(elevation.rounded(decimals: 4), forKey: .elevation)
        try container.encode(speed.rounded(decimals: 4), forKey: .speed)
        try container.encode(pace.rounded(decimals: 4), forKey: .pace)
        try container.encode(hr.rounded(decimals: 4), forKey: .hr)
        try container.encodeIfPresent(energy?.rounded(decimals: 4), forKey: .energy)
    }
}

extension RouteSample: Identifiable {

    var id: Double { x }
}

extension RouteSample {

    static var zero: RouteSample {
        .init(x: 0, y: 0, time: 0, distance: 0, elevation: 0, speed: 0, pace: 0, hr: 0, energy: nil)
    }
}

extension Collection where Element == RouteSample {

    var averageSample: RouteSample {
        guard var average = first else {
            return .zero
        }
        var energySamples = average.energy == nil ? 0 : 1
        for sample in dropFirst() {
            average.x += sample.x
            average.y += sample.y
            average.time += sample.time
            average.distance += sample.distance
            average.elevation += sample.elevation
            average.speed += sample.speed
            average.pace += sample.pace
            average.hr += sample.hr
            if let energy = sample.energy {
                average.energy = (average.energy ?? 0) + energy
                energySamples += 1
            }
        }
        let scale = 1 / Double(count)
        average.x *= scale
        average.y *= scale
        average.time *= scale
        average.distance *= scale
        average.elevation *= scale
        average.speed *= scale
        average.pace *= scale
        average.hr *= scale
        if let energy = average.energy, energySamples > 0 {
            average.energy = energy / Double(energySamples)
        }
        return average
    }
}
