# OverviewFragmentViewModel.kt

    /*
     * The MIT License
     *
     * Original work sponsored and donated by The Danish Health Data Authority (http://www.sundhedsdatastyrelsen.dk)
     *
     * Copyright (C) 2020 The Danish Health Data Authority (http://www.sundhedsdatastyrelsen.dk)
     *
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy of
     * this software and associated documentation files (the "Software"), to deal in
     * the Software without restriction, including without limitation the rights to
     * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
     * of the Software, and to permit persons to whom the Software is furnished to do
     * so, subject to the following conditions:
     *
     *
     * The above copyright notice and this permission notice shall be included in all
     * copies or substantial portions of the Software.
     *
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     * SOFTWARE.
     */

    package dk.nsp.mingraviditet.ui.main.overview

    import androidx.lifecycle.*
    import com.commonsense.android.kotlin.base.extensions.coroutines.launchIO
    import dagger.hilt.android.lifecycle.HiltViewModel
    import dk.nsp.domain.knowledge.Article
    import dk.nsp.domain.knowledge.ArticleCategory
    import dk.nsp.domain.repository.ArticlesRepository
    import dk.nsp.domain.repository.DataCardRepository
    import dk.nsp.mingraviditet.R
    import dk.nsp.mingraviditet.domain.AlertDialogButtonModel
    import dk.nsp.mingraviditet.domain.AlertDialogViewModel
    import dk.nsp.mingraviditet.extensions.fullName
    import dk.nsp.mingraviditet.extensions.getOverviewPregnancyState
    import dk.nsp.mingraviditet.extensions.userFullname
    import dk.nsp.mingraviditet.ui.adapterviews.EmptyStateRow
    import dk.nsp.mingraviditet.ui.adapterviews.ErrorStateRow
    import dk.nsp.mingraviditet.ui.adapterviews.FullWidthImageViewRow
    import dk.nsp.mingraviditet.ui.adapterviews.ImageViewData
    import dk.nsp.mingraviditet.ui.common.SectionHeaderAdapterView
    import dk.nsp.mingraviditet.ui.main.knowledge.CategoryCard
    import dk.nsp.mingraviditet.ui.main.knowledge.CategoryCardData
    import dk.nsp.mingraviditet.ui.main.more.documentation.DocumentationCardData
    import dk.nsp.mingraviditet.ui.main.more.documentation.DocumentationCardWithButtonRow
    import dk.nsp.mingraviditet.ui.main.overview.widget.OverviewPregnancyState
    import dk.nsp.mingraviditet.util.android.recycler.RecyclerAdapterView
    import dk.nsp.mingraviditet.util.extensions.*
    import dk.nsp.mingraviditet.util.livedata.SingleEvent
    import dk.nsp.models.ArticleSearchResult
    import dk.nsp.models.DataCard
    import dk.nsp.models.DataCardWarningState
    import dk.nsp.models.DateOfBirth
    import dk.nsp.shared.Failed
    import dk.nsp.shared.ResultWrapper
    import dk.nsp.shared.Success
    import dk.nsp.shared.util.sharedprefs.SharedPrefsHelper
    import dk.nsp.util.TextModel
    import dk.nsp.util.hasSearchResults
    import dk.nsp.util.isAfterBirth
    import dk.nsp.util.searchResults
    import kotlinx.coroutines.CoroutineScope
    import java.time.LocalDateTime
    import javax.inject.Inject
    import kotlin.math.max

    @HiltViewModel
    class OverviewFragmentViewModel @Inject constructor(
        private val articlesRepository: ArticlesRepository,
        private val dataCardRepository: DataCardRepository,
        private val sharedPrefs: SharedPrefsHelper
    ) : ViewModel() {

        private val _rowsToShow = MutableLiveData<List<RecyclerAdapterView<*>>>()
        val rowsToShow: LiveData<List<RecyclerAdapterView<*>>> = _rowsToShow

        private val _onFeedbackClicked = MutableLiveData<SingleEvent<Unit>>()
        val onFeedbackClicked: LiveData<SingleEvent<Unit>> = _onFeedbackClicked

        private val _onUpdateDateOfBirthClicked = MutableLiveData<SingleEvent<DateOfBirth>>()
        val onUpdateDateOfBirthClicked: LiveData<SingleEvent<DateOfBirth>> = _onUpdateDateOfBirthClicked

        private val _onSearchedArticleClicked: MutableLiveData<SingleEvent<Pair<Article, Int>>> =
            MutableLiveData()
        val onSearchArticleClicked: LiveData<SingleEvent<Pair<Article, Int>>> =
            _onSearchedArticleClicked

        private val _navigateToKnowledge = MutableLiveData<SingleEvent<Unit>>()
        val navigateToKnowledge: LiveData<SingleEvent<Unit>> = _navigateToKnowledge

        private val _navigateToDocumentation = MutableLiveData<SingleEvent<Unit>>()
        val navigateToDocumentation: LiveData<SingleEvent<Unit>> = _navigateToDocumentation

        val usersName = dataCardRepository.currentDataCard.transform {
            it?.userFullname
                ?: dataCardRepository.userInfoFromDataCard?.fullName
                ?: ""
        }

        val pregnancyProgress: LiveData<OverviewPregnancyState> =
            MediatorLiveData<OverviewPregnancyState>().apply {
                addSource(dataCardRepository.currentDataCard) {
                    postValue(
                        getPregnancyState(
                            it,
                            dataCardRepository.dateOfBirthForCurrentDataCard.value
                        )
                    )
                }
                addSource(dataCardRepository.dateOfBirthForCurrentDataCard) {
                    postValue(
                        getPregnancyState(
                            dataCardRepository.currentDataCard.value,
                            it
                        )
                    )
                }
            }

        val updateContentListener = pregnancyProgress.transform {
            viewModelScope.launchIO {
                val pregnancyWeek =
                    dataCardRepository.currentDataCard.value?.expectedDateOfDeliverySection?.week

                val dateOfBirthWeekOffset =
                    dataCardRepository.dateOfBirthForCurrentDataCard.value?.weeksSinceNow()?.toInt()

                val articlesWeek = when {
                    pregnancyWeek == null -> 1
                    dateOfBirthWeekOffset == null -> pregnancyWeek
                    else -> max(40 + dateOfBirthWeekOffset, pregnancyWeek)
                }

                _rowsToShow.postValue(getRows(articlesWeek, it))
            }
        }

        val dataCardWarning: LiveData<SingleEvent<AlertDialogViewModel>> =
            MediatorLiveData<SingleEvent<AlertDialogViewModel>>().apply {
                addSource(dataCardRepository.dataCardWarning) {
                    val warningState = when (it) {
                        DataCardWarningState.NoCourses -> {
                            if (sharedPrefs.dataCardNoCoursesWarningShown != true) {
                                AlertDialogViewModel(
                                    title = R.string.common_no_course_available_alert_title,
                                    message = R.string.common_no_course_available_alert_text,
                                    cancelable = false,
                                    positiveButton = AlertDialogButtonModel(R.string.common_ok) { _, _ ->
                                        sharedPrefs.dataCardNoCoursesWarningShown = true
                                    }
                                )
                            } else null
                        }
                        DataCardWarningState.NoActiveCourse -> {
                            if (sharedPrefs.dataCardNoActiveCourseWarningShown != true) {
                                AlertDialogViewModel(
                                    title = R.string.common_no_active_course_available_alert_title,
                                    message = R.string.common_no_active_course_available_alert_text,
                                    cancelable = false,
                                    positiveButton = AlertDialogButtonModel(R.string.common_ok) { _, _ ->
                                        sharedPrefs.dataCardNoActiveCourseWarningShown = true
                                    }
                                )
                            } else null
                        }
                        null -> null
                    }
                    warningState?.let {
                        postSingleEvent(it)
                    }
                }
            }

        private fun onDidGiveBirthPressed() {
            _onUpdateDateOfBirthClicked.postSingleEvent(DateOfBirth())
        }

        private fun presentDateOfBirthCallback(currentDateOfBirth: LocalDateTime?) {
            _onUpdateDateOfBirthClicked.postSingleEvent(DateOfBirth(currentDateOfBirth))
        }

        private fun getPregnancyState(
            currentCard: DataCard?,
            dateOfBirth: LocalDateTime?
        ): OverviewPregnancyState =
            currentCard.getOverviewPregnancyState(
                dateOfBirth,
                ::onDidGiveBirthPressed
            ) {
                presentDateOfBirthCallback(dateOfBirth)
            }

        private suspend fun getRows(
            pregnancyWeek: Int,
            state: OverviewPregnancyState
        ): List<RecyclerAdapterView<*>> {
            return when (state) {
                is OverviewPregnancyState.PregnancyEnded -> createEndedRows()
                else -> {
                    val combine =
                        createArticleRowsForCurrentAndPrevious(pregnancyWeek, viewModelScope)
                    combine.add(createFeedbackCard())
                    combine
                }
            }
        }

        private suspend fun createArticleRowsForCurrentAndPrevious(
            pregnancyWeek: Int,
            scope: CoroutineScope
        ): MutableList<RecyclerAdapterView<*>> {
            val thisWeek = getArticlesForWeek(scope, pregnancyWeek)
            val nextWeek = getArticlesForWeek(scope, pregnancyWeek + 1)

            val combine = mutableListOf<RecyclerAdapterView<*>>()

            val enableEmptyStateForThisWeek = when (nextWeek) {
                is Failed -> true
                is Success -> nextWeek.value.isNotEmpty()
            }

            combine.addAll(
                createArticleSection(
                    thisWeek,
                    isFutureSection = false,
                    showEmptyState = enableEmptyStateForThisWeek
                )
            )

            combine.addAll(
                createArticleSection(
                    nextWeek,
                    isFutureSection = true,
                    showEmptyState = true
                )
            )

            return combine
        }

        private fun createEndedRows() = listOf(
            FullWidthImageViewRow(ImageViewData.DrawableFromAttribute(R.attr.sofaIllustration)),
            DocumentationCardWithButtonRow(
                DocumentationCardData(
                    TextModel.from(
                        R.string.overview_ended_title,
                        dataCardRepository.currentDataCard.value?.closeDateTime
                            ?.toDefaultMonthDisplay()
                            .toTextModelOrDefault()
                    ),
                    TextModel.from(R.string.overview_ended_description)
                )
            ),
            CategoryCard(
                CategoryCardData.WithoutIcon(
                    TextModel.from(R.string.overview_ended_data_title),
                    TextModel.from(R.string.overview_ended_data_description),
                    TextModel.from(R.string.overview_ended_data_go_to_settings_label)
                )
                {
                    _navigateToDocumentation.postSingleEvent()
                }),
            createFeedbackCard()
        )

        private fun createFeedbackCard() = OverviewFeedbackCard { _onFeedbackClicked.postSingleEvent() }

        private fun createSectionHeader(isFutureSection: Boolean): RecyclerAdapterView<*> {
            val moreButtonText =
                if (!isFutureSection) TextModel.from(R.string.overview_find_more_knowledge_button) else null
            val moreButtonAction = if (!isFutureSection) {
                { _navigateToKnowledge.postSingleEvent() }
            } else null

            return SectionHeaderAdapterView(
                TextModel.from(
                    if (isFutureSection) R.string.overview_relevant_next_week_title else R.string.overview_relevant_right_now_title
                ),
                moreButtonText,
                moreButtonAction
            )
        }

        private fun createArticleSection(
            result: ResultWrapper<List<ArticleCategory>>,
            isFutureSection: Boolean,
            showEmptyState: Boolean
        ): List<RecyclerAdapterView<*>> {
            val hasGivenBirth = dataCardRepository.dateOfBirthForCurrentDataCard.value != null

            when (result) {
                is Failed -> return listOf(
                    createSectionHeader(isFutureSection),
                    ErrorStateRow(
                        TextModel.from(R.string.common_alert_title_error),
                        TextModel.from("${result.error.message}")
                    )
                )
                is Success -> {
                    val categories = if (hasGivenBirth) {
                        result.value.filter { it.isAfterBirth }
                    } else {
                        result.value
                    }

                    if (!categories.hasSearchResults() && !isFutureSection) {
                        if (!showEmptyState) {
                            return emptyList()
                        }

                        return listOf(
                            createSectionHeader(isFutureSection),
                            EmptyStateRow(
                                TextModel.from(R.string.overview_empty_state_title),
                                TextModel.from(R.string.overview_empty_state_text)
                            )
                        )
                    } else {
                        val categoryCards = categories.map { category ->
                            category.searchResults().map {
                                createCardForSearchResult(it)
                            }
                        }.flatten()

                        val rowsToShow = mutableListOf<RecyclerAdapterView<*>>()
                        rowsToShow.add(createSectionHeader(isFutureSection))
                        rowsToShow.addAll(categoryCards)

                        return rowsToShow
                    }
                }
            }
        }

        private suspend fun getArticlesForWeek(
            scope: CoroutineScope,
            week: Int
        ): ResultWrapper<List<ArticleCategory>> {
            return articlesRepository.getArticlesForWeek(scope, week).await()
        }

        private fun createCardForSearchResult(articleSearchResult: ArticleSearchResult): CategoryCard {
            return CategoryCard(
                CategoryCardData.WithIconUrl(
                    articleSearchResult.title,
                    articleSearchResult.description,
                    TextModel.from(articleSearchResult.subtitle),
                    articleSearchResult.iconUrl
                ) {
                    _onSearchedArticleClicked.postValue(
                        Pair(
                            articleSearchResult.article,
                            articleSearchResult.articleSectionId ?: -1
                        ).toSingleEvent()
                    )
                })
        }
    }