import {createContext, useContext, useEffect, useMemo, useState} from 'react'
import {
  measure,
  type MeasuredDimensions,
  runOnJS,
  runOnUI,
} from 'react-native-reanimated'
import {nanoid} from 'nanoid/non-secure'

import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {useHotkeysContext} from '#/lib/hotkeys'
import {type ImageSource} from '#/components/Lightbox/types'

export type LightboxMetricsContext = {
  layout: 'single' | 'grid' | 'carousel'
  postUri: string
  postAuthorDid: string
  feedDescriptor?: string
}

export type Lightbox = {
  id: string
  images: ImageSource[]
  index: number
  // Set for post photo embeds so the lightbox can emit post:photoEmbed:lightboxSwipe.
  // Left unset for non-post contexts (e.g. profile avatar/banner lightbox).
  metricsContext?: LightboxMetricsContext
}

const LightboxContext = createContext<{
  activeLightbox: Lightbox | null
}>({
  activeLightbox: null,
})
LightboxContext.displayName = 'LightboxContext'

const LightboxControlContext = createContext<{
  openLightbox: (lightbox: Omit<Lightbox, 'id'>) => void
  closeLightbox: () => boolean
}>({
  openLightbox: () => {},
  closeLightbox: () => false,
})
LightboxControlContext.displayName = 'LightboxControlContext'

export function Provider({children}: React.PropsWithChildren<{}>) {
  const [activeLightbox, setActiveLightbox] = useState<Lightbox | null>(null)
  const {disableScope, enableScope} = useHotkeysContext()

  useEffect(() => {
    if (activeLightbox) {
      disableScope('global')
    } else {
      enableScope('global')
    }
  }, [activeLightbox, disableScope, enableScope])

  const doOpen = useNonReactiveCallback((lightbox: Omit<Lightbox, 'id'>) => {
    setActiveLightbox(prevLightbox => {
      if (prevLightbox) {
        // Ignore duplicate open requests. If it's already open,
        // the user has to explicitly close the previous one first.
        return prevLightbox
      } else {
        return {...lightbox, id: nanoid()}
      }
    })
  })

  const openLightbox = useNonReactiveCallback(
    (lightbox: Omit<Lightbox, 'id'>) => {
      const thumbRef = lightbox.images[lightbox.index]?.thumbRef
      if (thumbRef) {
        // Measure the tapped image on the UI thread, then open with
        // the rect baked in so it's available from the first render.
        // Only the rect (plain data) goes through runOnJS — AnimatedRef
        // objects can't survive serialization across threads.
        const openWithRect = (rect: MeasuredDimensions | null) => {
          doOpen({
            ...lightbox,
            images: lightbox.images.map((img, i) =>
              i === lightbox.index ? {...img, thumbRect: rect} : img,
            ),
          })
        }
        runOnUI(() => {
          'worklet'
          const rect = measure(thumbRef)
          runOnJS(openWithRect)(rect)
        })()
      } else {
        doOpen(lightbox)
      }
    },
  )

  const closeLightbox = useNonReactiveCallback(() => {
    let wasActive = !!activeLightbox
    setActiveLightbox(null)
    return wasActive
  })

  const state = useMemo(
    () => ({
      activeLightbox,
    }),
    [activeLightbox],
  )

  const methods = useMemo(
    () => ({
      openLightbox,
      closeLightbox,
    }),
    [openLightbox, closeLightbox],
  )

  return (
    <LightboxContext.Provider value={state}>
      <LightboxControlContext.Provider value={methods}>
        {children}
      </LightboxControlContext.Provider>
    </LightboxContext.Provider>
  )
}

export function useLightbox() {
  return useContext(LightboxContext)
}

export function useLightboxControls() {
  return useContext(LightboxControlContext)
}
