import { useCallback, useEffect, useEffectEvent, useState } from 'react'
import { CartoBasemaps, MAPS_BASEMAP, MapsTypes } from '../const'
import type { DeckGLComponentProps } from '../types'
import { getCursorDefault, getTooltipDefault } from '../helpers'
import { MapLibre } from '../maps/map-libre'
import { GoogleMaps } from '../maps/google-maps'

/**
 * High-performance wrapper around deck.gl that provides WebGL-powered geospatial visualization with support for CARTO basemaps, Google Maps, and MapLibre.
 *
 * @remarks
 * This component operates independently and does not require a {@link MapProvider}. It manages its own
 * state and automatically selects between Google Maps and MapLibre renderers based on the basemap type.
 * For applications requiring CARTO's state management system, use {@link MapComponent} instead.
 *
 * @example
 * ```tsx
 * <DeckGLComponent
 *   id="my-map"
 *   basemap={CartoBasemaps.positron}
 *   initialViewState={{ latitude: 40.7, longitude: -74, zoom: 10 }}
 *   layers={[myLayer]}
 * />
 * ```
 */
export function DeckGLComponent({
  instanceRef,
  overlayRef,
  basemap = CartoBasemaps.positron,
  controller = true,
  id = 'deck-gl-map',
  layers = [],
  gmapsProps,
  getCursor = getCursorDefault,
  getTooltip = getTooltipDefault,
  onLoad,
  onViewStateChange = () => null,
  ...rest
}: DeckGLComponentProps) {
  const [isLoaded, setLoaded] = useState([false, false])

  const { url: mapStyle, type } = MAPS_BASEMAP[basemap]
  const isGmaps = type === MapsTypes.googleMaps

  const handleLoad = useCallback(
    (e: { type: 'deckgl' | 'load'; value: boolean }) => {
      const index = e.type === 'deckgl' ? 0 : 1
      setLoaded((state) =>
        state.map((loaded, i) => (i === index ? e.value : loaded)),
      )
    },
    [],
  )

  useEffect(() => {
    return () => {
      handleLoad({
        type: 'load',
        value: false,
      })
      handleLoad({
        type: 'deckgl',
        value: false,
      })
    }
  }, [handleLoad])

  // Notify the consumer when the combined load state settles. `onLoad` is
  // read through an Effect Event so a parent passing an inline callback does
  // not re-fire the notification on every render — it fires only when the
  // actual load state changes.
  const notifyLoad = useEffectEvent((isAllLoaded: boolean): void => {
    onLoad?.(isAllLoaded)
  })

  useEffect(() => {
    notifyLoad(isLoaded.every((loaded) => loaded))
  }, [isLoaded])

  if (isGmaps) {
    return (
      <GoogleMaps
        instanceRef={instanceRef}
        overlayRef={overlayRef}
        controller={controller}
        id={id}
        basemap={basemap}
        layers={layers}
        gmapsProps={gmapsProps}
        getCursor={getCursor}
        getTooltip={getTooltip}
        onLoad={handleLoad}
        onViewStateChange={onViewStateChange}
        {...rest}
      />
    )
  }

  return (
    <MapLibre
      instanceRef={instanceRef}
      overlayRef={overlayRef}
      controller={controller}
      id={id}
      layers={layers}
      mapStyle={mapStyle}
      getCursor={getCursor}
      getTooltip={getTooltip}
      onLoad={handleLoad}
      onViewStateChange={onViewStateChange}
      {...rest}
    />
  )
}
