import Foundation
import CoreLocation
import UIKit
import Capacitor
import TSLocationManager

// Event name constants are provided by TSEventName* from TSLocationManager SDK.

@objc(BackgroundGeolocationModule)
public class BackgroundGeolocationModule: CAPPlugin, CAPBridgedPlugin {
    public let identifier = "BackgroundGeolocationPlugin"
    public let jsName = "BackgroundGeolocation"
    
    public let pluginMethods: [CAPPluginMethod] = [
        CAPPluginMethod(name: "registerPlugin", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "ready", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "reset", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "setConfig", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "getState", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "start", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startGeofences", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startSchedule", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "stopSchedule", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "stop", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "changePace", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "getProviderState", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "requestPermission", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "requestTemporaryFullAccuracy", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "sync", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "getCurrentPosition", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "watchPosition", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "stopWatchPosition", returnType: CAPPluginReturnPromise),
        
        CAPPluginMethod(name: "addGeofence", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "addGeofences", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "getGeofences", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "getGeofence", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "geofenceExists", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "removeGeofence", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "removeGeofences", returnType: CAPPluginReturnPromise),
        
        CAPPluginMethod(name: "getOdometer", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "setOdometer", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "getLocations", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "getCount", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "insertLocation", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "destroyLocations", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "destroyLocation", returnType: CAPPluginReturnPromise),

        CAPPluginMethod(name: "startBackgroundTask", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "stopBackgroundTask", returnType: CAPPluginReturnPromise),
        
        CAPPluginMethod(name: "getLog", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "destroyLog", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "emailLog", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "uploadLog", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "log", returnType: CAPPluginReturnPromise),
        
        CAPPluginMethod(name: "getSensors", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "getDeviceInfo", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "isPowerSaveMode", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "isIgnoringBatteryOptimizations", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "requestSettings", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "showSettings", returnType: CAPPluginReturnPromise),
        
        CAPPluginMethod(name: "playSound", returnType: CAPPluginReturnPromise),
        
        CAPPluginMethod(name: "getTransistorToken", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "destroyTransistorToken", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "removeAllEventListeners", returnType: CAPPluginReturnPromise)
    ]

    var ready = false

    public override func load() {
        let locationManager = BackgroundGeolocation.sharedInstance()

        if let root = UIApplication.shared.delegate?.window??.rootViewController {
            locationManager.viewController = root
        }
        ready = false
        registerEventListeners()
    }

    func registerEventListeners() {

        let locationManager = BackgroundGeolocation.sharedInstance()
        weak var me = self
        
        locationManager.onLocation({ (event: TSLocationEvent?) in
            guard let me = me, me.hasListeners(TSEventNameLocation) else { return }
            if let locationDict = event?.toDictionary() as? [String: Any] {
                me.notifyListeners(TSEventNameLocation, data: locationDict)
            }
        }, failure: { error in
            guard let me = me, me.hasListeners(TSEventNameLocation) else { return }
            if let nsError = error as? NSError {
                me.notifyListeners(TSEventNameLocation, data: ["error": nsError.code] as [String: Any])
            }
        })

        locationManager.onMotionChange({ (event: TSLocationEvent?) in
            guard let me = me, me.hasListeners(TSEventNameMotionChange) else { return }
            let params: [String: Any] = [
                "isMoving": event?.isMoving as Any,
                "location": event?.toDictionary() as Any
            ]
            me.notifyListeners(TSEventNameMotionChange, data: params)
        })

        locationManager.onActivityChange({ (event: TSActivityChangeEvent?) in
            guard let me = me, me.hasListeners(TSEventNameActivityChange) else { return }
            let params: [String: Any] = [
                "activity": event?.activity as Any,
                "confidence": event?.confidence as Any
            ]
            me.notifyListeners(TSEventNameActivityChange, data: params)
        })

        locationManager.onLocationFilter({ (event: TSLocationFilterEvent?) in
            guard let me = me, me.hasListeners(TSEventNameLocationFilter) else { return }
            me.notifyListeners(TSEventNameLocationFilter, data: event?.toDictionary() as? [String: Any] ?? [:])
        })

        locationManager.onHeartbeat({ (event: TSHeartbeatEvent?) in
            guard let me = me, me.hasListeners(TSEventNameHeartbeat) else { return }
            if let eventData = event?.toDictionary() as? [String: Any] {
                me.notifyListeners(TSEventNameHeartbeat, data: eventData)
            }
        })

        locationManager.onGeofence({ (event: TSGeofenceEvent?) in
            guard let me = me, me.hasListeners(TSEventNameGeofence) else { return }
            if let eventData = event?.toDictionary() as? [String: Any] {
                me.notifyListeners(TSEventNameGeofence, data: eventData)
            }
        })

        locationManager.onGeofencesChange({ (event: TSGeofencesChangeEvent?) in
            guard let me = me, me.hasListeners(TSEventNameGeofencesChange) else { return }
            if let eventData = event?.toDictionary() as? [String: Any] {
                me.notifyListeners(TSEventNameGeofencesChange, data: eventData)
            }
        })

        locationManager.onHttp({ (event: TSHttpEvent?) in
            guard let me = me, me.hasListeners(TSEventNameHttp) else { return }
            if let eventData = event?.toDictionary() as? [String: Any] {
                me.notifyListeners(TSEventNameHttp, data: eventData)
            }
        })

        locationManager.onProviderChange({ (event: TSProviderChangeEvent?) in
            guard let me = me, me.hasListeners(TSEventNameProviderChange) else { return }
            if let eventData = event?.toDictionary() as? [String: Any] {
                me.notifyListeners(TSEventNameProviderChange, data: eventData)
            }
        })

        locationManager.onSchedule({ (event: TSScheduleEvent?) in
            guard let me = me, me.hasListeners(TSEventNameSchedule) else { return }
            if let state = event?.state as? [String: Any] {
                me.notifyListeners(TSEventNameSchedule, data: state)
            }
        })

        locationManager.onPowerSaveChange({ (event: TSPowerSaveChangeEvent?) in
            guard let me = me, me.hasListeners(TSEventNamePowerSaveChange) else { return }
            let params: [String: Any] = ["value": event?.isPowerSaveMode as Any]
            me.notifyListeners(TSEventNamePowerSaveChange, data: params)
        })

        locationManager.onConnectivityChange({ (event: TSConnectivityChangeEvent?) in
            guard let me = me, me.hasListeners(TSEventNameConnectivityChange) else { return }
            let params: [String: Any] = ["connected": event?.hasConnection as Any]
            me.notifyListeners(TSEventNameConnectivityChange, data: params)
        })

        locationManager.onEnabledChange({ (event: TSEnabledChangeEvent?) in
            guard let me = me, me.hasListeners(TSEventNameEnabledChange) else { return }
            me.notifyListeners(TSEventNameEnabledChange, data: ["value": event?.enabled as Any])
        })

        locationManager.onAuthorization({ (event: TSAuthorizationEvent?) in
            guard let me = me, me.hasListeners(TSEventNameAuthorization) else { return }
            if let eventData = event?.toDictionary() as? [String: Any] {
                me.notifyListeners(TSEventNameAuthorization, data: eventData)
            }
        })

    }

    @objc func registerPlugin(_ call: CAPPluginCall) {
        guard let pluginId = call.getString("id") else {
            call.reject("Missing required parameter: id")
            return
        }
        let config = TSConfig.sharedInstance()
        config.registerPlugin(pluginId)
        call.resolve()
    }

    @objc func ready(_ call: CAPPluginCall) {
        let locationManager = BackgroundGeolocation.sharedInstance()
        let config = TSConfig.sharedInstance()
        
        let params = call.getObject("options") ?? [:]
        let reset = (params["reset"] as? Bool) ?? true
        if ready {
            if reset {
                locationManager.log("warn", message: "#ready already called.  Redirecting to #setConfig")
                config.update(with: params)
            } else {
                locationManager.log("warn", message: "#ready already called.  Ignored Config since reset: false")
            }
            if let configDict = config.toDictionary() as? [String: Any] {
                call.resolve(configDict)
            } else {
                call.reject("Failed to convert config to dictionary", nil, nil, [:])
            }
            return
        }
        ready = true
        DispatchQueue.main.async {
            if config.isFirstBoot() {
                config.update(with: params)
            } else {
                if reset {
                    config.resetConfig(true)
                    config.update(with: params)
                } else if let auth = params["authorization"] as? [String: Any] {
                    config.batchUpdate { cfg in
                        cfg.authorization.update(with: auth)
                    }
                }
            }
            locationManager.ready()
            if let configDict = config.toDictionary() as? [String: Any] {
                call.resolve(configDict)
            } else {
                call.reject("Failed to convert config to dictionary", nil, nil, [:])
            }
        }
    }

    @objc func reset(_ call: CAPPluginCall) {
        let params = call.getObject("options") ?? [:]
        let config = TSConfig.sharedInstance()

        if !params.isEmpty {
            config.resetConfig(true)
            config.update(with: params)
        } else {
            config.resetConfig(false)
        }

        if let configDict = config.toDictionary() as? [String: Any] {
            call.resolve(configDict)
        } else {
            call.reject("Failed to convert config to dictionary", nil, nil, [:])
        }
    }

    @objc func setConfig(_ call: CAPPluginCall) {
        let params = call.getObject("options") ?? [:]
        let config = TSConfig.sharedInstance()
        config.update(with: params)
        if let configDict = config.toDictionary() as? [String: Any] {
            call.resolve(configDict)
        } else {
            call.reject("Failed to convert config to dictionary", nil, nil, [:])
        }
    }

    @objc func getState(_ call: CAPPluginCall) {
        let config = TSConfig.sharedInstance()
        if let configDict = config.toDictionary() as? [String: Any] {
            call.resolve(configDict)
        } else {
            call.reject("Failed to convert config to dictionary", nil, nil, [:])
        }
    }

    @objc func start(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            let locationManager = BackgroundGeolocation.sharedInstance()
            locationManager.start()
            if let state = locationManager.getState() as? [String: Any] {
                call.resolve(state)
            } else {
                call.reject("Failed to get location manager state", nil, nil, [:])
            }
        }
    }

    @objc func stop(_ call: CAPPluginCall) {
        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.stop()
        if let state = locationManager.getState() as? [String: Any] {
            call.resolve(state)
        } else {
            call.reject("Failed to get location manager state", nil, nil, [:])
        }
    }

    @objc func startSchedule(_ call: CAPPluginCall) {
        let config = TSConfig.sharedInstance()
        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.startSchedule()
        if let configDict = config.toDictionary() as? [String: Any] {
            call.resolve(configDict)
        } else {
            call.reject("Failed to convert config to dictionary", nil, nil, [:])
        }
    }

    @objc func stopSchedule(_ call: CAPPluginCall) {
        let config = TSConfig.sharedInstance()
        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.stopSchedule()
        if let configDict = config.toDictionary() as? [String: Any] {
            call.resolve(configDict)
        } else {
            call.reject("Failed to convert config to dictionary", nil, nil, [:])
        }
    }

    @objc func startGeofences(_ call: CAPPluginCall) {
        let config = TSConfig.sharedInstance()
        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.startGeofences()
        if let configDict = config.toDictionary() as? [String: Any] {
            call.resolve(configDict)
        } else {
            call.reject("Failed to convert config to dictionary", nil, nil, [:])
        }
    }

    @objc func changePace(_ call: CAPPluginCall) {
        let isMoving = call.getBool("isMoving") ?? false
        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.changePace(isMoving)
        call.resolve()
    }

    @objc func startBackgroundTask(_ call: CAPPluginCall) {
        let locationManager = BackgroundGeolocation.sharedInstance()
        let taskId = locationManager.createBackgroundTask()
        call.resolve(["taskId": taskId.rawValue])
    }

    @objc func stopBackgroundTask(_ call: CAPPluginCall) {
        guard let taskIdNumber = call.options["taskId"] as? NSNumber else {
            call.reject("Missing required parameter: taskId")
            return
        }
        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.stopBackgroundTask(UIBackgroundTaskIdentifier(rawValue: taskIdNumber.intValue))
        call.resolve()
    }

    @objc func getCurrentPosition(_ call: CAPPluginCall) {
        let options = call.getObject("options") ?? [:]
        let locationManager = BackgroundGeolocation.sharedInstance()

        let request = TSCurrentPositionRequest.make(
            type: .current,
            success: { event in
                if let eventData = event.data as? [String: Any] {
                    call.resolve(eventData)
                } else if let dict = event.toDictionary() as? [String: Any] {
                    call.resolve(dict)
                } else {
                    call.reject("Failed to convert location to dictionary", nil, nil, [:])
                }
            },
            failure: { error in
                call.reject("get_current_position_error", error.localizedDescription, error, [:])
            }
        )

        if let timeout = options["timeout"] as? Double {
            request.timeout = timeout
        }
        if let maximumAge = options["maximumAge"] as? Int {
            request.maximumAge = maximumAge
        }
        if let persist = options["persist"] as? Bool {
            request.persist = persist
        }
        if let samples = options["samples"] as? Int {
            request.samples = samples
        }
        if let desiredAccuracy = options["desiredAccuracy"] as? Double {
            request.desiredAccuracy = desiredAccuracy
        }
        if let extras = options["extras"] as? [String: Any] {
            request.extras = extras
        }

        locationManager.getCurrentPosition(request)
    }
    
    @objc func watchPosition(_ call: CAPPluginCall) {
        let options = call.getObject("options") ?? [:]
        weak var me = self
        let locationManager = BackgroundGeolocation.sharedInstance()

        let request = TSWatchPositionRequest.make(
            interval: 1000,
            success: { event in
                guard let me = me else { return }
                
                if let eventData = event.locationEvent.toDictionary() as? [String: Any] {
                    me.notifyListeners(TSEventNameWatchPosition, data: eventData)
                } else if let dict = event.toDictionary() as? [String: Any] {
                    me.notifyListeners(TSEventNameWatchPosition, data: dict)
                }
            },
            failure: { error in
                guard let me = me else { return }

                if let nsError = error as? NSError {
                    let result: [String: Any] = [
                        "error": [
                            "code": nsError.code,
                            "message": nsError.localizedDescription
                        ]
                    ]
                    me.notifyListeners(TSEventNameWatchPosition, data: result)
                } else {
                    let result: [String: Any] = [
                        "error": [
                            "code": -1,
                            "message": error.localizedDescription
                        ]
                    ]
                    me.notifyListeners(TSEventNameWatchPosition, data: result)
                }
            }
        )

        if let interval = options["interval"] as? Double {
            request.interval = interval
        }

        if let desiredAccuracy = options["desiredAccuracy"] as? Double {
            request.desiredAccuracy = desiredAccuracy
        }

        if let persist = options["persist"] as? Bool {
            request.persist = persist
        }

        if let extras = options["extras"] as? [String: Any] {
            request.extras = extras
        }

        if let timeoutNumber = options["timeout"] as? NSNumber {
            request.timeout = timeoutNumber.doubleValue
        }

        let watchId = Int(locationManager.watchPosition(request))
        call.resolve(["watchId": NSNumber(value: watchId)])
    }


    @objc func stopWatchPosition(_ call: CAPPluginCall) {
        guard let watchIdNumber = call.options["watchId"] as? NSNumber else {
            call.reject("Missing required parameter: watchId")
            return
        }
        let watchId = watchIdNumber.intValue
        BackgroundGeolocation.sharedInstance().stopWatchPosition(watchId)
        call.resolve()
    }

    // Locations Database

    @objc func getLocations(_ call: CAPPluginCall) {
        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.getLocations({ records in
            call.resolve(["locations": records as Any])
        }, failure: { error in
            call.reject(error, nil, nil, [:])
        })
    }

    @objc func sync(_ call: CAPPluginCall) {
        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.sync({ records in
            call.resolve(["locations": records as Any])
        }, failure: { error in
            if let nsError = error as? NSError {
                call.reject("\(nsError.code)", nil, error, nsError.userInfo)
            } else {
                call.reject("Unknown error occurred", nil, error, [:])
            }
        })
    }

    @objc func getGeofences(_ call: CAPPluginCall) {
        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.getGeofences({ geofences in
            let geofenceArray = geofences ?? []
            let result = geofenceArray.compactMap { ($0 as? TSGeofence)?.toDictionary() as? [String: Any] }
            call.resolve(["geofences": result])
        }, failure: { error in
            call.reject(error, nil, nil, [:])
        })
    }

    @objc func getGeofence(_ call: CAPPluginCall) {
        guard let identifier = call.getString("identifier") else {
            call.reject("Missing required parameter: identifier")
            return
        }

        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.getGeofence(identifier, success: { geofence in
            if let geofenceData = geofence.toDictionary() as? [String: Any] {
                call.resolve(geofenceData)
            } else {
                call.reject("Failed to convert geofence to dictionary", nil, nil, [:])
            }
        }, failure: { error in
            call.reject(error, nil, nil, [:])
        })
    }
    
    @objc func geofenceExists(_ call: CAPPluginCall) {
        guard let identifier = call.getString("identifier") else {
            call.reject("Missing required parameter: identifier")
            return
        }

        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.geofenceExists(identifier, callback: { exists in
            call.resolve(["exists": exists])
        })
    }

    @objc func addGeofence(_ call: CAPPluginCall) {
        let params = call.getObject("options") ?? [:]
        guard let geofence = buildGeofence(params) else {
            let error = "Invalid geofence data: \(params)"
            call.reject(error, nil, nil, [:])
            return
        }

        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.add(geofence, success: {
            call.resolve()
        }, failure: { error in
            call.reject(error, nil, nil, [:])
        })
    }

    @objc func addGeofences(_ call: CAPPluginCall) {
        guard let data = call.options["options"] as? [[String: Any]] else {
            call.reject("Invalid geofences data", nil, nil, [:])
            return
        }

        var geofences: [TSGeofence] = []
        for params in data {
            if let geofence = buildGeofence(params) {
                geofences.append(geofence)
            } else {
                let error = "Invalid geofence data: \(params)"
                call.reject(error, nil, nil, [:])
                return
            }
        }

        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.addGeofences(geofences, success: {
            call.resolve()
        }, failure: { error in
            call.reject(error, nil, nil, [:])
        })
    }

    @objc func removeGeofence(_ call: CAPPluginCall) {
        guard let identifier = call.getString("identifier") else {
            call.reject("Missing required parameter: identifier")
            return
        }

        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.removeGeofence(identifier, success: {
            call.resolve()
        }, failure: { error in
            call.reject(error, nil, nil, [:])
        })
    }

    @objc func removeGeofences(_ call: CAPPluginCall) {
        let identifiers = call.options["identifiers"] as? [String] ?? []
        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.removeGeofences(identifiers, success: {
            call.resolve()
        }, failure: { error in
            call.reject(error, nil, nil, [:])
        })
    }

    @objc func getOdometer(_ call: CAPPluginCall) {
        let locationManager = BackgroundGeolocation.sharedInstance()
        let distance = locationManager.getOdometer()
        call.resolve(["odometer": distance])
    }

    @objc func setOdometer(_ call: CAPPluginCall) {
        let value = call.getDouble("odometer") ?? 0
        let locationManager = BackgroundGeolocation.sharedInstance()

        let request = TSCurrentPositionRequest.make(
            type: .current,
            success: { event in
                if let eventData = event.data as? [String: Any] {
                    call.resolve(eventData)
                } else if let dict = event.toDictionary() as? [String: Any] {
                    call.resolve(dict)
                } else {
                    call.reject("Failed to convert location to dictionary", nil, nil, [:])
                }
            },
            failure: { error in
                call.reject("set_odometer_error", error.localizedDescription, error, [:])
            }
        )

        locationManager.setOdometer(value, request: request)
    }

    @objc func destroyLocations(_ call: CAPPluginCall) {
        let locationManager = BackgroundGeolocation.sharedInstance()
        let result = locationManager.destroyLocations()
        if result {
            call.resolve()
        } else {
            call.reject("destroyLocations:  Unknown error", nil, nil, [:])
        }
    }

    @objc func destroyLocation(_ call: CAPPluginCall) {
        guard let uuid = call.getString("uuid") else {
            call.reject("Missing required parameter: uuid")
            return
        }

        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.destroyLocation(uuid, success: {
            call.resolve()
        }, failure: { error in
            call.reject(error, nil, nil, [:])
        })
    }

    @objc func getCount(_ call: CAPPluginCall) {
        let locationManager = BackgroundGeolocation.sharedInstance()
        let count = locationManager.getCount()
        call.resolve(["count": count])
    }

    @objc func insertLocation(_ call: CAPPluginCall) {
        let params = call.getObject("options") ?? [:]
        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.insertLocation(params, success: { uuid in
            call.resolve(["uuid": uuid])
        }, failure: { error in
            call.reject(error, nil, nil, [:])
        })
    }

    @objc func getLog(_ call: CAPPluginCall) {
        let params = call.getObject("options") ?? [:]
        let locationManager = BackgroundGeolocation.sharedInstance()
        let query = LogQuery(dictionary: params)
        locationManager.getLog(query, success: { log in
            call.resolve(["log": log])
        }, failure: { error in
            call.reject(error, nil, nil, [:])
        })
    }

    @objc func destroyLog(_ call: CAPPluginCall) {
        let locationManager = BackgroundGeolocation.sharedInstance()
        let result = locationManager.destroyLog()
        if result {
            call.resolve()
        } else {
            call.reject("destroyLog Unknown error", nil, nil, [:])
        }
    }

    @objc func emailLog(_ call: CAPPluginCall) {
        let params = call.getObject("query") ?? [:]
        guard let email = call.getString("email") else {
            call.reject("Missing required parameter: email")
            return
        }

        let query = LogQuery(dictionary: params)
        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.emailLog(email, query: query, success: {
            call.resolve()
        }, failure: { error in
            call.reject(error, nil, nil, [:])
        })
    }

    @objc func uploadLog(_ call: CAPPluginCall) {
        let params = call.getObject("query") ?? [:]
        guard let url = call.getString("url") else {
            call.reject("Missing required parameter: url")
            return
        }

        let query = LogQuery(dictionary: params)
        let locationManager = BackgroundGeolocation.sharedInstance()
        locationManager.uploadLog(url, query: query, success: {
            call.resolve()
        }, failure: { error in
            call.reject(error, nil, nil, [:])
        })
    }

    @objc func log(_ call: CAPPluginCall) {
        let level = call.getString("level") ?? "debug"
        let message = call.getString("message") ?? "no message"
        BackgroundGeolocation.sharedInstance().log(level, message: message)
        call.resolve()
    }

    @objc func getSensors(_ call: CAPPluginCall) {
        let locationManager = BackgroundGeolocation.sharedInstance()
        let sensors: [String: Any] = [
            "platform": "ios",
            "accelerometer": locationManager.isAccelerometerAvailable(),
            "gyroscope": locationManager.isGyroAvailable(),
            "magnetometer": locationManager.isMagnetometerAvailable(),
            "motion_hardware": locationManager.isMotionHardwareAvailable()
        ]
        call.resolve(sensors)
    }

    @objc func getDeviceInfo(_ call: CAPPluginCall) {
        let deviceInfo = TSDeviceInfo.sharedInstance()
        if let infoDict = deviceInfo.toDictionary("capacitor") as? [String: Any] {
            call.resolve(infoDict)
        } else {
            call.reject("Failed to get device info", nil, nil, [:])
        }
    }

    @objc func isPowerSaveMode(_ call: CAPPluginCall) {
        let locationManager = BackgroundGeolocation.sharedInstance()
        call.resolve([
            "isPowerSaveMode": locationManager.isPowerSaveMode()
        ])
    }

    @objc func isIgnoringBatteryOptimizations(_ call: CAPPluginCall) {
        call.resolve([
            "isIgnoringBatteryOptimizations": false
        ])
    }

    @objc func requestSettings(_ call: CAPPluginCall) {
        call.reject("No iOS Implementation", nil, nil, [:])
    }

    @objc func showSettings(_ call: CAPPluginCall) {
        call.reject("No iOS Implementation", nil, nil, [:])
    }

    @objc func requestPermission(_ call: CAPPluginCall) {
        BackgroundGeolocation.sharedInstance().requestPermission({ status in
            call.resolve(["success": true, "status": status])
        }, failure: { status in
            call.resolve(["success": false, "status": status])
        })
    }

    @objc func requestTemporaryFullAccuracy(_ call: CAPPluginCall) {
        let purpose = call.getString("purpose") ?? ""
        BackgroundGeolocation.sharedInstance().requestTemporaryFullAccuracy(purpose, success: { accuracyAuthorization in
            call.resolve(["accuracyAuthorization": accuracyAuthorization])
        }, failure: { error in
            if let nsError = error as? NSError,
               let debugDescription = nsError.userInfo["NSDebugDescription"] as? String {
                call.reject(debugDescription, nil, error, nsError.userInfo)
            } else {
                call.reject("Unknown error occurred", nil, error, [:])
            }
        })
    }
    
    @objc func getProviderState(_ call: CAPPluginCall) {
        let locationManager = BackgroundGeolocation.sharedInstance()
        let event = locationManager.getProviderState()
        if let stateData = event.toDictionary() as? [String: Any] {
            call.resolve(stateData)
        } else {
            call.reject("Failed to convert provider state to dictionary", nil, nil, [:])
        }
    }

    @objc func getTransistorToken(_ call: CAPPluginCall) {
        guard let orgname = call.getString("org") else {
            call.resolve([
                "success": false,
                "status": -1,
                "message": "Missing required parameter: org"
            ])
            return
        }

        guard let username = call.getString("username") else {
            call.resolve([
                "success": false,
                "status": -1,
                "message": "Missing required parameter: username"
            ])
            return
        }

        guard let url = call.getString("url") else {
            call.resolve([
                "success": false,
                "status": -1,
                "message": "Missing required parameter: url"
            ])
            return
        }

        TransistorAuthorizationToken.findOrCreate(
            withOrg: orgname,
            username: username,
            url: url,
            framework: "capacitor",
            success: { token in
                if let tokenData = token.toDictionary() as? [String: Any] {
                    let result: [String: Any] = [
                        "success": true,
                        "token": tokenData
                    ]
                    call.resolve(result)
                } else {
                    call.reject("Failed to convert token to dictionary", nil, nil, [:])
                }
            },
            failure: { error in
                let result: [String: Any]
                if let nsError = error as? NSError {
                    result = [
                        "success": false,
                        "status": nsError.code,
                        "message": nsError.localizedDescription
                    ]
                } else {
                    result = [
                        "success": false,
                        "status": -1,
                        "message": "Unknown error occurred"
                    ]
                }
                call.resolve(result)
            }
        )
    }

    @objc func destroyTransistorToken(_ call: CAPPluginCall) {
        guard let url = call.getString("url") else {
            call.reject("Missing required parameter: url")
            return
        }
        TransistorAuthorizationToken.destroy(withUrl: url)
        call.resolve()
    }


    @objc func playSound(_ call: CAPPluginCall) {
        let soundId = call.getInt("soundId") ?? 0
        BackgroundGeolocation.sharedInstance().playSound(UInt32(soundId))
        call.resolve()
    }

    @objc func removeAllEventListeners(_ call: CAPPluginCall) {
        self.removeAllListeners(call)
        NSLog("BackgroundGeolocation plugin removeAllListeners")
    }

    func buildGeofence(_ params: [String: Any]) -> TSGeofence? {
        guard let identifier = params["identifier"] as? String,
              (params["vertices"] != nil || (params["radius"] != nil && params["latitude"] != nil && params["longitude"] != nil)) else {
            return nil
        }

        let radius = (params["radius"] as? NSNumber)?.doubleValue ?? 0
        let latitude = (params["latitude"] as? NSNumber)?.doubleValue ?? 0
        let longitude = (params["longitude"] as? NSNumber)?.doubleValue ?? 0
        let notifyOnEntry = (params["notifyOnEntry"] as? Bool) ?? false
        let notifyOnExit = (params["notifyOnExit"] as? Bool) ?? false
        let notifyOnDwell = (params["notifyOnDwell"] as? Bool) ?? false
        let loiteringDelay = (params["loiteringDelay"] as? NSNumber)?.doubleValue ?? 0
        let extras = params["extras"] as? [String: Any]

        let vertices: [[Double]]? = (params["vertices"] as? [[NSNumber]])?.map {
            $0.map { $0.doubleValue }
        }

        return TSGeofence(
            identifier: identifier,
            radius: radius,
            latitude: latitude,
            longitude: longitude,
            notifyOnEntry: notifyOnEntry,
            notifyOnExit: notifyOnExit,
            notifyOnDwell: notifyOnDwell,
            loiteringDelay: loiteringDelay,
            extras: extras,
            vertices: vertices
        )
    }

    deinit {
        BackgroundGeolocation.sharedInstance().removeListeners()
    }
}
