import { useCallback, useEffect, useRef } from 'react'
import { useLayerStore } from '../../providers/layers/store'
import { useMapStore } from '../../providers/map/provider'
import { DEFAULT_PROPS, MAP_MEASURE_LAYER_ID } from './const'
import { EditType } from './../const'
import { getMeasurementToolsLayer } from './layer'
import { MeasurementToolsControl } from './measurement-tools-control'
import type {
  MeasurementToolsLayerEditProps,
  MeasurementToolsProps,
} from './types'
import { useMeasurementTools } from './use-measurement-tools'
import { useShallow } from 'zustand/shallow'
import type { LayerProps } from '@deck.gl/core'
import { useEventManager } from '../../use-event-manager'

const EXCEPTED_LAYER_IDS = [MAP_MEASURE_LAYER_ID]

/**
 * Provides interactive distance, area, and buffer measurement capabilities on a map.
 *
 * @remarks
 * Manages the measurement layer lifecycle, disables double-click zoom while active,
 * and coordinates with `MeasurementToolsProvider` for state. Attach `MeasurementTools.Controls`
 * as a child to expose measurement actions via render props.
 *
 * @example
 * ```tsx
 * <MeasurementTools
 *   mapId="main-map"
 *   defaultProps={{ styles: { colors: { editing: [0, 123, 255], finished: [40, 167, 69], point: [255, 255, 255] } } }}
 * />
 * ```
 */
export function MeasurementTools({
  children,
  mapId,
  defaultProps: { styles = DEFAULT_PROPS.styles } = {
    styles: DEFAULT_PROPS.styles,
  },
  layerProps,
  textOptions,
}: MeasurementToolsProps & {
  children?: React.ReactNode
}) {
  const {
    setDistance,
    enabled,
    finished,
    setFinished,
    locale,
    setPositions,
    unit,
    modeInstance,
  } = useMeasurementTools({ mapId })

  const setLayer = useLayerStore((state) => state.setLayer)
  const getController = useMapStore((state) => state.getController)
  const setController = useMapStore((state) => state.setController)
  const addLayers = useMapStore((state) => state.addLayers)
  const removeLayers = useMapStore((state) => state.removeLayers)
  const mapLayers = useMapStore(
    useShallow((state) =>
      state.maps[mapId]?.layers.filter((l) => !EXCEPTED_LAYER_IDS.includes(l)),
    ),
  )
  const mapLayersVisibilityRef = useRef<Record<string, LayerProps['pickable']>>(
    {},
  )

  useEventManager({ mapId, layerId: MAP_MEASURE_LAYER_ID })

  useEffect(() => {
    const controller = getController(mapId)

    if (enabled) {
      addLayers(mapId, [MAP_MEASURE_LAYER_ID])

      setController(mapId, {
        doubleClickZoom: false,
      })
    }

    return () => {
      removeLayers(mapId, [MAP_MEASURE_LAYER_ID])
      setController(mapId, controller)
    }
  }, [addLayers, enabled, getController, mapId, removeLayers, setController])

  useEffect(() => {
    mapLayers?.forEach((layerId) => {
      const currentLayer = useLayerStore.getState().getLayer(layerId)

      if (!currentLayer) return

      if (enabled) {
        mapLayersVisibilityRef.current[layerId] = currentLayer.props?.pickable
      }

      const pickable =
        !enabled &&
        (mapLayersVisibilityRef.current[layerId] ??
          currentLayer.props?.pickable)

      setLayer(layerId, {
        props: {
          pickable,
        },
      })
    })
  }, [enabled, mapId, mapLayers, setLayer])

  const onEdit = useCallback(
    ({ editType, editContext }: MeasurementToolsLayerEditProps) => {
      if (editType === EditType.cancel) {
        modeInstance!.resetClickSequence()
        return
      }

      if (editType === EditType.addPosition) {
        setDistance(editContext.distance)
      }

      setFinished(!!editContext.finished)

      const positions = editContext.positions

      if (positions?.length) {
        setPositions(positions)
      }
    },
    [modeInstance, setDistance, setFinished, setPositions],
  )

  useEffect(() => {
    const guideColor = finished ? styles.colors.finished : styles.colors.editing
    const pointColor = finished ? styles.colors.finished : styles.colors.point

    const layer = getMeasurementToolsLayer({
      layerProps: {
        ...layerProps,
        pickable: layerProps?.pickable ?? !finished,
      },
      enabled,
      guideColor,
      pointColor,
      locale,
      onEdit,
      unit,
      mode: modeInstance!,
      textOptions: textOptions,
    })

    setLayer(MAP_MEASURE_LAYER_ID, layer)
  }, [
    finished,
    modeInstance,
    layerProps,
    locale,
    onEdit,
    setLayer,
    styles.colors.editing,
    styles.colors.finished,
    styles.colors.point,
    textOptions,
    unit,
    enabled,
  ])

  return children
}

MeasurementTools.Controls = MeasurementToolsControl
