
enum SamplingMode {
    case cumulative   // e.g., distance, energy
    case instantaneous // e.g., heart rate, altitude
}

struct Point: Identifiable, Equatable, Hashable {

    var x, y: Double

    var id: Double { x }

    /**
     Interpolate the y value at an x coordinate toward another point.
     */
    func interpolate(to other: Point, at location: Double) -> Double {
        let totalX = other.x - x
        if totalX == 0 {
            return (y + other.y) / 2
        }
        let diffX = location - x
        if diffX == 0 {
            return y
        }
        let ratio = diffX / totalX
        return y + (other.y - y) * ratio
    }

    static let zero = Point(x: 0, y: 0)
}

extension Array where Element == Point {

    func resample(numberOfSamples: Int, minX: Double? = nil, maxX: Double? = nil, mode: SamplingMode) -> [Point] {
        guard count >= 2, numberOfSamples > 0 else { return [] }
        let firstX = minX ?? first!.x
        let lastX = maxX ?? last!.x
        let totalDuration = lastX - firstX
        guard totalDuration > 0 else { return [] }

        let interval = totalDuration / Double(numberOfSamples)
        var result: [Point] = .init(repeating: .zero, count: numberOfSamples)

        var currentIndex = 0
        var current = self[0]

        for i in 0..<numberOfSamples {
            let startOfInterval = Double(i) * interval
            let endOfInterval = startOfInterval + interval

            var accumulated = 0.0
            var accumulatedDuration = 0.0

            while currentIndex < count - 1 {
                let next = self[currentIndex + 1]

                if next.x <= endOfInterval {
                    let dt = next.x - current.x
                    switch mode {
                    case .cumulative:
                        accumulated += next.y - current.y
                    case .instantaneous:
                        accumulated += (current.y + next.y) / 2 * dt
                        accumulatedDuration += dt
                    }
                    currentIndex += 1
                    current = next
                } else {
                    // partial segment at the interval end
                    let dt = endOfInterval - current.x
                    let slope = (next.y - current.y) / (next.x - current.x)
                    let interpolatedY = current.y + slope * dt
                    switch mode {
                    case .cumulative:
                        accumulated += interpolatedY - current.y
                    case .instantaneous:
                        accumulated += (current.y + interpolatedY) / 2 * dt
                        accumulatedDuration += dt
                    }
                    current = Point(x: endOfInterval, y: interpolatedY)
                    break
                }
            }

            result[i].x = (startOfInterval + endOfInterval) / 2 // midpoint
            switch mode {
            case .cumulative:
                result[i].y = accumulated / interval   // average rate over interval
            case .instantaneous:
                result[i].y = accumulatedDuration > 0 ? accumulated / accumulatedDuration : current.y
            }
        }
        return result
    }

    func resample(
        numberOfSamples: Int,
        minX: Double? = nil,
        maxX: Double? = nil,
        mode: SamplingMode
    ) -> [Double] {
        guard count >= 2, numberOfSamples > 0 else { return [] }
        let minX = minX ?? first!.x
        let maxX = maxX ?? last!.x
        let totalDuration = maxX - minX
        guard totalDuration > 0 else { return [] }

        let interval = totalDuration / Double(numberOfSamples)
        var result = [Double](repeating: 0.0, count: numberOfSamples)

        var currentIndex = 0
        var current = self[0]

        for i in 0..<numberOfSamples {
            let startOfInterval = minX + Double(i) * interval
            let endOfInterval = startOfInterval + interval

            var accumulated = 0.0
            var accumulatedDuration = 0.0

            while currentIndex < count - 1 {
                let next = self[currentIndex + 1]

                if next.x <= endOfInterval {
                    let dt = next.x - current.x
                    switch mode {
                    case .cumulative:
                        accumulated += next.y - current.y
                    case .instantaneous:
                        accumulated += (current.y + next.y) / 2 * dt
                        accumulatedDuration += dt
                    }
                    currentIndex += 1
                    current = next
                } else {
                    let dt = endOfInterval - current.x
                    let slope = (next.y - current.y) / (next.x - current.x)
                    let interpolatedY = current.y + slope * dt
                    switch mode {
                    case .cumulative:
                        accumulated += interpolatedY - current.y
                    case .instantaneous:
                        accumulated += (current.y + interpolatedY) / 2 * dt
                        accumulatedDuration += dt
                    }
                    current = Point(x: endOfInterval, y: interpolatedY)
                    break
                }
            }

            let value: Double
            switch mode {
            case .cumulative:
                value = accumulated / interval
            case .instantaneous:
                value = accumulatedDuration > 0 ? accumulated / accumulatedDuration : current.y
            }
            result[i] = value
        }

        return result
    }
}

extension Sequence {
    /// Resamples any sequence of elements into evenly spaced intervals.
    ///
    /// - Parameters:
    ///   - numberOfSamples: Number of output points
    ///   - xSelector: Closure to get the independent variable (e.g., time)
    ///   - ySelector: Closure to get the dependent variable (e.g., distance, heart rate)
    ///   - mode: .cumulative or .instantaneous
    /// - Returns: Array of Points with evenly spaced `x` and averaged `y`
    func resample(
        numberOfSamples: Int,
        x: (Element) -> Double,
        y: (Element) -> Double,
        mode: SamplingMode
    ) -> [Point] {
        let points = self.map { Point(x: x($0), y: y($0)) }
        return points.resample(numberOfSamples: numberOfSamples, mode: mode)
    }

    func resample(
        numberOfSamples: Int,
        minX: Double? = nil,
        maxX: Double? = nil,
        x: (Element) -> Double,
        y: (Element) -> Double,
        mode: SamplingMode
    ) -> [Double] {
        let points = self.map { Point(x: x($0), y: y($0)) }
        return points.resample(
            numberOfSamples: numberOfSamples,
            minX: minX,
            maxX: maxX,
            mode: mode)
    }

}
