import { useCallback, useMemo, useRef } from 'react'
import type {
  Layer,
  MapViewState,
  ViewStateChangeParameters,
} from '@deck.gl/core'
import { DeckGLComponent } from './deckgl'
import type { DeckGLComponentProps, MapComponentProps } from '../types'
import { useMapStore } from '../providers/map/provider'
import { useLayers } from '../use-layers'
import { sortLayers } from '../utils/sort-layers'
import type { DeckGLProps } from '@deck.gl/react'

const ZOOM_DEBOUNCE_MS = 200

/**
 * Core React component for creating interactive maps with deck.gl integration, layer management, basemap configuration, and view state synchronization.
 *
 * @remarks
 * Must be used within a {@link MapsProvider} or {@link MapProvider}. The `id` prop must match
 * a map configuration passed to the provider. Layers, basemap, and view state are managed
 * automatically through the map store.
 *
 * @example
 * ```tsx
 * <MapsProvider maps={[{ id: 'main-map', basemap: CartoBasemaps.voyager, viewState: { latitude: 40.7, longitude: -74, zoom: 10 } }]}>
 *   <MapComponent id="main-map" />
 * </MapsProvider>
 * ```
 */
export function MapComponent({
  id,
  onLoad,
  onIdle,
  onViewStateChange,
  ...props
}: MapComponentProps) {
  const basemap = useMapStore((state) => state.maps[id]!.basemap)
  const instanceRef = useMapStore((state) => state.maps[id]!.instanceRef)
  const overlayRef = useMapStore((state) => state.maps[id]!.overlayRef)
  const timerRef = useRef<NodeJS.Timeout | null>(null)
  const viewState = useMapStore((state) => state.viewStates[id])
  const controller = useMapStore((state) => state.maps[id]!.controller)
  const getDeckInstance = useMapStore((state) => state.getDeckInstance)
  const setViewState = useMapStore((state) => state.setViewState)
  const setLoad = useMapStore((state) => state.setLoad)
  const setLayersLoad = useMapStore((state) => state.setLayersLoad)
  const setIdle = useMapStore((state) => state.setIdle)
  const updateViewPort = useMapStore((state) => state.updateViewPort)

  const layers = useLayers({ mapId: id })
  const computedLayers = useMemo(() => {
    return sortLayers(layers)
  }, [layers])

  const handleViewStateChange = useCallback(
    (newViewState: ViewStateChangeParameters<MapViewState>) => {
      const callback = setViewState

      if (timerRef.current) {
        clearTimeout(timerRef.current)
      }
      timerRef.current = setTimeout(() => {
        updateViewPort(id)
      }, ZOOM_DEBOUNCE_MS)

      if (onViewStateChange) {
        onViewStateChange(newViewState, callback)
        return
      }

      callback(id, newViewState.viewState)
    },
    [id, onViewStateChange, setViewState, updateViewPort],
  )

  const handleLoad = useCallback(
    (value: Parameters<NonNullable<DeckGLComponentProps['onLoad']>>[0]) => {
      setLoad(id, value)
      onLoad?.(value)
      updateViewPort(id)
    },
    [id, onLoad, setLoad, updateViewPort],
  )

  const handleIdle = useCallback(() => {
    setIdle(id, true)
    onIdle?.()
  }, [id, onIdle, setIdle])

  const handleAfterRender = useCallback(
    (
      context: Parameters<
        NonNullable<DeckGLComponentProps['onAfterRender']>
      >[0],
    ) => {
      const layers = getDeckInstance(id)?.props.layers
      const areLayersLoaded = !!layers?.every(
        (layer) => (layer as Layer<never>)?.isLoaded,
      )
      setLayersLoad(id, areLayersLoaded)
      props.onAfterRender?.(context)
    },
    [getDeckInstance, id, props, setLayersLoad],
  )

  // NOTE: destructuring here to keep the reference to the props object outside of the useCallback
  const _onDragEnd = props.onDragEnd
  const handleDragEnd = useCallback(
    (...args: Parameters<NonNullable<DeckGLProps['onDragEnd']>>) => {
      updateViewPort(id)
      if (_onDragEnd) {
        _onDragEnd(...args)
      }
    },
    [id, updateViewPort, _onDragEnd],
  )

  return (
    <DeckGLComponent
      {...props}
      controller={controller}
      id={id}
      layers={computedLayers}
      instanceRef={instanceRef}
      overlayRef={overlayRef}
      basemap={basemap}
      initialViewState={viewState}
      onLoad={handleLoad}
      onIdle={handleIdle}
      onViewStateChange={handleViewStateChange}
      onAfterRender={handleAfterRender}
      onDragEnd={handleDragEnd}
    />
  )
}
