package com.airbnb.lottie.samples

import android.content.Context
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingDataAdapter
import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.cachedIn
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.lottie.samples.api.LottiefilesApi
import com.airbnb.lottie.samples.databinding.LottiefilesFragmentBinding
import com.airbnb.lottie.samples.model.AnimationData
import com.airbnb.lottie.samples.model.AnimationResponse
import com.airbnb.lottie.samples.model.CompositionArgs
import com.airbnb.lottie.samples.utils.MvRxViewModel
import com.airbnb.lottie.samples.utils.hideKeyboard
import com.airbnb.lottie.samples.utils.viewBinding
import com.airbnb.lottie.samples.views.AnimationItemView
import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch

data class LottiefilesState(
    val mode: LottiefilesMode = LottiefilesMode.Recent,
    val query: String = ""
) : MvRxState

class LottiefilesViewModel(initialState: LottiefilesState, private val api: LottiefilesApi) : MvRxViewModel<LottiefilesState>(initialState) {

    private var mode = initialState.mode
    private var query = initialState.query

    private var dataSource: LottiefilesDataSource? = null
    val pager = Pager(PagingConfig(pageSize = 16)) {
        LottiefilesDataSource(api, mode, query).also { dataSource = it }
    }.flow.cachedIn(viewModelScope)

    init {
        selectSubscribe(LottiefilesState::mode, LottiefilesState::query) { mode, query ->
            this.mode = mode
            this.query = query
            dataSource?.invalidate()
        }
    }

    fun setMode(mode: LottiefilesMode) = setState { copy(mode = mode) }

    fun setQuery(query: String) = setState { copy(query = query) }

    companion object : MvRxViewModelFactory<LottiefilesViewModel, LottiefilesState> {
        override fun create(viewModelContext: ViewModelContext, state: LottiefilesState): LottiefilesViewModel {
            val service = viewModelContext.app<LottieApplication>().lottiefilesService
            return LottiefilesViewModel(state, service)
        }
    }
}

class LottiefilesDataSource(
    private val api: LottiefilesApi,
    val mode: LottiefilesMode,
    private val query: String
) : PagingSource<Int, AnimationData>() {

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AnimationData> {
        val page = params.key ?: 1
        return try {
            val response = when (mode) {
                LottiefilesMode.Popular -> api.getPopular(page)
                LottiefilesMode.Recent -> api.getRecent(page)
                LottiefilesMode.Search -> {
                    if (query.isBlank()) {
                        AnimationResponse(page, emptyList(), "", page, null, "", 0, "", 0, 0)
                    } else {
                        api.search(query, page)
                    }
                }
            }

            LoadResult.Page(
                response.data,
                if (page == 1) null else page + 1,
                (page + 1).takeIf { page < response.lastPage }
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Int, AnimationData>): Int? {
        return state.closestItemToPosition(state.anchorPosition ?: return null)?.id as Int?
    }
}

class LottiefilesFragment : BaseMvRxFragment(R.layout.lottiefiles_fragment) {
    private val binding: LottiefilesFragmentBinding by viewBinding()
    private val viewModel: LottiefilesViewModel by fragmentViewModel()

    private object AnimationItemDataDiffCallback : DiffUtil.ItemCallback<AnimationData>() {
        override fun areItemsTheSame(oldItem: AnimationData, newItem: AnimationData) = oldItem.id == newItem.id

        override fun areContentsTheSame(oldItem: AnimationData, newItem: AnimationData) = oldItem == newItem
    }

    private class AnimationItemViewHolder(context: Context) : RecyclerView.ViewHolder(AnimationItemView(context)) {
        fun bind(data: AnimationData?) {
            val view = itemView as AnimationItemView
            view.setTitle(data?.title)
            view.setPreviewUrl(data?.preview)
            view.setPreviewBackgroundColor(data?.bgColorInt)
            view.setOnClickListener {
                val intent = PlayerActivity.intent(view.context, CompositionArgs(animationData = data))
                view.context.startActivity(intent)
            }
        }
    }


    private val adapter = object : PagingDataAdapter<AnimationData, AnimationItemViewHolder>(AnimationItemDataDiffCallback) {
        override fun onBindViewHolder(holder: AnimationItemViewHolder, position: Int) = holder.bind(getItem(position))

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = AnimationItemViewHolder(parent.context)

    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        binding.recyclerView.adapter = adapter
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.pager.collectLatest(adapter::submitData)
        }
        binding.tabBar.setRecentClickListener {
            viewModel.setMode(LottiefilesMode.Recent)
            requireContext().hideKeyboard()
        }
        binding.tabBar.setPopularClickListener {
            viewModel.setMode(LottiefilesMode.Popular)
            requireContext().hideKeyboard()
        }
        binding.tabBar.setSearchClickListener {
            viewModel.setMode(LottiefilesMode.Search)
            requireContext().hideKeyboard()
        }
        binding.searchView.query.onEach { query ->
            viewModel.setQuery(query)
        }.launchIn(viewLifecycleOwner.lifecycleScope)
    }

    override fun invalidate(): Unit = withState(viewModel) { state ->
        binding.searchView.isVisible = state.mode == LottiefilesMode.Search
        binding.tabBar.setMode(state.mode)
    }
}