/*
 * This file is part of SubTypo.
 *
 * SubTypo is free software: you can redistribute it and/or modify it under the terms of
 * the GNU General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * SubTypo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with SubTypo.
 * If not, see <https://www.gnu.org/licenses/>.
 */

package com.teixeira0x.subtypo.ui.projectlist.fragment

import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.selection.DefaultSelectionTracker
import androidx.recyclerview.selection.SelectionPredicates
import androidx.recyclerview.selection.SelectionTracker
import androidx.recyclerview.selection.StorageStrategy
import androidx.recyclerview.widget.LinearLayoutManager
import com.teixeira0x.subtypo.core.domain.model.Project
import com.teixeira0x.subtypo.ui.activity.Navigator.navigateToProjectActivity
import com.teixeira0x.subtypo.ui.common.R
import com.teixeira0x.subtypo.ui.common.base.Selectable
import com.teixeira0x.subtypo.ui.common.databinding.FragmentProjectListBinding
import com.teixeira0x.subtypo.ui.common.dialog.showConfirmDialog
import com.teixeira0x.subtypo.ui.common.fragment.ProgressDialogFragment
import com.teixeira0x.subtypo.ui.common.mvi.ViewEvent
import com.teixeira0x.subtypo.ui.common.util.showToastShort
import com.teixeira0x.subtypo.ui.optionlist.dialog.showOptionListDialog
import com.teixeira0x.subtypo.ui.optionlist.model.OptionItem
import com.teixeira0x.subtypo.ui.projectedit.fragment.ProjectEditSheetFragment
import com.teixeira0x.subtypo.ui.projectlist.adapter.ProjectClickListener
import com.teixeira0x.subtypo.ui.projectlist.adapter.ProjectListAdapter
import com.teixeira0x.subtypo.ui.projectlist.mvi.ProjectListIntent
import com.teixeira0x.subtypo.ui.projectlist.mvi.ProjectListViewEvent
import com.teixeira0x.subtypo.ui.projectlist.mvi.ProjectListViewState
import com.teixeira0x.subtypo.ui.projectlist.mvi.ToolbarViewState
import com.teixeira0x.subtypo.ui.projectlist.util.ProjectKeyProvider
import com.teixeira0x.subtypo.ui.projectlist.viewmodel.ProjectListViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

@AndroidEntryPoint
class ProjectListFragment : Fragment(), ProjectClickListener, Selectable {

  private var _binding: FragmentProjectListBinding? = null
  private val binding: FragmentProjectListBinding
    get() = checkNotNull(_binding) { "ProjectsFragment has been destroyed!" }

  private val viewModel by viewModels<ProjectListViewModel>()

  private var progress: ProgressDialogFragment? = null
  private var selectionActionMode: ActionMode? = null
  private lateinit var selectionTracker: SelectionTracker<Long>
  private lateinit var projectsAdapter: ProjectListAdapter

  override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?,
  ): View {
    return FragmentProjectListBinding.inflate(inflater, container, false)
      .also { _binding = it }
      .root
  }

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    observeViewModel()
    configureUI()

    viewModel.doIntent(ProjectListIntent.Refresh)
  }

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

  private fun observeViewModel() {
    viewModel.projectListViewState
      .flowWithLifecycle(viewLifecycleOwner.lifecycle)
      .onEach { state ->
        when (state) {
          is ProjectListViewState.Loading -> onChangeLoadingState(true, true)
          is ProjectListViewState.Loaded -> {
            onChangeLoadingState(false, state.projects.isEmpty())
            projectsAdapter.submitList(
              state.projects.sortedByDescending { it.id }
            )
          }
          is ProjectListViewState.Error -> Unit
        }
      }
      .launchIn(viewLifecycleOwner.lifecycleScope)

    viewModel.toolbarViewState
      .flowWithLifecycle(viewLifecycleOwner.lifecycle)
      .onEach { state ->
        when (state) {
          is ToolbarViewState.ActionBar -> {
            val selection = state.selection

            if (selection.isNotEmpty()) {
              binding.fabNewProject.hide()
              startSelectionActionMode()

              selectionActionMode?.title = "${selection.size}"
            } else {
              binding.fabNewProject.show()
              for (project in projectsAdapter.currentList) {
                toggleSelection(project, false)
              }
              finishSelectionActionMode()
            }
          }
        }
      }
      .launchIn(viewLifecycleOwner.lifecycleScope)

    viewModel.viewEvent
      .flowWithLifecycle(viewLifecycleOwner.lifecycle)
      .onEach { event ->
        when (event) {
          is ViewEvent.Toast -> requireContext().showToastShort(event.message)
        }
      }
      .launchIn(viewLifecycleOwner.lifecycleScope)

    viewModel.customViewEvent
      .flowWithLifecycle(viewLifecycleOwner.lifecycle)
      .onEach { event ->
        when (event) {
          is ProjectListViewEvent.ShowProgress -> {
            progress?.dismiss()
            progress =
              ProgressDialogFragment.newInstance(
                orientation = ProgressDialogFragment.ORIENTATION_HORIZONTAL,
                style = ProgressDialogFragment.STYLE_DEFAULT,
                message = event.message,
              )
            progress?.show(childFragmentManager, null)
          }
          is ProjectListViewEvent.DismissProgress -> {
            progress?.dismiss()
            progress = null
          }
        }
      }
      .launchIn(viewLifecycleOwner.lifecycleScope)
  }

  private fun onChangeLoadingState(isLoading: Boolean, isEmptyList: Boolean) =
    binding.apply {
      if (isLoading) fabNewProject.hide() else fabNewProject.show()
      noProjects.isVisible = !isLoading && isEmptyList
      rvProjects.isVisible = !isLoading
      progress.isVisible = isLoading
    }

  private fun configureUI() {
    binding.apply {
      fabNewProject.setOnClickListener { showEditProjectSheet() }

      rvProjects.layoutManager = LinearLayoutManager(requireContext())
      rvProjects.adapter =
        ProjectListAdapter(
            DefaultSelectionTracker<Long>(
                "DefaultSelectionTracker",
                ProjectKeyProvider(binding.rvProjects),
                SelectionPredicates.createSelectAnything(),
                StorageStrategy.createLongStorage(),
              )
              .also { selectionTracker = it },
            this@ProjectListFragment,
          )
          .also { projectsAdapter = it }
    }

    selectionTracker.addObserver(
      object : SelectionTracker.SelectionObserver<Long>() {
        override fun onSelectionCleared() {
          val selection = projectsAdapter.selection(selectionTracker.selection)
          viewModel.doIntent(ProjectListIntent.UpdateSelection(selection))
        }

        override fun onSelectionChanged() {
          val selection = projectsAdapter.selection(selectionTracker.selection)
          viewModel.doIntent(ProjectListIntent.UpdateSelection(selection))
        }
      }
    )
  }

  override fun onProjectClickListener(view: View, project: Project) {
    if (selectionActionMode != null) {
      return toggleSelection(project)
    }
    navigateToProjectActivity(requireContext(), project.id)
  }

  override fun onProjectLongClickListener(
    view: View,
    project: Project,
  ): Boolean {
    if (selectionActionMode == null) {
      showProjectOptionsSheet(project)
    } else {
      toggleSelection(project)
    }
    return true
  }

  private fun showProjectOptionsSheet(project: Project) {
    showOptionListDialog(
      context = requireContext(),
      options =
        listOf(
          OptionItem(
            R.drawable.ic_select_group,
            getString(R.string.common_selection_select),
          ),
          OptionItem(R.drawable.ic_pencil, getString(R.string.edit)),
          OptionItem(R.drawable.ic_delete, getString(R.string.delete)),
        ),
    ) { position, _ ->
      when (position) {
        0 -> toggleSelection(project, true)
        1 -> showEditProjectSheet(project.id)
        2 -> {
          requireContext().showConfirmDialog(
            title = getString(R.string.delete),
            message =
              getString(R.string.proj_delete_confirmation_msg, project.name),
          ) { _, _ ->
            viewModel.doIntent(ProjectListIntent.DeleteProject(project.id))
          }
        }
      }
    }
  }

  private fun showEditProjectSheet(projectId: Long = 0) {
    ProjectEditSheetFragment.newInstance(projectId)
      .show(childFragmentManager, null)
  }

  override fun onSelect() {}

  override fun onUnselect() = finishSelectionActionMode()

  private fun startSelectionActionMode() {
    if (selectionActionMode == null) {
      (requireActivity() as? AppCompatActivity)
        ?.startSupportActionMode(
          object : ActionMode.Callback {

            override fun onCreateActionMode(
              actionMode: ActionMode,
              menu: Menu,
            ): Boolean {
              menu.add(0, 0, 0, R.string.delete).setIcon(R.drawable.ic_delete)
              return true
            }

            override fun onPrepareActionMode(
              actionMode: ActionMode,
              menu: Menu,
            ): Boolean {
              return true
            }

            override fun onActionItemClicked(
              actionMode: ActionMode,
              item: MenuItem,
            ): Boolean {
              when (item.itemId) {
                0 -> {
                  requireContext().showConfirmDialog(
                    title = getString(R.string.delete),
                    message =
                      getString(
                        R.string.proj_delete_confirmation_plural_msg,
                        viewModel.selection.map { it.name }.toString(),
                      ),
                  ) { _, _ ->
                    viewModel.doIntent(ProjectListIntent.DeleteProject(-1))
                  }
                }
              }
              return true
            }

            override fun onDestroyActionMode(actionMode: ActionMode) {
              viewModel.doIntent(ProjectListIntent.UpdateSelection(emptyList()))
            }
          }
        )
        .also { selectionActionMode = it }
    }
  }

  private fun finishSelectionActionMode() {
    selectionActionMode?.finish()
    selectionActionMode = null
  }

  private fun toggleSelection(
    project: Project,
    select: Boolean = !selectionTracker.isSelected(project.id),
  ) {
    val index = projectsAdapter.currentList.indexOf(project)
    if (select) {
      selectionTracker.select(project.id)
    } else {
      selectionTracker.deselect(project.id)
    }
    projectsAdapter.notifyItemChanged(index)
  }
}
