import { Paper, IconButton } from '@mui/material'
import {
  MyLocationOutlined,
  LocationDisabledOutlined,
} from '@mui/icons-material'
import type { GeolocationControlProps } from './types'
import {
  useCallback,
  useEffect,
  useEffectEvent,
  useReducer,
  useRef,
  type JSX,
} from 'react'
import { ariaLabel, tooltipLabelsDefault } from './const'
import { Tooltip } from '../tooltip/tooltip'

/**
 * Provides a seamless interface for accessing device location through the browser's Geolocation API, with permission handling, continuous tracking, and error management.
 *
 * @example
 * ```tsx
 * <GeolocationControls
 *   onChange={(coords) => console.log(coords.latitude, coords.longitude)}
 *   onError={(error) => console.error(error.message)}
 * />
 * ```
 */
export function GeolocationControls({
  disabled,
  labels,
  PaperProps,
  TooltipProps,
  watch,
  onChange,
  onError,
}: GeolocationControlProps): JSX.Element {
  const watchRef = useRef<number>(null)
  const hasGeolocation = 'geolocation' in navigator

  const [allowed, dispatch] = useReducer(
    (_: boolean, action: 'granted' | 'denied') => action === 'granted',
    hasGeolocation,
  )

  // Read the latest `onError` without making the permission-query effect
  // re-run (and re-register the `result.onchange` permission listener)
  // whenever the consumer passes a fresh callback identity.
  const notifyError = useEffectEvent(
    (error: Parameters<PositionErrorCallback>[0]): void => {
      onError?.(error)
    },
  )

  useEffect(() => {
    if (hasGeolocation) {
      navigator.permissions
        .query({ name: 'geolocation' })
        .then((result) => {
          dispatch(result.state === 'granted' ? 'granted' : 'denied')
          result.onchange = () => {
            dispatch(result.state === 'granted' ? 'granted' : 'denied')
          }
        })
        .catch((error: Parameters<PositionErrorCallback>[0]) => {
          dispatch('denied')
          notifyError(error)
        })
    }

    return () => {
      if (watchRef.current) {
        navigator.geolocation.clearWatch(watchRef.current)
      }
    }
  }, [hasGeolocation])

  const success = useCallback(
    (position: Parameters<PositionCallback>[0]) => {
      onChange(position.coords)
    },
    [onChange],
  )

  const error = useCallback(
    (error: Parameters<PositionErrorCallback>[0]) => {
      dispatch('denied')
      onError?.(error)
    },
    [onError],
  )

  const handleClick = useCallback(() => {
    if (watchRef.current) {
      navigator.geolocation.clearWatch(watchRef.current)
    }

    if (watch) {
      watchRef.current = navigator.geolocation.watchPosition(success, error)
      return
    }

    navigator.geolocation.getCurrentPosition(success, error)
  }, [watch, success, error])

  const tooltipLabels = labels?.tooltip ?? tooltipLabelsDefault

  return (
    <Paper {...PaperProps}>
      <Tooltip
        arrow
        placement='right'
        title={tooltipLabels[allowed ? 'enabled' : 'disabled']}
        {...TooltipProps}
      >
        <IconButton
          onClick={handleClick}
          aria-label={labels?.ariaLabel ?? ariaLabel}
          disabled={!hasGeolocation || disabled}
        >
          {allowed ? <MyLocationOutlined /> : <LocationDisabledOutlined />}
        </IconButton>
      </Tooltip>
    </Paper>
  )
}
