package com.isidroid.core.vm

import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import timber.log.Timber

interface ViewEvent

interface ViewState

interface ViewSideEffect

const val SIDE_EFFECTS_KEY = "SIDE_EFFECTS_KEY"
const val LOG_TAG_MVI_COLLECTOR = "mvi"

abstract class BaseViewModel<Event : ViewEvent, UiState : ViewState, Effect : ViewSideEffect> : ViewModel() {
    private var showFullEventDebug = true
    private var logTag = LOG_TAG_MVI_COLLECTOR
    private var logTagSuffix: String? = null

    protected abstract val isDebug: Boolean
    protected abstract fun setInitialState(): UiState
    protected abstract suspend fun handleEvents(event: Event)

    private val initialState: UiState by lazy(LazyThreadSafetyMode.NONE) { setInitialState() }

    // we always want to receive last view state and also have the initial state
    private val _viewState: MutableState<UiState> = mutableStateOf(initialState)
    val viewState: State<UiState> = _viewState

    // if there is no any subscribers, event is immediately dropped
    private val _event: MutableSharedFlow<Event> = MutableSharedFlow()

    // Channels are hot. Kind of SingleLiveEvent. Event will be triggered for the first subscriber or be dropped otherwise
    private val _effect: Channel<Effect> = Channel()
    val effect = _effect.receiveAsFlow()

    init {
        subscribeToEvents()
    }

    private fun subscribeToEvents() {
        viewModelScope.launch {
            _event.collect { handleEvents(it) }
        }
    }

    fun setEvent(event: Event) {
        printLog("setEvent ${event.mviCollectionItem}")

        viewModelScope.launch { _event.emit(event) }
    }

    protected fun setState(reducer: UiState.() -> UiState) {
        val newState = viewState.value.reducer()
        printLog("setState ${newState.mviCollectionItem}, isChanged=${newState != _viewState.value}")
        _viewState.value = newState
    }

    protected fun setEffect(builder: () -> Effect) {
        val effectValue = builder()
        printLog("setEffect ${effectValue.mviCollectionItem}")

        viewModelScope.launch { _effect.send(effectValue) }
    }

    protected fun updateDebug(
        showFullEventDebug: Boolean? = null,
        logTag: String? = null,
        logTagSuffix: String? = null
    ) {
        showFullEventDebug?.also { this.showFullEventDebug = showFullEventDebug }
        logTag?.also { this.logTag = logTag }
        logTagSuffix?.also { this.logTagSuffix = it }
    }

    private fun printLog(message: String) {
        if (isDebug) {
            val logMessage = buildString {
                append(message)
            }

            val messageTag = buildString {
                append(logTag)
                append("_${this@BaseViewModel.javaClass.simpleName.replace("ViewModel", "")}")
                if (!logTagSuffix.isNullOrBlank())
                    append("_$logTagSuffix")
            }

            Timber.tag(messageTag).i(logMessage)
        }
    }

    private val Any.mviCollectionItem get() = if (showFullEventDebug) "$this" else javaClass.simpleName
}