package com.anytypeio.anytype.core_utils.ui

import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.anytypeio.anytype.core_utils.BuildConfig
import com.anytypeio.anytype.core_utils.ext.throttleFirst
import com.anytypeio.anytype.core_utils.insets.RootViewDeferringInsetsCallback
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber


abstract class BaseFragment<T : ViewBinding>(
    @LayoutRes private val layout: Int,
    private val fragmentScope: Boolean = true
) : Fragment(layout) {

    private var applyWindowRootInsets: Boolean = true
    protected val handler = Handler(Looper.getMainLooper())

    private var _binding: T? = null
    val binding: T get() = _binding!!
    val hasBinding get() = _binding != null

    val jobs = mutableListOf<Job>()
    private val throttleFlow = MutableSharedFlow<() -> Unit>(0)

    abstract fun injectDependencies()
    abstract fun releaseDependencies()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        injectDependencies()
    }

    override fun onStart() {
        super.onStart()
        proceed(throttleFlow.throttleFirst(THROTTLE_DURATION)) { it() }
    }

    protected fun throttle(task: () -> Unit) {
        jobs += this.lifecycleScope.launch { throttleFlow.emit { task() } }
    }

    override fun onStop() {
        super.onStop()
        handler.removeCallbacksAndMessages(null)
        jobs.apply {
            forEach { it.cancel() }
            clear()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        if (fragmentScope) releaseDependencies()
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = inflateBinding(inflater, container)
        return _binding?.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        if (applyWindowRootInsets) {
            onApplyWindowRootInsets()
        }
    }

    open fun onApplyWindowRootInsets() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            val deferringInsetsListener = RootViewDeferringInsetsCallback(
                persistentInsetTypes = WindowInsetsCompat.Type.systemBars(),
                deferredInsetTypes = WindowInsetsCompat.Type.ime()
            )
            ViewCompat.setWindowInsetsAnimationCallback(binding.root, deferringInsetsListener)
            ViewCompat.setOnApplyWindowInsetsListener(binding.root, deferringInsetsListener)
        }
    }

    protected fun DialogFragment.showChildFragment(tag: String? = null) {
        jobs += this@BaseFragment.lifecycleScope.launch {
            try {
                throttleFlow.emit {
                    runCatching {
                        show(this@BaseFragment.childFragmentManager, tag)
                    }.fold(
                        onSuccess = {
                            // Do nothing.
                        },
                        onFailure = {
                            Timber.e(it, "Error while navigation")
                        }
                    )
                }
            } catch (e: Exception) {
                Timber.e(e, "Error while navigation")
            }
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    protected abstract fun inflateBinding(inflater: LayoutInflater, container: ViewGroup?): T
}

fun <T> BaseFragment<*>.proceed(flow: Flow<T>, body: suspend (T) -> Unit) {
    jobs += flow
        .cancellable()
        .onEach {
            try {
                body(it)
            } catch (e: Exception) {
                Timber.e(e, "Unhandled exception in proceed flow")
            }
        }
        .launchIn(lifecycleScope)
}

const val THROTTLE_DURATION = 300L