// Copyright (C) 2026 Jason M. Schwefel. All Rights Reserved.
package com.coldboreballisticsllc.scopedope.data.sensor

import android.content.Context
import android.hardware.GeomagneticField
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.location.LocationManager
import androidx.core.content.ContextCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
import javax.inject.Singleton

enum class CompassWindStatus { IDLE, READING, OK, LOW_ACCURACY, UNAVAILABLE }

@Singleton
class PhoneCompassManager @Inject constructor(
    @ApplicationContext private val context: Context,
) {
    private val sm = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager

    private val _status  = MutableStateFlow(CompassWindStatus.IDLE)
    val status: StateFlow<CompassWindStatus> = _status.asStateFlow()

    private val _bearingDeg = MutableStateFlow<Double?>(null)
    val bearingDeg: StateFlow<Double?> = _bearingDeg.asStateFlow()

    @Volatile private var listener: SensorEventListener? = null

    fun start() {
        if (listener != null) return
        val sensor = sm.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
        if (sensor == null) {
            _status.value = CompassWindStatus.UNAVAILABLE
            return
        }
        _status.value = CompassWindStatus.READING
        val l = object : SensorEventListener {
            override fun onSensorChanged(event: SensorEvent) {
                val lowAccuracy = event.accuracy < SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM
                val rotMatrix = FloatArray(9)
                SensorManager.getRotationMatrixFromVector(rotMatrix, event.values)
                val orientation = FloatArray(3)
                SensorManager.getOrientation(rotMatrix, orientation)
                var azimuthRad = orientation[0].toDouble()

                val hasPerm = ContextCompat.checkSelfPermission(
                    context, android.Manifest.permission.ACCESS_COARSE_LOCATION
                ) == android.content.pm.PackageManager.PERMISSION_GRANTED
                val loc = if (hasPerm) runCatching {
                    val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
                    listOf(
                        LocationManager.NETWORK_PROVIDER,
                        LocationManager.GPS_PROVIDER,
                        LocationManager.PASSIVE_PROVIDER,
                    ).firstNotNullOfOrNull { lm.getLastKnownLocation(it) }
                }.getOrNull() else null

                val geoLat = loc?.latitude?.toFloat()  ?: 0f
                val geoLon = loc?.longitude?.toFloat() ?: 0f
                val geoAlt = loc?.altitude?.toFloat()  ?: 0f
                if (geoLat != 0.0f) {
                    val decl = GeomagneticField(geoLat, geoLon, geoAlt, System.currentTimeMillis()).declination
                    azimuthRad += Math.toRadians(decl.toDouble())
                }

                _bearingDeg.value = ((Math.toDegrees(azimuthRad) + 360.0) % 360.0)
                _status.value = if (lowAccuracy) CompassWindStatus.LOW_ACCURACY else CompassWindStatus.OK
            }
            override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
        }
        listener = l
        sm.registerListener(l, sensor, SensorManager.SENSOR_DELAY_UI)
    }

    fun stop() {
        listener?.let { sm.unregisterListener(it) }
        listener = null
        _status.value = CompassWindStatus.IDLE
        _bearingDeg.value = null
    }
}
