package top.yeij.cyrene.voice.stt

import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import top.yeij.cyrene.util.RuntimeLog

class SttManager(
    private val dashScopeService: DashScopeSttService,
    private val backendProvider: BackendSttProvider,
    private val systemRecognizer: SpeechRecognizer,
) {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

    private val _isListening = MutableStateFlow(false)
    val isListening: StateFlow<Boolean> = _isListening.asStateFlow()

    private val _partialResult = MutableStateFlow("")
    val partialResult: StateFlow<String> = _partialResult.asStateFlow()

    private val _finalResult = MutableSharedFlow<SttResult>(extraBufferCapacity = 16)
    val finalResult: SharedFlow<SttResult> = _finalResult.asSharedFlow()

    private val _onError = MutableSharedFlow<String>(extraBufferCapacity = 8)
    val onError: SharedFlow<String> = _onError.asSharedFlow()

    private var activeProvider: SttProvider? = null
    private var dashScopeFailed = false

    fun start() {
        cancel()

        val provider = if (dashScopeService.apiKey.isNotBlank() && !dashScopeFailed) {
            Log.d(TAG, "Using DashScope STT")
            RuntimeLog.stt("start", "Using DashScope provider")
            dashScopeService.also { dashScopeFailed = false }
        } else {
            Log.d(TAG, "Using Backend STT (fallback)")
            RuntimeLog.stt("start", "Using Backend provider (fallback)")
            backendProvider
        }

        activeProvider = provider
        _isListening.value = true
        _partialResult.value = ""
        collectFrom(provider)
        provider.start()
    }

    fun stop() {
        RuntimeLog.stt("stop", "Stopping STT")
        activeProvider?.stop()
        _isListening.value = false
    }

    fun cancel() {
        RuntimeLog.stt("cancel", "Cancelling STT")
        activeProvider?.cancel()
        activeProvider = null
        _isListening.value = false
        _partialResult.value = ""
    }

    fun configureDashScope(apiKey: String, endpoint: String, model: String) {
        dashScopeService.apiKey = apiKey
        dashScopeService.endpoint = endpoint
        dashScopeService.model = model
        dashScopeFailed = false
    }

    fun updateDashScopeApiKey(apiKey: String) {
        dashScopeService.apiKey = apiKey
        if (apiKey.isNotBlank()) dashScopeFailed = false
    }

    fun shutdown() {
        cancel()
        dashScopeService.shutdown()
        backendProvider.shutdown()
        scope.cancel()
    }

    private fun collectFrom(provider: SttProvider) {
        scope.launch {
            provider.partialResult.collect { text ->
                if (provider == activeProvider && text.isNotEmpty()) {
                    _partialResult.value = text
                }
            }
        }
        scope.launch {
            provider.finalResult.collect { result ->
                if (provider == activeProvider) {
                    RuntimeLog.stt("result", "Final result: isFinal=${result.isFinal} text=${result.text.take(80)}")
                    _finalResult.tryEmit(result)
                }
            }
        }
        scope.launch {
            provider.onError.collect { error ->
                if (provider == activeProvider) {
                    Log.w(TAG, "STT error: $error")
                    RuntimeLog.stt("error", error)
                    _onError.tryEmit(error)

                    if (provider == dashScopeService) {
                        dashScopeFailed = true
                        RuntimeLog.stt("fallback", "DashScope failed, will use backend next time")
                        Log.i(TAG, "DashScope failed, will use backend next time")
                    }
                }
            }
        }
    }

    companion object {
        private const val TAG = "SttManager"
    }
}
