import type { AppTheme } from './theme'

type ViewTransition = {
  finished: Promise<void>
  ready: Promise<void>
  skipTransition: () => void
}

type ViewTransitionDocument = Document & {
  startViewTransition?: (callback: () => void) => ViewTransition
}

type AnimationOptionsWithPseudoElement = KeyframeAnimationOptions & {
  pseudoElement?: string
}

type ThemeTransitionOptions = {
  source?: Element | null
  theme: AppTheme
  updateTheme: (theme: AppTheme) => void
}

const THEME_TRANSITION_CLASS = 'theme-transition-no-css-transitions'
const THEME_TRANSITION_DURATION = 620
const THEME_TRANSITION_EASING = 'cubic-bezier(0.22, 1, 0.36, 1)'

let activeTransition: ViewTransition | null = null

function shouldReduceMotion() {
  return window.matchMedia('(prefers-reduced-motion: reduce)').matches
}

function getTransitionGeometry(source?: Element | null) {
  const rect = source?.getBoundingClientRect()
  const x = rect ? rect.left + rect.width / 2 : window.innerWidth - 48
  const y = rect ? rect.top + rect.height / 2 : 48
  const endRadius = Math.hypot(
    Math.max(x, window.innerWidth - x),
    Math.max(y, window.innerHeight - y),
  )

  return {
    endRadius,
    x,
    y,
  }
}

export function updateThemeWithTransition({
  source,
  theme,
  updateTheme,
}: ThemeTransitionOptions) {
  if (typeof document === 'undefined' || typeof window === 'undefined') {
    updateTheme(theme)
    return
  }

  const transitionDocument = document as ViewTransitionDocument

  if (!transitionDocument.startViewTransition || shouldReduceMotion()) {
    updateTheme(theme)
    return
  }

  activeTransition?.skipTransition()

  const root = document.documentElement
  const { endRadius, x, y } = getTransitionGeometry(source)

  root.classList.add(THEME_TRANSITION_CLASS)

  const transition = transitionDocument.startViewTransition(() => {
    updateTheme(theme)
  })

  activeTransition = transition

  transition.ready
    .then(() => {
      root.classList.remove(THEME_TRANSITION_CLASS)

      root.animate(
        [
          {
            clipPath: `circle(0px at ${x}px ${y}px)`,
          },
          {
            clipPath: `circle(${endRadius}px at ${x}px ${y}px)`,
          },
        ],
        {
          duration: THEME_TRANSITION_DURATION,
          easing: THEME_TRANSITION_EASING,
          pseudoElement: '::view-transition-new(root)',
        } as AnimationOptionsWithPseudoElement,
      )
    })
    .catch(() => {
      root.classList.remove(THEME_TRANSITION_CLASS)
    })

  transition.finished
    .catch(() => undefined)
    .finally(() => {
      root.classList.remove(THEME_TRANSITION_CLASS)

      if (activeTransition === transition) {
        activeTransition = null
      }
    })
}
