//
//  ActivityRecorder.swift
//  Gran Fondo
//
//  Created by Willem L. Middelkoop on 27/04/2024.
//

import Foundation
import Combine
import HealthKit
import CoreLocation
import ActivityKit
import UIKit
import AVFoundation

class ActivityRecorder: NSObject, ObservableObject, CLLocationManagerDelegate, UIApplicationDelegate {
    static let NOVALUE: Int = -999999
    static let FREEMETERS: Double = 10985 // isn't that funny.
    
    @Published var isPaused = false
    @Published var isRecording = false
    @Published var isSaving = false
    @Published var selectedWorkoutType: HKWorkoutActivityType {
        didSet {
            saveSelectedWorkoutType()
        }
    }
    @Published var dataPoints: [DataPoint] = []
    @Published var dataPointsSelected: [DataPoint] = []
    @Published var dataPointsFiltered: [DataPointScope: [DataPoint]] = [
        .lastMinute: [],
        .last5Minutes: [],
        .last15Minutes: [],
        .lastHour: [],
        .sinceStart: []
    ]
    private var scopeGranularity: [DataPointScope: TimeInterval] = [
        .lastMinute: 1,
        .last5Minutes: 1,
        .last15Minutes: 5,
        .lastHour: 15,
        .sinceStart: 60
    ]
    @Published var preferredScope: DataPointScope = .sinceStart
    @Published var selectedScope: DataPointScope = .lastMinute
    @Published var intervals: [DataPoint] = []
    
    private var liveActivity: Activity<Gran_Fondo_StatusAttributes>?
    
    //private var dataStore: [HeartRateData] = []
    private var workoutBuilder: HKWorkoutBuilder?
    private var routeBuilder: HKWorkoutRouteBuilder?
    private var healthStore: HKHealthStore?
    private var bluetoothManager: BluetoothManager
    private var recordingStartDate: Date
    private var speechSynthesizer: AVSpeechSynthesizer
    
    private var lastSpokenDataPointCount: Int = 0
        
    private var cancellables = Set<AnyCancellable>()
    private var locationManager: CLLocationManager?
    private var lastKnownLocation: CLLocation?
    
    @Published var unitsPreference = UnitsPreference.Metric
    
    @Published var activeEnergyInputHeight: Double = 175 // cm
    @Published var activeEnergyInputWeight: Double = 70 // kilos
    
    @Published var activeEnergyInputHeartRateRest: Int = 70 // bpm
    @Published var activeEnergyInputHeartRateMax: Int = 190 // bpm
    
    @Published var activeEnergyInputSex: HKBiologicalSex = .notSet
    
    private var speedBuffer: [Double] = []
    @Published var currentSpeed: Double = 0
    @Published var currentHeartRate: Int = ActivityRecorder.NOVALUE
    @Published var currentPower: Int = ActivityRecorder.NOVALUE
    @Published var currentCadence: Int = ActivityRecorder.NOVALUE
    
    @Published var currentScopeDistance: Double = 0
    @Published var currentScopeAverageSpeed: Double = 0
    @Published var currentScopeAverageHeartRate: Int = 0
    @Published var currentScopeAveragePower: Int = 0
    @Published var currentScopeAverageCadence: Int = 0
    
    @Published var isLocationRestricted: Bool = true
    @Published var isHealthKitRestricted: Bool = true
    
    @Published var isWelcomed: Bool = false
    
    @Published var totalDistance: Double = 0
    
    private var nextIntervalDistance: Double = 0
    private var intervalDistanceRunning: Double =  1000; // in meters, can change when folks select imperial units
    private var intervalDistanceCycling: Double = 5000; // in meters
    private var currentIntervalStartDate: Date
    
    @Published var totalDistanceLifetime: Double = 0
    
    @Published var elapsedTime: TimeInterval = 0
    private var timer: Timer?
    
    @Published var workouts: [HKWorkout] = []
    
    init(bluetoothManager: BluetoothManager, workoutType: HKWorkoutActivityType = .cycling) {
        self.bluetoothManager = bluetoothManager
        // Load the saved workout type or default to .other
        selectedWorkoutType = UserDefaults.standard.object(forKey: UserDefaultsKeys.selectedWorkoutType)
            .flatMap { HKWorkoutActivityType(rawValue: $0 as? UInt ?? HKWorkoutActivityType.other.rawValue) } ?? .cycling
        
        self.totalDistanceLifetime = UserDefaults.standard.double(forKey: "totalDistanceLifetime")
        self.isWelcomed = UserDefaults.standard.bool(forKey: "isWelcomed")
        
        self.speechSynthesizer = AVSpeechSynthesizer()
        
        recordingStartDate = Date()
        currentIntervalStartDate = recordingStartDate
       
        super.init()
        configureAudioSession()
        
        setDataPointScope(to: self.preferredScope)
        
        self.activeEnergyInputHeight = UserDefaults.standard.double(forKey: "activeEnergyInputHeight")
        if self.activeEnergyInputHeight < 50 || self.activeEnergyInputHeight > 300 {
            self.setActiveEnergyInputHeight(to: 170)
        }
        
        self.activeEnergyInputWeight = UserDefaults.standard.double(forKey: "activeEnergyInputWeight")
        if self.activeEnergyInputWeight < 50 || self.activeEnergyInputWeight > 500{
            self.setActiveEnergyInputWeight(to: 70)
        }
        
        self.activeEnergyInputHeartRateMax = UserDefaults.standard.integer(forKey: "activeEnergyInputHeartRateMax")
        self.activeEnergyInputHeartRateRest = UserDefaults.standard.integer(forKey: "activeEnergyInputHeartRateRest")
        
        if self.activeEnergyInputHeartRateMax < 50 || self.activeEnergyInputHeartRateMax < self.activeEnergyInputHeartRateRest {
            self.setActiveEnergyInputHeartRateMax(to: 170)
        }
        if self.activeEnergyInputHeartRateRest < 20 || self.activeEnergyInputHeartRateRest > 200 {
            self.setActiveEnergyInputHeartRateRest(to: 70)
        }
        
        let sexRawValue = UserDefaults.standard.integer(forKey: "activeEnergyInputSex")
        if let sex = HKBiologicalSex(rawValue: sexRawValue) {
            self.activeEnergyInputSex = sex
        } else {
            self.activeEnergyInputSex = .notSet
        }
        
        if let scopeRawValue = UserDefaults.standard.string(forKey: "preferredScope"),
           let scope = DataPointScope(rawValue: scopeRawValue) {
            self.preferredScope = scope
        } else {
            self.preferredScope = .last5Minutes // Default value if nothing is saved
        }
        if let unitsRawValue = UserDefaults.standard.string(forKey: "preferredUnits"),
           let unitsPreference = UnitsPreference(rawValue: unitsRawValue) {
            self.unitsPreference = unitsPreference
        } else {
            if(Locale.current.measurementSystem == .metric){
                self.unitsPreference = UnitsPreference.Metric
            }else{
                self.unitsPreference = UnitsPreference.Imperial
            }
        }
        
        if self.unitsPreference == UnitsPreference.Imperial {
            self.intervalDistanceRunning = 1609.344 // one mile in kilometer
            self.intervalDistanceCycling = 8046.72 // five miles in kilometer
        }
        
        if(isWelcomed){
            startEverything()
        }
        
        setupSubscriptions()
        
        startTimer()
        
        NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.willResignActiveNotification, object: nil)
        
        NotificationCenter.default.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(appCameToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)

    }
    
    func configureAudioSession() {
        do {
            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
            try AVAudioSession.sharedInstance().setActive(true)
        } catch {
            print("Failed to set up audio session: \(error)")
        }
    }
    
    public func startEverything(){
        self.startLocation()
        self.startHealthKit()
        self.bluetoothManager.startBluetooth()
    }
    
    public func markAsWelcomed(){
        self.isWelcomed = true
        UserDefaults.standard.setValue(self.isWelcomed, forKey: "isWelcomed")
    }
    
    public func startLocation(){
        DispatchQueue.main.async {
            self.setupLocationManager()
            self.locationManager?.startUpdatingLocation()
            self.checkLocationAuthorization()
        }
    }
    func checkLocationAuthorization() {
        if let status = locationManager?.authorizationStatus{
            
            switch status {
            case .authorizedAlways, .authorizedWhenInUse:
                print("ActivityRecorder: Location permission granted.")
                self.isLocationRestricted = false
            case .denied:
                print("ActivityRecorder: Location permission denied.")
                self.isLocationRestricted = true
            case .restricted:
                print("ActivityRecorder: Location access restricted.")
                self.isLocationRestricted = true
            case .notDetermined:
                print("ActivityRecorder: Location permission not determined. Requesting permission...")
                locationManager?.requestAlwaysAuthorization()
            @unknown default:
                print("ActivityRecorder: Unknown location authorization status")
                self.isLocationRestricted = true
            }
        }else{
            self.isLocationRestricted = true
        }
    }
    
    public func startHealthKit(){
        DispatchQueue.main.async {
            if HKHealthStore.isHealthDataAvailable() {
                self.healthStore = HKHealthStore()
                self.requestHealthKitPermissions()
                
            }
        }
    }
    
    private func startTimer() {
        guard timer == nil else { return } // Prevent creating multiple timers
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.timerTick()
        }
    }

    private func stopTimer() {
        timer?.invalidate()
        timer = nil
    }

    private func timerTick() {
        self.recordDataPoint()
        if self.isRecording{
            self.elapsedTime = Date().timeIntervalSince(self.recordingStartDate)
        }
    }
    
    @objc func appWillResignActive(_ application: UIApplication) {
        print("ActivityRecorder: App will resign active")
        if self.isRecording {
            startLiveActivity()
        }
    }
    
    @objc func appMovedToBackground() {
        if !self.isRecording {
            print("ActivityRecorder: Suspending updating of location to conserve power")
            locationManager?.stopUpdatingLocation()
            stopTimer()
            
            bluetoothManager.stopScanningForSensors()
        }
    }

    @objc func appCameToForeground() {
        Task{
            await self.endLiveActivity()
        }
        
        if !self.isRecording {
            resetData() // clear any weird gaps in the graphs // data due to timer sleeping.
        }
        
        startTimer()
        
        locationManager?.startUpdatingLocation()
        bluetoothManager.scanForSensors() 
    }
    
    private func setupLocationManager() {
        print("ActivityRecorder: Setting up location manager...")
        locationManager = CLLocationManager()
        locationManager?.delegate = self
        locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
        locationManager?.activityType = .fitness
        locationManager?.allowsBackgroundLocationUpdates = true
        print("ActivityRecorder: Location manager setup completed.")
    }
    
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        DispatchQueue.main.async {
            switch manager.authorizationStatus {
            case .notDetermined:
                // Request appropriate authorization from the user
                print("ActivityRecorder: Location access is not determined.")
                self.isLocationRestricted = true
                manager.requestAlwaysAuthorization()  // or requestAlwaysAuthorization(), as needed
            case .restricted, .denied:
                print("ActivityRecorder: Location access is restricted or denied by the user.")
                self.isLocationRestricted = true
            case .authorizedWhenInUse, .authorizedAlways:
                //manager.requestLocation()  // Request location if already authorized
                print("ActivityRecorder: Location access is granted by the user.")
                self.isLocationRestricted = false
                manager.startUpdatingLocation()
                manager.requestLocation()
            @unknown default:
                self.isLocationRestricted = true
                print("ActivityRecorder: Unknown authorization status")
            }
        }
    }
    
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let firstLocation = locations.first else { return }
        guard let lastLocation = locations.last else { return }
       // print("New location: \(lastLocation)")
        
       // Start with the last known location if it exists
       var previousLocation = lastKnownLocation ?? firstLocation

       // Calculate the distance for each new location
       for location in locations {
           let distance = previousLocation.distance(from: location)
           totalDistance += distance
          // print("ActivityRecorder: Added \(distance) meters to total distance, now \(totalDistance) meters.")
           previousLocation = location
       }

       // Update the last known location to the last of the new locations
       lastKnownLocation = locations.last
        
        // Calculate the moving average of the speed
        if lastLocation.speed >= 0 {
            let currentSpeed = lastLocation.speed * 3.6 // Convert m/s to km/h
            self.speedBuffer.append(currentSpeed)
            
            // Keep the buffer size to 5 to calculate the average of the last 5 readings
            if self.speedBuffer.count > 10 {
                self.speedBuffer.removeFirst()
            }
            
            let averageSpeed = self.speedBuffer.reduce(0, +) / Double(self.speedBuffer.count)
            self.currentSpeed = averageSpeed // Use the smoothed speed
        }
        
        if self.isRecording && !self.isPaused {
            routeBuilder?.insertRouteData(locations) { (success, error) in
                if !success {
                    print("ActivityRecorder: Failed to insert route data: \(String(describing: error))")
                }
            }
        }
    }

   func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
       print("ActivityRecorder: Failed to get location: \(error.localizedDescription)")
   }
    
    func requestHealthKitPermissions() {
        // Define data types that the app needs permission to write to HealthKit.
        let typesToShare: Set = [
            HKObjectType.workoutType(),  // Permission to write workouts
            HKQuantityType.quantityType(forIdentifier: .heartRate)!,
            HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)!,
            HKQuantityType.quantityType(forIdentifier: .distanceCycling)!,
            HKQuantityType.quantityType(forIdentifier: .cyclingPower)!,
            HKQuantityType.quantityType(forIdentifier: .cyclingSpeed)!,
            HKQuantityType.quantityType(forIdentifier: .cyclingCadence)!,
            HKQuantityType.quantityType(forIdentifier: .runningSpeed)!,
            HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)! ,
            HKSeriesType.workoutRoute() // For GPS tracking during workouts
        ]

        // Define data types that the app needs permission to read from HealthKit.
        let typesToRead: Set = [
            HKQuantityType.quantityType(forIdentifier: .heartRate)!,
            HKQuantityType.quantityType(forIdentifier: .restingHeartRate)!,
            HKQuantityType.quantityType(forIdentifier: .bodyMass)!,
            HKQuantityType.quantityType(forIdentifier: .height)!,
            HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)!,
            HKQuantityType.quantityType(forIdentifier: .distanceCycling)!,
            HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)!,
            HKQuantityType.quantityType(forIdentifier: .cyclingPower)!,
            HKQuantityType.quantityType(forIdentifier: .cyclingSpeed)!,
            HKQuantityType.quantityType(forIdentifier: .cyclingCadence)!,
            HKQuantityType.quantityType(forIdentifier: .runningSpeed)!,
            HKObjectType.characteristicType(forIdentifier: .biologicalSex)!,
            HKObjectType.workoutType()  // To read workouts
        ]

        // Request authorization to access the specified types.
        healthStore?.requestAuthorization(toShare: typesToShare, read: typesToRead) { success, error in
            DispatchQueue.main.async {
                if !success {
                    // Handle the error here if authorization failed.
                    print("ActivityRecorder: HealthKit authorization failed with error: \(String(describing: error))")
                    self.isHealthKitRestricted = true
                } else {
                    // Here, handle the success scenario if needed.
                    print("ActivityRecorder: HealthKit authorization granted.")
                    self.isHealthKitRestricted = false
                    
                    Task{
                        await self.fetchAndSaveWorkouts()
                    }
                    
                    Task{
                        await self.fetchActiveEnergyInputParameters()
                    }
                }
            }
        }
    }
    
    private func setupSubscriptions() {
        
        bluetoothManager.$currentCyclingPower
            .receive(on: RunLoop.main)
            .sink { [weak self] power in
                guard let self = self else { return }
                self.currentPower =  (self.bluetoothManager.isPowerMeterConnected) ? power : ActivityRecorder.NOVALUE
                // print("ActivityRecorder: Updated Power: \(power)")
            }
            .store(in: &cancellables)
        
        bluetoothManager.$currentCyclingCadence
            .receive(on: RunLoop.main)
            .sink { [weak self] cadence in
                guard let self = self else { return }
                self.currentCadence =  (self.bluetoothManager.isCadenceSensorConnected) ? cadence : ActivityRecorder.NOVALUE
                // print("ActivityRecorder: Updated Cadence: \(cadence)")
            }
            .store(in: &cancellables)
        
        bluetoothManager.$currentHeartRate
            .receive(on: RunLoop.main)
            .sink { [weak self] heartRate in
                guard let self = self else { return }
                self.currentHeartRate =  (self.bluetoothManager.isHeartRateMonitorConnected) ? heartRate : ActivityRecorder.NOVALUE
                // print("ActivityRecorder: Updated Heart Rate: \(cadence)")
            }
            .store(in: &cancellables)
    }
    
    private func recordDataPoint() {
        
        let now = Date()
        
        let newPoint = DataPoint(
            timestamp: now,
            start: now,
            end: now,
            speed: self.isPaused ? 0 : self.currentSpeed,
            heartRate: self.currentHeartRate,
            power: self.isPaused ? 0 : self.currentPower,
            cadence: self.isPaused ? 0 : self.currentCadence,
            distance: self.totalDistance
        )
        self.dataPoints.append(newPoint)
        
        if self.isRecording {
            if self.totalDistance >= self.nextIntervalDistance {
                self.saveCurrentInterval()
            }
        }
        
        if (self.preferredScope == .lastMinute && ( self.dataPoints.count == 10 || self.dataPoints.count - self.lastSpokenDataPointCount > 120 )) {
            
            let distance = self.getDistanceString(self.totalDistance)
            let speed = self.getSpeedString(self.currentScopeAverageSpeed, workoutType: self.selectedWorkoutType)
            let heart = self.bluetoothManager.isHeartRateMonitorConnected
                ? "Heart rate: \(self.currentScopeAverageHeartRate) BPM"
                : ""
            
            //let text = "Afstand: \(distance) Snelheid: 5'12\" per kilometer. Hartslag: \(self.currentScopeAverageHeartRate) BPM"
            let text = "Distance: \(distance) Speed: \(speed) \(heart)"
            print(text)
            
            let speechUtterance = AVSpeechUtterance(string: text)//  "Tik")
            //let speechUtterance = AVSpeechUtterance(string: "Tussentijd 2, snelheid 5 minuut 54.")
            
            speechUtterance.voice = AVSpeechSynthesisVoice(language: "en-UK")
            speechUtterance.rate = AVSpeechUtteranceDefaultSpeechRate
            speechUtterance.pitchMultiplier = 1.0
            speechUtterance.volume = 1.0
            
            self.lastSpokenDataPointCount = self.dataPoints.count
            
            speechSynthesizer.speak(speechUtterance)
        }
        
       
        
        DispatchQueue.main.async {
            self.setDataPointScope(to: self.preferredScope)
            self.updateFilteredData(with: newPoint)
        }
       // print("ActivityRecorder: recordDataPoint.")
        Task{
         //   print("ActivityRecorder: recordDataPoint inside Task \(self.currentHeartRate).")
            await self.updateLiveActivity()
        }
    }
    
    func saveCurrentInterval(){
        print("ActivityRecorder: completed interval \(self.nextIntervalDistance), now starting the next")
        
        let intervalPoint = self.averageDataPointSince(self.currentIntervalStartDate, timestamp: Date())
        self.intervals.append(intervalPoint)
        
        print("ActivityRecorder: intervalPoint.distance: \(intervalPoint.distance) intervalPoint.speed: \(intervalPoint.speed)")
        print("ActivityRecorder: as speed string: \(self.getSpeedString(intervalPoint.speed, workoutType: self.selectedWorkoutType))")
        
        self.nextIntervalDistance = self.nextIntervalDistance + (self.selectedWorkoutType == .cycling
            ? self.intervalDistanceCycling
            : self.intervalDistanceRunning)
        
        currentIntervalStartDate = intervalPoint.end
        print("ActivityRecorder: next interval is at \(self.nextIntervalDistance)")
    }
    
    func setUnitPreference(to newUnitPreference: UnitsPreference){
        UserDefaults.standard.set(newUnitPreference.rawValue, forKey: "preferredUnits")
        self.unitsPreference = newUnitPreference
        
        if self.unitsPreference == UnitsPreference.Imperial {
            self.intervalDistanceRunning = 1609.344 // one mile in meter
            self.intervalDistanceCycling = 8046.72 // five miles in meter
        } else {
            self.intervalDistanceRunning = 1000
            self.intervalDistanceCycling = 5000
        }
    }
    func setActiveEnergyInputSex(to newSex: HKBiologicalSex){
        UserDefaults.standard.set(newSex.rawValue, forKey: "activeEnergyInputSex")
        self.activeEnergyInputSex = newSex
    }
    func setActiveEnergyInputWeight(to newWeight: Double){
        UserDefaults.standard.set(newWeight, forKey: "activeEnergyInputWeight")
        self.activeEnergyInputWeight = newWeight
    }
    func setActiveEnergyInputHeight(to newHeight: Double){
        UserDefaults.standard.set(newHeight, forKey: "activeEnergyInputHeight")
        self.activeEnergyInputHeight = newHeight
    }
    
    func setActiveEnergyInputHeartRateRest(to newValue: Int){
        UserDefaults.standard.set(newValue, forKey: "activeEnergyInputHeartRateRest")
        self.activeEnergyInputHeartRateRest = newValue
    }
    
    func setActiveEnergyInputHeartRateMax(to newValue: Int){
        UserDefaults.standard.set(newValue, forKey: "activeEnergyInputHeartRateMax")
        self.activeEnergyInputHeartRateMax = newValue
    }
    
    func setDataPointScope(to newScope: DataPointScope) {
        if preferredScope != newScope {
            UserDefaults.standard.set(newScope.rawValue, forKey: "preferredScope")
            preferredScope = newScope
        }
       
        let bestMatchingScope = self.getBestMatchingScope(preferredScope: preferredScope)
        if selectedScope != bestMatchingScope { // we're changing
            print("ActivityRecorder: changing data scope to \(bestMatchingScope)")
            selectedScope = bestMatchingScope
            
            dataPointsSelected.removeAll()
            dataPointsSelected.insert(contentsOf: dataPointsFiltered[selectedScope]!, at: 0)
        }
    }
    
    func getBestMatchingScope(preferredScope: DataPointScope) -> DataPointScope{
        let now = Date()
        let recordingStart = recordingStartDate
        
        // Calculate the cutoff time for the preferred scope
        let cutoffTime = now.addingTimeInterval(-preferredScope.duration)
        
        // If recording started before the cutoff, return the preferred scope
        if preferredScope == .sinceStart{//} && (now.timeIntervalSince(recordingStart) > DataPointScope.lastHour.duration) {
            if now.timeIntervalSince(recordingStart) > DataPointScope.lastHour.duration {
                return preferredScope
            }else{
                return getBestMatchingScope(preferredScope: DataPointScope.lastHour)
            }
        }else if cutoffTime >= recordingStart  {
            return preferredScope
        } else {
            // Find the largest scope that accommodates the recording start time
            let availableScopes = DataPointScope.allCases.filter { $0.duration > 0 && $0.duration <= preferredScope.duration }.sorted { $0.duration < $1.duration }

            for scope in availableScopes {
                let scopeCutoffTime = now.addingTimeInterval(-scope.duration)
                if  scopeCutoffTime < recordingStart{
                    return scope
                }
            }
        }

        // Return the smallest available scope if none match
        return .lastMinute
    }
    
    
    private func updateFilteredData(with newPoint: DataPoint) {
        //print("ActivityRecorder: dataPoints.count is:  \(dataPoints.count) ")
        for scope in DataPointScope.allCases {
            let granularity = scopeGranularity[scope]!

            let lastTimestamp = dataPointsFiltered[scope]?.last?.timestamp ?? Date.distantPast
            if newPoint.timestamp.timeIntervalSince(lastTimestamp) >= granularity {
                
                let averagePoint = averageDataPointSince(lastTimestamp, timestamp: newPoint.timestamp)
                dataPointsFiltered[scope]?.append(averagePoint)
            
                if scope == selectedScope{
                    dataPointsSelected.append(averagePoint)
                    
                    let relevantPoints = dataPointsSelected.filter({ $0.timestamp >= lastTimestamp.addingTimeInterval(-scope.duration)})
                    
                    // The average speed is wrong due to not accounting for
                    // the faster speeds covering more distance!
                    // 5 5 5 10 = 25 / 4 = 6.25
                    // 5 5 5 (10 + 10) = 35 / 4s = 8.75
                    
                   /* currentScopeAverageSpeed = relevantPoints.isEmpty
                        ? 0.0
                        : relevantPoints.map { $0.speed }.reduce(0, +) / Double(relevantPoints.count) */
                    currentScopeAverageSpeed = relevantPoints.isEmpty
                        ? 0.0
                        : {
                            if relevantPoints.count == 1 {
                                // only one data point, return value
                                return relevantPoints[0].speed
                            } else { // calculate a weighted average
                                let firstPoint = relevantPoints.first!
                                let lastPoint = relevantPoints.last!
                                
                                let totalDistanceInKilometers = (lastPoint.distance - firstPoint.distance) / 1000.0
                                let totalTimeInHours = lastPoint.timestamp.timeIntervalSince(firstPoint.timestamp) / 3600.0
                                return totalTimeInHours > 0 ? (totalDistanceInKilometers / totalTimeInHours) : 0.0
                            }
                        }()
                    
                    currentScopeDistance = relevantPoints.isEmpty
                        ? 0.0
                        : (relevantPoints.last?.distance ?? 0) - (relevantPoints.first?.distance ?? 0)
                    
                    let dataPointsWithHeartRate = relevantPoints.filter({ $0.heartRate > ActivityRecorder.NOVALUE })
                    currentScopeAverageHeartRate = dataPointsWithHeartRate.isEmpty
                        ? 0
                        : Int(dataPointsWithHeartRate.map { Double($0.heartRate) }.reduce(0, +) / Double(dataPointsWithHeartRate.count))
                    
                    let dataPointsWithCadence = relevantPoints.filter({ $0.cadence > ActivityRecorder.NOVALUE })
                    currentScopeAverageCadence = dataPointsWithCadence.isEmpty
                        ? 0
                        : Int(dataPointsWithCadence.map { Double($0.cadence) }.reduce(0, +) / Double(dataPointsWithCadence.count))
                    
                    let dataPointsWithPower = relevantPoints.filter({ $0.power > ActivityRecorder.NOVALUE })
                    currentScopeAveragePower = dataPointsWithPower.isEmpty
                        ? 0
                        : Int(dataPointsWithPower.map { Double($0.power) }.reduce(0, +) / Double(dataPointsWithPower.count))
                }
            
                // Calculate the cut-off time based on the last timestamp
                if scope != .sinceStart{
                    let cutOffTime = lastTimestamp.addingTimeInterval(-scope.duration)
                    
                    // Remove old data points that are out of the scope's time range
                    dataPointsFiltered[scope]?.removeAll(where: { $0.timestamp < cutOffTime })
                    
                    if scope == selectedScope{
                        dataPointsSelected.removeAll(where: { $0.timestamp < cutOffTime })
                    }
                }
            }
        }
    }
    
    private func averageDataPointSince(_ sinceTime: Date, timestamp: Date) -> DataPoint {
        let relevantPoints = dataPoints.filter { $0.timestamp >= sinceTime }
        
        //let averageSpeed = relevantPoints.map { $0.speed }.reduce(0, +) / Double(relevantPoints.count)
        let averageSpeed = relevantPoints.isEmpty
            ? 0.0
        : {
                if relevantPoints.count == 1 {
                    return relevantPoints[0].speed
                }else if relevantPoints.count < 10 {
                    // gps errors are to much of a problem with calculating
                    // average speed based on distance, we use the actual
                    // speed values for this, to simplify things:
                    return relevantPoints.map { $0.speed }.reduce(0, +) / Double(relevantPoints.count)
                    
                }else{
                    // given more data points, we calculate average speed using
                    // distance and time (and ignore actual speed values)
                    
                    let firstPoint = relevantPoints.first!
                    let lastPoint = relevantPoints.last!
                    
                    let totalDistanceInKilometers = (lastPoint.distance - firstPoint.distance) / 1000.0
                    let totalTimeInHours = lastPoint.timestamp.timeIntervalSince(firstPoint.timestamp) / 3600.0
                    
                    return totalTimeInHours > 0 ? (totalDistanceInKilometers / totalTimeInHours) : 0.0
                }
            }()
        
        let averageHeartrate = relevantPoints.contains(where: { $0.heartRate == ActivityRecorder.NOVALUE })
            ? ActivityRecorder.NOVALUE
            : Int(relevantPoints.map { Double($0.heartRate) }.reduce(0, +) / Double(relevantPoints.count))
        
        let averageCadence = relevantPoints.contains(where: { $0.cadence == ActivityRecorder.NOVALUE })
            ? ActivityRecorder.NOVALUE
            : Int(relevantPoints.map { Double($0.cadence) }.reduce(0, +) / Double(relevantPoints.count))
        
        let averagePower = relevantPoints.contains(where: { $0.power == ActivityRecorder.NOVALUE })
            ? ActivityRecorder.NOVALUE
            : Int(relevantPoints.map { Double($0.power) }.reduce(0, +) / Double(relevantPoints.count))
        
        let distance = relevantPoints.last?.distance ?? 0

       // let lastTimestamp = dataPoints.last?.timestamp.addingTimeInterval(1) ?? Date.distantPast

        return DataPoint(timestamp: timestamp,
                         start: relevantPoints.isEmpty ? timestamp : relevantPoints.first!.timestamp,
                         end: relevantPoints.isEmpty ? timestamp : relevantPoints.last!.timestamp,
                         speed: averageSpeed,
                         heartRate: averageHeartrate,
                         power: averagePower,
                         cadence: averageCadence,
                         distance: distance
                         )
    }
    
    func startRecording() {
        isRecording = true
        recordingStartDate = Date()
        resetData()
        setupWorkoutBuilder(workoutType: self.selectedWorkoutType)
        
        if let healthStore = healthStore {
            routeBuilder = HKWorkoutRouteBuilder(healthStore: healthStore, device: nil)
        }
       
        // startLiveActivity()
       
       
        
        print("ActivityRecorder: Recording started.")
    }
    
    func pauseRecording() {
        print("ActivityRecorder: pause recording.")
        isPaused = true
        
        // could do more stuff here.
    }
    
    func resumeRecording() {
        if !self.isSaving {
            print("ActivityRecorder: resume recording.")
            if self.isPaused {
                self.isPaused = false
                // hook for further magic here
            } // we're not paused, we're not doing anyting.
        }
    }
    
    func resetData(){
        print("ActivityRecorder: data reset.")
        dataPoints.removeAll()
        intervals.removeAll()
        
        dataPointsSelected.removeAll()
        for scope in DataPointScope.allCases {
            dataPointsFiltered[scope]?.removeAll()
        }
        
        recordingStartDate = Date()
        currentIntervalStartDate = recordingStartDate
        
        currentSpeed = 0
        elapsedTime = 0
        
        currentPower = bluetoothManager.isPowerMeterConnected ? 0 : ActivityRecorder.NOVALUE
        currentCadence = bluetoothManager.isCadenceSensorConnected ? 0 : ActivityRecorder.NOVALUE
        currentHeartRate = bluetoothManager.isHeartRateMonitorConnected ? 0 : ActivityRecorder.NOVALUE
        totalDistance = 0
        nextIntervalDistance = self.selectedWorkoutType == .cycling
            ? self.intervalDistanceCycling
            : self.intervalDistanceRunning
        
        lastSpokenDataPointCount = 0
    }

    private func startLiveActivity(){
        print("ActivityRecorder: startLiveActivity.")
        //UIApplication.shared.isIdleTimerDisabled = true
        
        if self.liveActivity == nil {
            
            if ActivityAuthorizationInfo().areActivitiesEnabled {
                print("ActivityRecorder: areActivitiesEnabled.")
                
                do {
                    let attributes = Gran_Fondo_StatusAttributes(activityImageName: getActivityTypeImageName(for: selectedWorkoutType), activityDescription: getActivityTypeDescription(for: selectedWorkoutType))
                    let initialState = getLiveActivityContentState()
                    
                    let activity = try Activity.request(
                        attributes: attributes,
                        content: .init(state: initialState, staleDate: nil)
                    )
                    //print("ActivityRecorder: areActivitiesEnabled.")
                    self.setupLiveActivity(withActivity: activity)
                } catch {
                    let errorMessage = """
                            Couldn't start activity
                            ------------------------
                            \(String(describing: error))
                            """
                    print("ActivityRecorder: error starting activity \(errorMessage).")
                    //self.errorMessage = errorMessage
                }
            }else{
                print("ActivityRecorder: live activities not enabled.")
            }
        }
    }
    
    private func setupLiveActivity(withActivity: Activity<Gran_Fondo_StatusAttributes>){
        self.liveActivity = withActivity;
    }
    
    private func getLiveActivityContentState() -> Gran_Fondo_StatusAttributes.ContentState{
        return Gran_Fondo_StatusAttributes.ContentState(
            isPaused: self.isPaused,
            currentHeartRate: self.currentHeartRate,
            currentPower: self.currentPower,
            currentCadence: self.currentCadence,
            currentSpeed: self.currentSpeed,
            currentSpeedText: self.getSpeedString(self.currentSpeed, workoutType: self.selectedWorkoutType), 
            isHeartRateMonitorConnected: self.bluetoothManager.isHeartRateMonitorConnected,
            isPowerMeterConnected: self.bluetoothManager.isPowerMeterConnected,
            isCadenceSensorConnected: self.bluetoothManager.isCadenceSensorConnected,
            isSpeedSensorConnected: self.bluetoothManager.isSpeedSensorConnected,
            totalDistance: self.totalDistance,
            totalDistanceText: self.getDistanceString(self.totalDistance),
            elapsedTime: self.elapsedTime,
            elapsedTimeText: self.getFormattedElapsedTime(for: self.elapsedTime)
        )
    }
    
    private func updateLiveActivity() async{
       // print("ActivityRecorder: updateLiveActivity.")
        let contentState = getLiveActivityContentState()
        
        await self.liveActivity?.update(
            ActivityContent<Gran_Fondo_StatusAttributes.ContentState>(
                    state: contentState,
                    staleDate: nil
                )
        )
    }
    private func endLiveActivity() async {
        guard let activity = self.liveActivity else {
            return
        }
        
        let finalContent = getLiveActivityContentState()
        let dismissalPolicy = ActivityUIDismissalPolicy.immediate
        await activity.end(ActivityContent(state: finalContent, staleDate: nil), dismissalPolicy: dismissalPolicy)
        
        self.liveActivity = nil
    }
    
    private func setupWorkoutBuilder(workoutType: HKWorkoutActivityType) {
        guard let healthStore = healthStore else { return }
        let configuration = HKWorkoutConfiguration()
        configuration.activityType = workoutType
        configuration.locationType = .outdoor
        
        workoutBuilder = HKWorkoutBuilder(healthStore: healthStore, configuration: configuration, device: nil)
        workoutBuilder?.beginCollection(withStart: Date()) { success, error in
            if !success {
                print("ActivityRecorder: Failed to start workout collection: \(error?.localizedDescription ?? "unknown error")")
            }
        }
    }
    
    func stopRecording(){ // completion: @escaping (Bool) -> Void) {
        DispatchQueue.main.async {
            self.saveCurrentInterval() 
            
            self.isSaving = true
            self.isPaused = false
        }
      
        Task{
            await self.endLiveActivity()
        }
        
        // Simulate a delay of 1 second
        DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 1) {
            
            DispatchQueue.global(qos: .background).async {
                self.endWorkout(startDate: self.recordingStartDate, endDate: Date()) { success in
                    DispatchQueue.main.async {
                        self.isRecording = false
                        self.isSaving = false;
                        // completion(success)
                    }
                }
            }
        }
        
        print("ActivityRecorder: Recording stopped.")
    }
    
    private func endWorkout(startDate: Date, endDate: Date, completion: @escaping (Bool) -> Void) {
        guard let healthStore = healthStore else { return }
        
        // Define our types and units:
        let typeSpeed = selectedWorkoutType == .cycling
            ? HKQuantityType.quantityType(forIdentifier: .cyclingSpeed)
            : HKQuantityType.quantityType(forIdentifier: .runningSpeed)
        
        let unitSpeed: HKUnit = (unitsPreference == UnitsPreference.Metric)
            ? HKUnit.meterUnit(with: .kilo).unitDivided(by: HKUnit.hour())  // km / h
            : HKUnit.mile().unitDivided(by: HKUnit.hour())                  // m p h
        
        HKUnit.meterUnit(with: .kilo).unitDivided(by: HKUnit.hour())
        
        let typeDistance = selectedWorkoutType == .cycling
            ? HKQuantityType.quantityType(forIdentifier: .distanceCycling)
            : HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)
        let unitDistance: HKUnit = (unitsPreference == UnitsPreference.Metric) ? HKUnit.meterUnit(with: .kilo) : HKUnit.mile()
        
        let typeHeartRate = HKQuantityType.quantityType(forIdentifier: .heartRate)!
        let unitHeartRate = HKUnit(from: "count/min")
        
        let typeCyclingCadence = HKQuantityType.quantityType(forIdentifier: .cyclingCadence)
        let unitCyclingCadence = HKUnit(from: "count/min")
        
        let typeCyclingPower = HKQuantityType.quantityType(forIdentifier: .cyclingPower)
        let unitCyclingPower = HKUnit.watt()
        
        let typeActiveEnergy = HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)
        let unitActiveEnergy = HKUnit.largeCalorie()
        
        var quantitySamples: [HKQuantitySample] = []

        var workoutEvents: [HKWorkoutEvent] = []
        
        var heartRateTotal = 0
        var heartRateSampleCount = 0
        
        var previousStart: Date = startDate
        var previousDistance: Double = 0
        
        // individual data points
        
        for data in self.dataPoints {
            
            // Speed: we always have speed:
            if let typeSpeed = typeSpeed {
                quantitySamples.append(
                    HKQuantitySample(
                        type: typeSpeed,
                        quantity:  HKQuantity(
                            unit: unitSpeed,
                            doubleValue: (unitsPreference == UnitsPreference.Metric) ? data.speed : data.speed / 1.609344
                        ),
                        start: data.start,
                        end: data.end
                    )
                )
            }
            
            // Distance: given an interval, how much did we move?
            let elapsedDistance = data.distance - previousDistance
            
            if let typeDistance = typeDistance {
                 quantitySamples.append(
                     HKQuantitySample(
                         type: typeDistance,
                         quantity:  HKQuantity(
                             unit: unitDistance,
                             doubleValue: (unitsPreference == UnitsPreference.Metric) ? elapsedDistance / 1000 : elapsedDistance / 1609.344
                         ),
                         start: previousStart,
                         end: data.end
                     )
                 )
             }
            previousStart = data.end
            previousDistance = data.distance
            
            // Heart rate, if available:
            if(data.heartRate != ActivityRecorder.NOVALUE){
                quantitySamples.append(
                    HKQuantitySample(
                        type: typeHeartRate,
                        quantity:  HKQuantity(
                            unit: unitHeartRate,
                            doubleValue: Double(data.heartRate)
                        ),
                        start: data.start,
                        end: data.end
                    )
                )
                
                heartRateTotal += data.heartRate
                heartRateSampleCount += 1
            }
            
            // only write cadence and power if we're cycling.
            if(selectedWorkoutType == .cycling){
                // Cycling cadence
                if let typeCyclingCadence = typeCyclingCadence, (data.cadence != ActivityRecorder.NOVALUE){
                    quantitySamples.append(
                        HKQuantitySample(
                            type: typeCyclingCadence,
                            quantity:  HKQuantity(
                                unit: unitCyclingCadence,
                                doubleValue: Double(data.cadence)
                            ),
                            start: data.start,
                            end: data.end
                        )
                    )
                }
                
                // Cycling power
                if let typeCyclingPower = typeCyclingPower, (data.power != ActivityRecorder.NOVALUE){
                    quantitySamples.append(
                        HKQuantitySample(
                            type: typeCyclingPower,
                            quantity:  HKQuantity(
                                unit: unitCyclingPower,
                                doubleValue: Double(data.power)
                            ),
                            start: data.start,
                            end: data.end
                        )
                    )
                }
            }
        }
        
        // intervals
        for interval in intervals {
            // Create a workout event for the interval, using the timestamp
            let intervalEvent = HKWorkoutEvent(
                type: .lap,
                dateInterval: DateInterval(start: interval.start, end: interval.end),
                metadata: nil//metadata
            )
            workoutEvents.append(intervalEvent)
            
        }
        
        // some totals:
        
        // total cycling // runing distance:
       /* if let typeDistance = typeDistance {
            quantitySamples.append(
                HKQuantitySample(
                    type: typeDistance,
                    quantity:  HKQuantity(
                        unit: unitDistance,
                        doubleValue: (unitsPreference == UnitsPreference.Metric) ? totalDistance / 1000 : totalDistance / 1609.344
                    ),
                    start: startDate,
                    end: endDate
                )
            )
        }*/
        
        // Calculate max heart rate
        let maxHeartRate = self.activeEnergyInputHeartRateMax //220 - Double(age)
        // Calculate heart rate reserve
        let heartRateReserve = maxHeartRate - self.activeEnergyInputHeartRateRest
        
        let heartRateAverage = (heartRateSampleCount > 1) ? Double( heartRateTotal / heartRateSampleCount ) : (Double(self.activeEnergyInputHeartRateMax) * 0.7)
        
        // Calculate intensity as a percentage of HRR
        let intensityPercentage = (heartRateAverage - Double(self.activeEnergyInputHeartRateRest)) / Double(heartRateReserve)
        
        // Estimate MET based on intensity
        let met = estimateMET(fromIntensity: intensityPercentage)
        
        // Calculate calories burned using the formula
        let durationHours =  endDate.timeIntervalSince(startDate) / 3600 //durationMinutes / 60
        let caloriesBurned = met * self.activeEnergyInputWeight * durationHours
        //return caloriesBurned
        
        if let typeActiveEnergy = typeActiveEnergy {
            quantitySamples.append(
                HKQuantitySample(
                    type: typeActiveEnergy,
                    quantity:  HKQuantity(
                        unit: unitActiveEnergy,
                        doubleValue: caloriesBurned
                    ),
                    start: startDate,
                    end: endDate
                )
            )
        }
        
        print("ActivityRecorder: maxHeartRate: \(maxHeartRate)")
        print("ActivityRecorder: self.activeEnergyInputHeartRateRest: \(self.activeEnergyInputHeartRateRest)")
        print("ActivityRecorder: heartRateReserve: \(heartRateReserve)")
        print("ActivityRecorder: heartRateAverage: \(heartRateAverage)")
        print("ActivityRecorder: intensityPercentage: \(intensityPercentage)")
        print("ActivityRecorder: MET: \(met)")
        print("ActivityRecorder: durationHours: \(durationHours)")
        print("ActivityRecorder: End workout: \(caloriesBurned) calories")
        
        // save some for the record book:
        DispatchQueue.main.async {
            self.totalDistanceLifetime = self.totalDistanceLifetime + self.totalDistance
            UserDefaults.standard.set(self.totalDistanceLifetime, forKey: "totalDistanceLifetime")
        }
        // return; // DEBUG disable polluting my HealthKit data
        
        
        healthStore.save(quantitySamples) { [weak self] success, error in
            guard let self = self else { return }
            if success {
                self.workoutBuilder?.add(quantitySamples, completion: { success, error in
                    guard success else {
                        print("ActivityRecorder: Failed to add quantity samples to workout: \(error?.localizedDescription ?? "unknown error")")
                        completion(false)
                        return
                    }
                    
                    self.workoutBuilder?.addWorkoutEvents(workoutEvents, completion: { success, error in
                        guard success else {
                            print("ActivityRecorder: Failed to add workout events to workout: \(error?.localizedDescription ?? "unknown error")")
                            completion(false)
                            return
                        }
                        
                        // End the workout
                        self.workoutBuilder?.endCollection(withEnd: endDate) { success, error in
                            guard success else {
                                print("ActivityRecorder: Error ending the workout collection: \(error?.localizedDescription ?? "unknown error")")
                                completion(false)
                                return
                            }
                            self.workoutBuilder?.finishWorkout { workout, error in
                                if let workout = workout {
                                    print("ActivityRecorder: Workout saved: \(workout)")
                                    self.finishRoute(with: workout)
                                    
                                    completion(true)
                                } else {
                                    print("ActivityRecorder: Error finishing workout: \(error?.localizedDescription ?? "unknown error")")
                                }
                            }
                        }
                    })
                })
            } else {
                completion(false)
                print("ActivityRecorder: Error saving heart rate data: \(error?.localizedDescription ?? "unknown error")")
            }
        }
    }
    
    private func finishRoute(with workout: HKWorkout) {
        self.routeBuilder?.finishRoute(with: workout, metadata: nil) { route, error in
            if let route = route {
                print("ActivityRecorder: Route saved: \(route)")
            } else {
                print("ActivityRecorder: Failed to save route: \(String(describing: error))")
            }
        }
    }
    

    private func estimateMET(fromIntensity intensity: Double) -> Double {
        // Intensity ranges and corresponding MET values with linear interpolation
        switch intensity {
        case 0..<0.20:
            return 2.0 + (intensity / 0.20) * 1.0  // Very light activity (2.0 - 3.0 METs)
        case 0.20..<0.40:
            return 3.0 + ((intensity - 0.20) / 0.20) * 1.0  // Light activity (3.0 - 4.0 METs)
        case 0.40..<0.60:
            return 4.0 + ((intensity - 0.40) / 0.20) * 2.0  // Moderate activity (4.0 - 6.0 METs)
        case 0.60..<0.80:
            return 6.0 + ((intensity - 0.60) / 0.20) * 2.0  // Vigorous activity (6.0 - 8.0 METs)
        case 0.80..<1.00:
            return 8.0 + ((intensity - 0.80) / 0.20) * 4.0  // High intensity (8.0 - 12.0 METs)
        default:
            return 12.0 // Maximum MET value for extreme intensity
        }
        
    }
    
    private func saveSelectedWorkoutType() {
        UserDefaults.standard.set(selectedWorkoutType.rawValue, forKey: UserDefaultsKeys.selectedWorkoutType)
    }
    
    func timeRangeForPreferredScope() -> ClosedRange<Date> {
        let latest = dataPointsSelected.last?.timestamp ?? Date.now//dataPointsFiltered[preferredScope]?.last?.timestamp ?? Date.now
        //let startDate = dataPointsFiltered[selectedScope]?.first?.timestamp ?? recordingStartDate
        var startDate: Date

        switch preferredScope {
        case .lastMinute:
            startDate = Calendar.current.date(byAdding: .minute, value: -1, to: latest)!
        case .last5Minutes:
            startDate = Calendar.current.date(byAdding: .minute, value: -5, to: latest)!
        case .last15Minutes:
            startDate = Calendar.current.date(byAdding: .minute, value: -15, to: latest)!
        case .lastHour:
            startDate = Calendar.current.date(byAdding: .hour, value: -1, to: latest)!
        case .sinceStart:
            // Assuming 'sinceStart' could mean the start of the current day for simplicity
            startDate = recordingStartDate
        }

        return startDate...latest // Closed range from calculated start to current time
    }
    
    public func getDistanceString(_ distanceInMeters: Double) -> String{
        if unitsPreference == UnitsPreference.Metric {
            return String(format: "%.1f", (distanceInMeters / 1000))+" KM"
        }else{
            return String(format: "%.1f", (distanceInMeters / 1609.344))+" mi"
        }
    }
    
    public func getSpeedString(_ speedInKilometersPerHour: Double, workoutType: HKWorkoutActivityType) -> String{
        if workoutType == .running {
            return paceFromSpeedKmPerHour(speedInKilometersPerHour)
        } else{
            if unitsPreference == UnitsPreference.Metric {
                return String(format: "%.1f", speedInKilometersPerHour )+" KM/h"
            }else{
                return String(format: "%.1f", (speedInKilometersPerHour / 1.609344))+" MPH"
            }
        }
    }
    
    public func getActivityTypeImageName(for type: HKWorkoutActivityType) -> String{
        switch type {
        case .running:
            return "figure.run"
        case .cycling:
            return "bicycle"
        default:
            return "figure.mixed.cardio"
        }
    }
    
    // Depending on the elapsedTime, adjust the allowedUnits and zero formatting behavior
    public func getFormattedElapsedTime(for duration: TimeInterval) -> String {
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .positional

        if duration < 5400 { // Less than 90 minutes
            formatter.allowedUnits = [.minute, .second]
            formatter.zeroFormattingBehavior = [.pad] // Zero-pad seconds always
            return formatter.string(from: duration) ?? "0:00"
        } else {
            formatter.allowedUnits = [.hour, .minute, .second]
            formatter.zeroFormattingBehavior = [.pad] // Zero-pad minutes and seconds
            return formatter.string(from: duration) ?? "0:00:00"
        }
    }
    
    public func paceFromSpeedKmPerHour(_ speedKmPerHour: Double) -> String {
        if speedKmPerHour <= 1 {
                return "∞" // or some appropriate representation for infinite pace
        }

        let speedMPerSec = speedKmPerHour * (5.0 / 18.0) //0.27778
        let secondsPerKm = 1 / speedMPerSec * 1000
        let minutes = Int(secondsPerKm / 60)
        let seconds = Int(secondsPerKm.truncatingRemainder(dividingBy: 60))
        return String(format: "%d'%02d\"", minutes, seconds)
    }
    
    public func getActivityTypeDescription(for type: HKWorkoutActivityType) -> String {
        switch type {
        case .running:
            return "Running"
        case .cycling:
            return "Cycling"
        case .other:
            return "Other Activities"
        default:
            return "Unknown Activity"
        }
    }
    
    public func getFormattedDateString(_ date: Date) -> String{
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "EEEE dd MMM yyyy '@' HH:mm"
        return dateFormatter.string(from: date)
    }
    
    
    public func fetchAndSaveWorkouts() async {
        if let workouts = await self.readWorkouts() {
            self.workouts = workouts
            print("ActivityRecorder: got \(self.workouts.count) workouts.")
            
            // Display the workouts in a SwiftUI view
        } else {
            // Handle the case where no workouts were returned
        }
    }
    
    public func readWorkouts() async -> [HKWorkout]? {
        // Create a date for 4th August 2024, starting from midnight.
            let calendar = Calendar.current
            let august4thStartDate = calendar.date(from: DateComponents(year: 2024, month: 1, day: 9, hour: 0, minute: 0, second: 0))!
            let august4thEndDate = calendar.date(from: DateComponents(year: 2024, month: 1, day: 9, hour: 23, minute: 59, second: 59))!

            // Create a predicate for workouts on 4th August 2024.
            let datePredicate = HKQuery.predicateForSamples(withStart: august4thStartDate, end: august4thEndDate, options: .strictEndDate)

        
        let cycling = HKQuery.predicateForWorkouts(with: .cycling)
        let running = HKQuery.predicateForWorkouts(with: .running)
        let distance = HKQuery.predicateForWorkouts(with: .greaterThan, totalDistance: HKQuantity(unit: HKUnit.meter(), doubleValue: 100))
        let combinedPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [datePredicate, distance,  NSCompoundPredicate(orPredicateWithSubpredicates: [cycling, running])])

        let samples = try! await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[HKSample], Error>) in
            self.healthStore?.execute(HKSampleQuery(sampleType: .workoutType(), predicate: combinedPredicate, limit: 100 ,sortDescriptors: [.init(keyPath: \HKSample.startDate, ascending: false)], resultsHandler: { query, samples, error in
                if let hasError = error {
                    continuation.resume(throwing: hasError)
                    return
                }

                guard let samples = samples else {
                    fatalError("*** Invalid State: This can only fail if there was an error. ***")
                }

                continuation.resume(returning: samples)
            }))
        }

        guard let workouts = samples as? [HKWorkout] else {
            return nil
        }
        
        // Process each workout
            for workout in workouts {
                print("Workout: \(workout.workoutActivityType), Start: \(workout.startDate), End: \(workout.endDate), Total Distance: \(workout.totalDistance?.doubleValue(for: HKUnit.meter()) ?? 0) meters")
                
                // Retrieve and print each workout event
                for event in workout.workoutEvents ?? [] {
                    print("  Event Type: \(event.type)")
                    print("  Event Interval: \(event.dateInterval.start) to \(event.dateInterval.end)")
                    
                    // Print metadata if available
                    if let metadata = event.metadata {
                        for (key, value) in metadata {
                            print("    Metadata - \(key): \(value)")
                        }
                    }
                }
            }

        
        return workouts
    }
    
    func fetchActiveEnergyInputParameters() async{
        guard let healthStore = healthStore else { return }
        
        // biological sex
        do {
            // Attempt to fetch biological sex, handling any errors thrown
            let biologicalSex = try await healthStore.biologicalSex()
            self.activeEnergyInputSex = biologicalSex.biologicalSex
            print("Biological Sex: \(self.activeEnergyInputSex)")
        } catch {
            // Handle the error appropriately (for example, print an error message)
            print("Error fetching biological sex: \(error.localizedDescription)")
        }
    
        
        // height
        await fetchHeight()
        
        // weight
        await fetchWeight()
        
        // rest heart rate
        await fetchRestingHeartRate()
        
        // max heart rate
        await fetchMaxHeartRate()
        
    }
        
    // Fetch Height (cm)
    private func fetchHeight() async {
        guard let heightType = HKQuantityType.quantityType(forIdentifier: .height) else {
            print("ActivityRecorder: Height type is not available in HealthKit.")
            return
        }
        
        do {
            // Query for height data
            let heightSamples = try await getMostRecentSample(for: heightType)
            
            if let heightSample = heightSamples?.first {
                let heightInMeters = heightSample.quantity.doubleValue(for: HKUnit.meter())
                // Convert height from meters to centimeters
                self.activeEnergyInputHeight = heightInMeters * 100
                print("ActivityRecorder: Height from HealthKit: \(self.activeEnergyInputHeight) cm")
            }
        } catch {
            print("ActivityRecorder: Error fetching height: \(error)")
        }
    }
    
    // Fetch Weight (kg)
    private func fetchWeight() async {
        guard let weightType = HKQuantityType.quantityType(forIdentifier: .bodyMass) else {
            print("ActivityRecorder: Weight type is not available in HealthKit.")
            return
        }
        
        do {
            // Query for weight data
            let weightSamples = try await getMostRecentSample(for: weightType)
            
            if let weightSample = weightSamples?.first {
                self.activeEnergyInputWeight = weightSample.quantity.doubleValue(for: HKUnit.gram()) / 1000
                print("ActivityRecorder: Weight from HealthKit: \(self.activeEnergyInputWeight) kg")
            }
        } catch {
            print("ActivityRecorder: Error fetching weight: \(error)")
        }
    }
    
    private func getMostRecentSample(for type: HKQuantityType) async throws -> [HKQuantitySample]? {
        let predicate = HKQuery.predicateForSamples(withStart: nil, end: Date(), options: .strictEndDate)
        
        // Use checked throwing continuation to wrap the asynchronous query
        return try await withCheckedThrowingContinuation { continuation in
            // Set up the query
            let query = HKSampleQuery(sampleType: type, predicate: predicate, limit: 1, sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)]) { _, samples, error in
                
                if let error = error {
                    // If there was an error, resume the continuation with the error
                    continuation.resume(throwing: error)
                    return
                }
                
                // If there was no error, resume the continuation with the result (samples)
                continuation.resume(returning: samples as? [HKQuantitySample])
            }
            
            // Execute the query
            healthStore?.execute(query)
        }
    }

    // Fetch Resting Heart Rate (bpm)
        private func fetchRestingHeartRate() async {
            guard let heartRateType = HKQuantityType.quantityType(forIdentifier: .restingHeartRate) else {
                print("ActivityRecorder: Resting heart rate type is not available in HealthKit.")
                return
            }
            
            do {
                // Query for resting heart rate data
                let heartRateSamples = try await getMostRecentSample(for: heartRateType)
                
                if let heartRateSample = heartRateSamples?.first {
                    self.activeEnergyInputHeartRateRest = Int(heartRateSample.quantity.doubleValue(for: HKUnit.count().unitDivided(by: HKUnit.minute())))
                    print("ActivityRecorder: Resting Heart Rate from HealthKit: \(self.activeEnergyInputHeartRateRest) bpm")
                }
            } catch {
                print("ActivityRecorder: Error fetching resting heart rate: \(error)")
            }
        }
    
    // Fetch Maximum Heart Rate (bpm)
        private func fetchMaxHeartRate() async {
            guard let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate) else {
                print("ActivityRecorder: Heart rate type is not available in HealthKit.")
                return
            }
            
            do {
                // Query for heart rate data (max value)
                let maxHeartRateSample = try await getMaxSample(for: heartRateType)
                
                if let maxHeartRate = maxHeartRateSample {
                    self.activeEnergyInputHeartRateMax = Int(maxHeartRate)
                    print("ActivityRecorder: Max Heart Rate: \(self.activeEnergyInputHeartRateMax) bpm")
                }
            } catch {
                print("ActivityRecorder: Error fetching max heart rate: \(error)")
            }
        }
    
    // Helper method to fetch the maximum value of a heart rate sample
    private func getMaxSample(for type: HKQuantityType) async throws -> Double? {
        //let predicate = HKQuery.predicateForSamples(withStart: nil, end: Date(), options: .strictEndDate)
        
        // Calculate the start date 12 months ago
            let startDate = Calendar.current.date(byAdding: .month, value: -12, to: Date())
            
            // Ensure startDate is not nil
            guard let startDate = startDate else {
                throw NSError(domain: "HealthKitError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to calculate start date."])
            }
            
            let predicate = HKQuery.predicateForSamples(withStart: startDate, end: Date(), options: .strictEndDate)
            
        
        return try await withCheckedThrowingContinuation { continuation in
            let query = HKStatisticsQuery(quantityType: type, quantitySamplePredicate: predicate, options: .discreteMax) { _, result, error in
                if let error = error {
                    print("ActivityRecorder: Error retrieving max sample: \(error.localizedDescription)")
                    continuation.resume(throwing: error) // Make sure to handle error properly
                    return
                }
                
                // Safely unwrap the result and return the value
                if let maxQuantity = result?.maximumQuantity() {
                    continuation.resume(returning: maxQuantity.doubleValue(for: HKUnit.count().unitDivided(by: HKUnit.minute())))
                } else {
                    continuation.resume(returning: nil) // If no value is found, return nil
                }
            }
            
            // Execute the query on the health store
            healthStore?.execute(query)
        }
    }


}


struct UserDefaultsKeys {
    static let selectedWorkoutType = "selectedWorkoutType"
}

// Define a simple data model
struct DataPoint: Identifiable {
    let id = UUID()
    let timestamp: Date
    let start: Date
    let end: Date
    let speed: Double
    let heartRate: Int
    let power: Int
    let cadence: Int
    let distance: Double
}


enum UnitsPreference : String {
    case Metric, Imperial
}

enum DataPointScope: String, CaseIterable {
    case sinceStart
    case lastMinute
    case last5Minutes
    case last15Minutes
    case lastHour
    
    var description: String {
        switch self {
            case .sinceStart: return "Since start"
            case .lastMinute: return "Last minute"
            case .last5Minutes: return "Last 5 minutes"
            case .last15Minutes: return "Last 15 minutes"
            case .lastHour: return "Last hour"
        }
    }
    
    var duration: Double {
        switch self {
            case .sinceStart: return -1
            case .lastMinute: return 60
            case .last5Minutes: return 300
            case .last15Minutes: return 900
            case .lastHour: return 3600
        }
    }
}

    
