package org.thoughtcrime.securesms.stories.viewer

import android.graphics.RenderEffect
import android.graphics.Shader
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.core.app.ActivityCompat
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.viewpager2.widget.ViewPager2
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.getParcelableArrayListCompat
import org.signal.core.util.getParcelableCompat
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.spoiler.SpoilerAnnotation
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stories.StoryViewerArgs
import org.thoughtcrime.securesms.stories.viewer.first.StoryFirstTimeNavigationFragment
import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageArgs
import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageFragment
import org.thoughtcrime.securesms.stories.viewer.reply.StoriesSharedElementCrossFaderView

/**
 * Fragment which manages a vertical pager fragment of stories.
 */
class StoryViewerFragment :
  Fragment(R.layout.stories_viewer_fragment),
  StoryViewerPageFragment.Callback,
  StoriesSharedElementCrossFaderView.Callback {

  private val onPageChanged = OnPageChanged()

  private lateinit var storyPager: ViewPager2

  private val viewModel: StoryViewerViewModel by viewModels(
    factoryProducer = {
      StoryViewerViewModel.Factory(storyViewerArgs, StoryViewerRepository())
    }
  )

  private val lifecycleDisposable = LifecycleDisposable()

  private val storyViewerArgs: StoryViewerArgs by lazy { requireArguments().getParcelableCompat(ARGS, StoryViewerArgs::class.java)!! }

  private lateinit var storyCrossfader: StoriesSharedElementCrossFaderView

  private var pagerOnPageSelectedLock: Boolean = false

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    storyCrossfader = view.findViewById(R.id.story_content_crossfader)
    storyPager = view.findViewById(R.id.story_item_pager)

    ViewCompat.setTransitionName(storyCrossfader, "story")
    storyCrossfader.callback = this

    SpoilerAnnotation.resetRevealedSpoilers()

    val adapter = StoryViewerPagerAdapter(
      this,
      StoryViewerPageArgs(
        recipientId = Recipient.UNKNOWN.id,
        initialStoryId = storyViewerArgs.storyId,
        isJumpForwardToUnviewed = storyViewerArgs.isJumpToUnviewed,
        isOutgoingOnly = storyViewerArgs.isFromMyStories,
        source = when {
          storyViewerArgs.isFromInfoContextMenuAction -> StoryViewerPageArgs.Source.INFO_CONTEXT
          storyViewerArgs.isFromNotification -> StoryViewerPageArgs.Source.NOTIFICATION
          else -> StoryViewerPageArgs.Source.UNKNOWN
        },
        groupReplyStartPosition = storyViewerArgs.groupReplyStartPosition
      )
    )

    storyPager.adapter = adapter
    storyPager.overScrollMode = ViewPager2.OVER_SCROLL_NEVER

    lifecycleDisposable += viewModel.allowParentScrolling.observeOn(AndroidSchedulers.mainThread()).subscribe {
      storyPager.isUserInputEnabled = it
    }

    storyPager.offscreenPageLimit = 1

    lifecycleDisposable.bindTo(viewLifecycleOwner)
    lifecycleDisposable += viewModel.state.observeOn(AndroidSchedulers.mainThread()).subscribe { state ->
      if (state.noPosts) {
        onBackPressed(adapter.getRecipientId(storyPager.currentItem))
      }

      adapter.setPages(state.pages)
      if (state.pages.isNotEmpty() && storyPager.currentItem != state.page) {
        pagerOnPageSelectedLock = true
        storyPager.isUserInputEnabled = false
        storyPager.setCurrentItem(state.page, state.previousPage > -1)
        pagerOnPageSelectedLock = false

        if (state.page >= state.pages.size) {
          onBackPressed(adapter.getRecipientId(storyPager.currentItem))
          lifecycleDisposable.clear()
        }
      }

      when (state.crossfadeSource) {
        is StoryViewerState.CrossfadeSource.TextModel -> storyCrossfader.setSourceView(state.crossfadeSource.storyTextPostModel)
        is StoryViewerState.CrossfadeSource.ImageUri -> storyCrossfader.setSourceView(state.crossfadeSource.imageUri, state.crossfadeSource.imageBlur)
        StoryViewerState.CrossfadeSource.None -> Unit
      }

      if (state.crossfadeTarget is StoryViewerState.CrossfadeTarget.Record) {
        storyCrossfader.setTargetView(state.crossfadeTarget.messageRecord)
        requireActivity().supportStartPostponedEnterTransition()
      }

      if (state.skipCrossfade) {
        viewModel.setCrossfaderIsReady(true)
      }
    }

    lifecycleDisposable += viewModel.loadState.subscribe {
      if (it.isReady()) {
        Log.d(TAG, "Content is ready, clearing crossfader.")
        storyCrossfader.alpha = 0f
      }
    }

    if (savedInstanceState != null && savedInstanceState.containsKey(HIDDEN)) {
      val ids: List<RecipientId> = savedInstanceState.getParcelableArrayListCompat(HIDDEN, RecipientId::class.java)!!
      viewModel.addHiddenAndRefresh(ids.toSet())
    } else {
      viewModel.refresh()

      if (!SignalStore.story.userHasSeenFirstNavView && !SignalStore.story.userHasViewedOnboardingStory) {
        StoryFirstTimeNavigationFragment().show(childFragmentManager, null)
      }
    }

    if (Build.VERSION.SDK_INT >= 31) {
      lifecycleDisposable += viewModel.isFirstTimeNavigationShowing.subscribe {
        if (it) {
          requireView().rootView.setRenderEffect(RenderEffect.createBlurEffect(100f, 100f, Shader.TileMode.CLAMP))
        } else {
          requireView().rootView.setRenderEffect(null)
        }
      }
    }

    requireActivity().onBackPressedDispatcher.addCallback(
      viewLifecycleOwner,
      object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
          onBackPressed(adapter.getRecipientId(storyPager.currentItem))
        }
      }
    )
  }

  private fun onBackPressed(currentId: RecipientId) {
    if (viewModel.getInitialRecipientId() != currentId) {
      requireActivity().finish()
    } else {
      ActivityCompat.finishAfterTransition(requireActivity())
    }
  }

  override fun onSaveInstanceState(outState: Bundle) {
    outState.putParcelableArrayList(HIDDEN, ArrayList(viewModel.getHidden()))
  }

  override fun onResume() {
    super.onResume()
    viewModel.setIsScrolling(false)
    storyPager.registerOnPageChangeCallback(onPageChanged)
  }

  override fun onPause() {
    super.onPause()
    viewModel.setIsScrolling(false)
    storyPager.unregisterOnPageChangeCallback(onPageChanged)
  }

  override fun onGoToPreviousStory(recipientId: RecipientId) {
    viewModel.onGoToPrevious(recipientId)
  }

  override fun onFinishedPosts(recipientId: RecipientId) {
    viewModel.onGoToNext(recipientId)
  }

  override fun onStoryHidden(recipientId: RecipientId) {
    viewModel.addHiddenAndRefresh(setOf(recipientId))
  }

  override fun onContentTranslation(x: Float, y: Float) {
    storyCrossfader.translationX = x
    storyCrossfader.translationY = y
  }

  override fun onReadyToAnimate() {
  }

  override fun onAnimationStarted() {
    viewModel.setCrossfaderIsReady(false)
  }

  override fun onAnimationFinished() {
    viewModel.setCrossfaderIsReady(true)
  }

  inner class OnPageChanged : ViewPager2.OnPageChangeCallback() {
    override fun onPageSelected(position: Int) {
      if (!pagerOnPageSelectedLock) {
        viewModel.setSelectedPage(position)
      }
    }

    override fun onPageScrollStateChanged(state: Int) {
      viewModel.setIsScrolling(state == ViewPager2.SCROLL_STATE_DRAGGING)
      if (state == ViewPager2.SCROLL_STATE_IDLE) {
        storyPager.isUserInputEnabled = true
      }
    }
  }

  companion object {
    private val TAG = Log.tag(StoryViewerFragment::class.java)

    private const val ARGS = "args"
    private const val HIDDEN = "hidden"

    fun create(storyViewerArgs: StoryViewerArgs): Fragment {
      return StoryViewerFragment().apply {
        arguments = Bundle().apply {
          putParcelable(ARGS, storyViewerArgs)
        }
      }
    }
  }
}
