import { useHead } from '@unhead/vue'
import { computed, getCurrentInstance, reactive, ref, shallowRef, watch } from 'vue'
import { useRouter } from 'vue-router'
import setups from '#slidev/setups/root'
import { createFixedClicks } from '../composables/useClicks'
import { useEmbeddedControl } from '../composables/useEmbeddedCtrl'
import { useNav } from '../composables/useNav'
import { usePrintStyles } from '../composables/usePrintStyles'
import { injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionSlidevContext, TRUST_ORIGINS } from '../constants'
import { configs, slidesTitle } from '../env'
import { getSlidePath } from '../logic/slides'
import { makeId } from '../logic/utils'
import { hmrSkipTransition, syncDirections } from '../state'
import { initDrawingState } from '../state/drawings'
import { initSharedState, onPatch, patch } from '../state/shared'

export default function setupRoot() {
  const app = getCurrentInstance()!.appContext.app

  const context = reactive({
    nav: useNav(),
    configs,
    themeConfigs: computed(() => configs.themeConfig),
  })
  app.provide(injectionRenderContext, ref('none' as const))
  app.provide(injectionSlidevContext, context)
  app.provide(injectionCurrentPage, computed(() => context.nav.currentSlideNo))
  app.provide(injectionClicksContext, shallowRef(createFixedClicks()))

  // allows controls from postMessages
  if (__DEV__) {
    // @ts-expect-error expose global
    window.__slidev__ = context
    useEmbeddedControl()
  }

  // User Setups
  for (const setup of setups)
    setup()

  const {
    clicksContext,
    currentSlideNo,
    hasPrimarySlide,
    isNotesViewer,
    isPresenter,
    isPrintMode,
  } = useNav()

  useHead({
    title: slidesTitle,
    htmlAttrs: configs.htmlAttrs,
  })

  usePrintStyles()

  initSharedState(`${slidesTitle} - shared`)
  initDrawingState(`${slidesTitle} - drawings`)

  const id = `${location.origin}_${makeId()}`
  const syncType = computed(() => isPresenter.value ? 'presenter' : 'viewer')

  // update shared state
  function updateSharedState() {
    const shouldSend = isPresenter.value
      ? syncDirections.value.presenterSend
      : syncDirections.value.viewerSend

    if (!shouldSend)
      return
    if (isNotesViewer.value || isPrintMode.value)
      return
    // we allow Presenter mode, or Viewer mode from trusted origins to update the shared state
    if (!isPresenter.value && !TRUST_ORIGINS.includes(location.host.split(':')[0]))
      return

    patch('page', +currentSlideNo.value)
    patch('clicks', clicksContext.value.current)
    patch('clicksTotal', clicksContext.value.total)
    patch('lastUpdate', {
      id,
      type: syncType.value,
      time: Date.now(),
    })
  }
  const router = useRouter()
  router.afterEach(updateSharedState)
  watch(clicksContext, updateSharedState)

  onPatch((state) => {
    const shouldReceive = isPresenter.value
      ? syncDirections.value.presenterReceive
      : syncDirections.value.viewerReceive
    if (!shouldReceive)
      return
    if (!hasPrimarySlide.value || isPrintMode.value)
      return
    if (state.lastUpdate?.type === syncType.value)
      return
    if ((+state.page === +currentSlideNo.value && +clicksContext.value.current === +state.clicks))
      return
    // if (state.lastUpdate?.type === 'presenter') {
    hmrSkipTransition.value = false
    router.replace({
      path: getSlidePath(state.page, isPresenter.value),
      query: {
        ...router.currentRoute.value.query,
        clicks: state.clicks || 0,
      },
    })
    // }
  })
}
