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

import androidx.compose.runtime.Immutable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import com.coldboreballisticsllc.scopedope.data.prefs.AppPreferences
import com.coldboreballisticsllc.scopedope.data.sensor.CompassWindStatus
import com.coldboreballisticsllc.scopedope.data.sensor.PhoneCompassManager
import com.coldboreballisticsllc.scopedope.domain.sensor.SensorState
import com.coldboreballisticsllc.scopedope.domain.sensor.WeatherSensorSource
import javax.inject.Inject

enum class SensorSharedSubMode { READ_ONCE, FREEZE_DISCONNECT, TIMED_WINDOW }

@Immutable
data class SensorUiState(
    val sensorSharedMode:       Boolean             = false,
    val sensorSharedSubMode:    SensorSharedSubMode = SensorSharedSubMode.READ_ONCE,
    val sensorSharedWindowSecs: Int                 = 30,
    val sensorFriendlyName:     String?             = null,
    val usePhoneCompassWind:    Boolean             = true,
)

sealed class SensorEvent {
    data object FreezeConditions : SensorEvent()
}

@HiltViewModel
class SensorViewModel @Inject constructor(
    private val btSensor:       WeatherSensorSource,
    private val prefs:          AppPreferences,
    val phoneCompass:           PhoneCompassManager,
) : ViewModel() {

    private val _uiState = MutableStateFlow(SensorUiState())
    val uiState: StateFlow<SensorUiState> = _uiState.asStateFlow()

    val sensorState: StateFlow<SensorState> = btSensor.state.stateIn(
        viewModelScope, SharingStarted.Eagerly, SensorState.Disconnected,
    )

    val sensorDriverId: StateFlow<String> = prefs.sensorDriverId.stateIn(
        viewModelScope, SharingStarted.Eagerly, "weatherflow_tactical",
    )

    val compassStatus: StateFlow<CompassWindStatus> = phoneCompass.status.stateIn(
        viewModelScope, SharingStarted.Eagerly, CompassWindStatus.IDLE,
    )

    val compassBearingDeg: StateFlow<Double?> = phoneCompass.bearingDeg.stateIn(
        viewModelScope, SharingStarted.Eagerly, null,
    )

    private val _events = Channel<SensorEvent>(Channel.BUFFERED)
    val events: Flow<SensorEvent> = _events.receiveAsFlow()

    init {
        viewModelScope.launch { loadPrefs() }

        viewModelScope.launch {
            prefs.sensorDriverId.collect { driverId ->
                val name = prefs.getSensorFriendlyName(driverId)
                _uiState.update { it.copy(sensorFriendlyName = name) }
            }
        }

        // Start/stop phone compass in sync with BLE sensor connection state.
        viewModelScope.launch {
            btSensor.state.collect { state ->
                if (state is SensorState.Connected && _uiState.value.usePhoneCompassWind) {
                    phoneCompass.start()
                } else if (state !is SensorState.Connected) {
                    phoneCompass.stop()
                }
            }
        }
    }

    private suspend fun loadPrefs() {
        val sensorSharedMode       = prefs.sensorSharedMode.first()
        val sensorSharedSubModeStr = prefs.sensorSharedSubMode.first()
        val sensorSharedWindowSecs = prefs.sensorSharedWindowSeconds.first()
        val currentDriverId        = prefs.sensorDriverId.first()
        val sensorFriendlyName     = prefs.getSensorFriendlyName(currentDriverId)
        val usePhoneCompassWind    = prefs.usePhoneCompassWind.first()
        _uiState.update { it.copy(
            sensorSharedMode      = sensorSharedMode,
            sensorSharedSubMode   = SensorSharedSubMode.entries.find { it.name == sensorSharedSubModeStr }
                                        ?: SensorSharedSubMode.READ_ONCE,
            sensorSharedWindowSecs = sensorSharedWindowSecs,
            sensorFriendlyName    = sensorFriendlyName,
            usePhoneCompassWind   = usePhoneCompassWind,
        ) }
    }

    fun connect() {
        viewModelScope.launch {
            val s = _uiState.value
            btSensor.connect()
            if (s.sensorSharedMode) {
                when (s.sensorSharedSubMode) {
                    SensorSharedSubMode.READ_ONCE -> {
                        withTimeoutOrNull(30_000L) {
                            btSensor.readings.first {
                                it.temperatureF != null || it.windSpeedMph != null ||
                                it.pressureHpa  != null || it.humidityPct  != null
                            }
                        }
                        btSensor.disconnect()
                    }
                    SensorSharedSubMode.FREEZE_DISCONNECT -> {
                        withTimeoutOrNull(30_000L) {
                            btSensor.readings.first {
                                it.temperatureF != null || it.windSpeedMph != null ||
                                it.pressureHpa  != null || it.humidityPct  != null
                            }
                        }
                        _events.send(SensorEvent.FreezeConditions)
                        btSensor.disconnect()
                    }
                    SensorSharedSubMode.TIMED_WINDOW -> {
                        delay(s.sensorSharedWindowSecs * 1000L)
                        _events.send(SensorEvent.FreezeConditions)
                        btSensor.disconnect()
                    }
                }
            }
        }
    }

    fun disconnect() { viewModelScope.launch { btSensor.disconnect() } }

    fun setSensorDriverId(id: String) {
        viewModelScope.launch { prefs.setSensorDriverId(id) }
    }

    fun setSensorSharedMode(value: Boolean) {
        viewModelScope.launch {
            prefs.setSensorSharedMode(value)
            _uiState.update { it.copy(sensorSharedMode = value) }
        }
    }

    fun setSensorSharedSubMode(mode: SensorSharedSubMode) {
        viewModelScope.launch {
            prefs.setSensorSharedSubMode(mode.name)
            _uiState.update { it.copy(sensorSharedSubMode = mode) }
        }
    }

    fun setSensorSharedWindowSeconds(seconds: Int) {
        val clamped = seconds.coerceIn(1, 3600)
        viewModelScope.launch {
            prefs.setSensorSharedWindowSeconds(clamped)
            _uiState.update { it.copy(sensorSharedWindowSecs = clamped) }
        }
    }

    fun saveSensorFriendlyName(name: String) {
        val driverId = sensorDriverId.value
        val stored   = name.trim()
        viewModelScope.launch {
            prefs.setSensorFriendlyName(driverId, stored)
            _uiState.update { it.copy(sensorFriendlyName = stored.ifEmpty { null }) }
        }
    }

    fun setUsePhoneCompassWind(value: Boolean) {
        viewModelScope.launch {
            prefs.setUsePhoneCompassWind(value)
            _uiState.update { it.copy(usePhoneCompassWind = value) }
            if (value && sensorState.value is SensorState.Connected) {
                phoneCompass.start()
            } else if (!value) {
                phoneCompass.stop()
            }
        }
    }

    /** Called when the user confirms the manual-override dialog on the Conditions screen. */
    fun overrideCompassWithManual() {
        phoneCompass.stop()
    }
}
