import { ArrowDropDown, Close } from '@mui/icons-material'
import {
  Box,
  Button,
  Dialog,
  DialogActions,
  DialogContent,
  DialogTitle,
  Divider,
  FormControl,
  FormControlLabel,
  IconButton,
  ListItemIcon,
  ListItemText,
  Menu,
  MenuItem,
  Paper,
  Radio,
  RadioGroup,
  SvgIcon,
  ToggleButton,
  ToggleButtonGroup,
  Typography,
} from '@mui/material'
import {
  useMemo,
  useState,
  type FormEvent,
  type JSX,
  type MouseEvent,
  type PropsWithChildren,
} from 'react'
import { styles } from './styles'
import deepmerge from 'deepmerge'
import type { MeasurementToolsComponentProps } from './types'
import type { PickDeep, ValueOf } from 'type-fest'
import {
  DEFAULT_MEASUREMENT_TOOLS_LABELS,
  DEFAULT_MEASUREMENT_TOOLS_UNITS_MAPPING,
  DEFAULT_MEASUREMENT_TOOLS_MODES_MAPPING,
} from './const'
import type {
  MeasurementSystem,
  MeasurementUnit,
  MeasurementUnitImperialDistance,
  MeasurementUnitMetricDistance,
  MeasurementUnitOption,
  MeasureUnitMapping,
} from '../types'
import { Tooltip } from '../tooltip/tooltip'

const EMPTY_PAPER_PROPS: NonNullable<
  MeasurementToolsComponentProps['PaperProps']
> = {}

/**
 * Provides a measurement interface for calculating distances, areas, and buffers on maps with support for metric and imperial unit systems and real-time unit conversion.
 *
 * @example
 * ```tsx
 * <MeasurementToolsUI
 *   enabled={enabled}
 *   value={measurementValue}
 *   modes={DEFAULT_MEASUREMENT_TOOLS_MODES}
 *   units={DEFAULT_MEASUREMENT_TOOLS_UNITS}
 *   modesMapping={DEFAULT_MEASUREMENT_TOOLS_MODES_MAPPING}
 *   unitsMapping={DEFAULT_MEASUREMENT_TOOLS_UNITS_MAPPING}
 *   modeSelected={mode}
 *   unitSelected={unit}
 *   onActionToggle={setEnabled}
 *   onChangeMode={setMode}
 *   onChangeUnit={setUnit}
 * />
 * ```
 */
export function MeasurementToolsUI({
  enabled,
  actionProps,
  labels = DEFAULT_MEASUREMENT_TOOLS_LABELS,
  modes,
  modesMapping = DEFAULT_MEASUREMENT_TOOLS_MODES_MAPPING,
  unitsMapping = DEFAULT_MEASUREMENT_TOOLS_UNITS_MAPPING,
  modeSelected,
  PaperProps: { sx, ...PaperProps } = EMPTY_PAPER_PROPS,
  units,
  unitSelected,
  onActionToggle,
  onChangeMode,
  onChangeUnit,
}: MeasurementToolsComponentProps): JSX.Element {
  const handleToggle: MeasurementToolsComponentProps['onActionToggle'] = (
    data,
  ) => {
    return onActionToggle(!!data)
  }

  const data = useMemo(() => {
    return deepmerge(modes, modesMapping)
  }, [modes, modesMapping])

  const modeExists = (modeSelected ?? '') in data

  const mode = (
    modeExists ? modeSelected : Object.keys(data)[0]
  ) as keyof typeof modes

  const modeSelectedValue = data[mode]

  const _units = useMemo(() => {
    const option = units[mode]
    const options =
      Object.keys(units).length >= 1 ? units : { [mode]: option.slice(0, 1) }
    return options[mode].map((unit) => {
      return {
        ...unit,
        ...unitsMapping[unit.value],
      }
    })
  }, [mode, units, unitsMapping])

  return (
    <Paper
      sx={{
        ...styles.container,
        ...sx,
      }}
      {...PaperProps}
    >
      <Box>
        <MeasurementToolsUIAction
          actionProps={actionProps}
          labels={labels?.action}
          enabled={enabled}
          onActionToggle={handleToggle}
        >
          {modeSelectedValue?.icon}
        </MeasurementToolsUIAction>
        <Options
          labels={labels?.options}
          modes={data}
          modeSelected={mode}
          units={_units}
          unitSelected={unitSelected}
          onChangeMode={onChangeMode}
          onChangeUnit={onChangeUnit}
        />
      </Box>
    </Paper>
  )
}

function MeasurementToolsUIAction({
  actionProps,
  labels,
  enabled,
  children,
  onActionToggle,
}: PropsWithChildren<
  Pick<
    MeasurementToolsComponentProps,
    'actionProps' | 'enabled' | 'onActionToggle'
  > & {
    labels?: PickDeep<
      MeasurementToolsComponentProps['labels'],
      'action'
    >['action']
  }
>) {
  const actionState = enabled ? 'active' : 'inactive'
  const actionLabel =
    labels?.tooltip?.[actionState] ??
    DEFAULT_MEASUREMENT_TOOLS_LABELS.action.tooltip[actionState]

  return (
    <Tooltip
      title={actionLabel}
      placement='right'
      {...actionProps?.TooltipProps}
    >
      <ToggleButton
        value='toggle'
        sx={styles.actions.icon}
        onClick={() => onActionToggle(!enabled)}
        aria-label={actionLabel}
        selected={enabled}
      >
        {children}
      </ToggleButton>
    </Tooltip>
  )
}

function Options({
  modes,
  modeSelected,
  units,
  unitSelected,
  labels,
  onChangeMode,
  onChangeUnit,
}: Required<
  Pick<
    MeasurementToolsComponentProps,
    'modeSelected' | 'onChangeMode' | 'onChangeUnit'
  >
> & {
  modes: MeasurementToolsComponentProps['modes'] &
    MeasurementToolsComponentProps['modesMapping']
  units: (MeasurementUnitOption<MeasurementUnit> & MeasureUnitMapping)[]
  unitSelected?: MeasurementToolsComponentProps['unitSelected']
  labels?: PickDeep<
    MeasurementToolsComponentProps['labels'],
    'options'
  >['options']
}) {
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
  const [unitModal, setUnitModal] = useState(false)

  const modesAvailable = Object.values(modes)
  const unitsAvailable = Object.values(units)

  const hasValues = modesAvailable.length > 1 || unitsAvailable.length > 1

  if (!hasValues) {
    return null
  }

  const open = Boolean(anchorEl)

  const handleToggle = (event: MouseEvent<HTMLButtonElement>) => {
    setAnchorEl(event.currentTarget)
  }

  const handleClose = () => {
    setAnchorEl(null)
  }

  const handleClick = (
    e: MouseEvent<HTMLLIElement>,
    mode: ValueOf<Required<typeof modes>>,
  ) => {
    e.preventDefault()
    onChangeMode(mode.value)
    handleClose()
  }

  const handleClickUnit = () => {
    setUnitModal(true)
  }

  const handleCloseModal = () => {
    setUnitModal(false)
  }

  const handleApplyModal = (data: string) => {
    handleCloseModal()
    onChangeUnit(data)
  }

  const modeTitle =
    labels?.mode?.title ?? DEFAULT_MEASUREMENT_TOOLS_LABELS.options.mode.title

  const unitTitle =
    labels?.units?.title ?? DEFAULT_MEASUREMENT_TOOLS_LABELS.options.units.title

  const hasDivider = modesAvailable.length > 1 && unitsAvailable.length > 1

  const unitSelectedValue = (units.find(
    (unit) => unit.value === unitSelected,
  ) ?? units[0])!

  return (
    <>
      <IconButton sx={styles.options.icon} onClick={handleToggle}>
        <ArrowDropDown />
      </IconButton>
      <Menu
        id='measurement-menu'
        anchorEl={anchorEl}
        open={open}
        onClose={handleClose}
        MenuListProps={{
          'aria-labelledby': 'basic-button',
          sx: styles.options.menu,
        }}
      >
        <div>
          {modesAvailable.length > 1 && (
            <>
              <Typography
                variant='subtitle2'
                color='text.secondary'
                sx={styles.options.title}
              >
                {modeTitle}
              </Typography>
              {modesAvailable.map((mode) => {
                const options =
                  labels?.mode?.options ??
                  DEFAULT_MEASUREMENT_TOOLS_LABELS.options.mode.options
                const label = options[mode.value]

                return (
                  <MenuItem
                    key={mode.value}
                    onClick={(e) => handleClick(e, mode)}
                    selected={mode.value === modeSelected}
                  >
                    <ListItemIcon sx={styles.options.icons}>
                      <SvgIcon>{mode.icon}</SvgIcon>
                    </ListItemIcon>
                    <ListItemText>{label}</ListItemText>
                  </MenuItem>
                )
              })}
            </>
          )}
          {hasDivider && <Divider />}
          {unitsAvailable.length > 1 && (
            <>
              <MenuItem onClick={handleClickUnit}>
                <ListItemText>{unitTitle}</ListItemText>
                <Typography
                  variant='caption'
                  fontWeight={500}
                  sx={styles.options.tag}
                >
                  {unitSelectedValue.short}
                </Typography>
              </MenuItem>
              <UnitsModal
                open={unitModal}
                labels={labels}
                units={units}
                unitSelectedValue={unitSelectedValue}
                onClose={handleCloseModal}
                onSubmit={handleApplyModal}
              />
            </>
          )}
        </div>
      </Menu>
    </>
  )
}

function UnitsModal({
  open,
  labels,
  units,
  unitSelectedValue,
  onClose,
  onSubmit,
}: {
  open: boolean
  labels?: PickDeep<
    MeasurementToolsComponentProps['labels'],
    'options'
  >['options']
  units: MeasurementUnitOption<MeasurementUnit>[]
  unitSelectedValue: MeasurementUnitOption<MeasurementUnit>
  onClose: () => void
  onSubmit: (data: string) => void
}) {
  const [system, setSystem] = useState<MeasurementSystem>('metric')

  const _units = useMemo(() => {
    return units.filter((unit) => unit.system === system)
  }, [system, units])

  const handleToggleChange = (
    _: MouseEvent<HTMLElement>,
    value: MeasurementSystem,
  ) => {
    setSystem(value)
  }

  const _labels =
    labels?.units?.modal?.options ??
    DEFAULT_MEASUREMENT_TOOLS_LABELS.options.units.modal.options

  const labelsBySystem = _labels[system].options

  const modalUnitTitle =
    labels?.units?.modal?.title ??
    DEFAULT_MEASUREMENT_TOOLS_LABELS.options.units.modal.title
  const modalUnitSubtitle =
    labels?.units?.modal?.subtitle ??
    DEFAULT_MEASUREMENT_TOOLS_LABELS.options.units.modal.subtitle
  const modalUnitApply =
    labels?.units?.modal?.apply ??
    DEFAULT_MEASUREMENT_TOOLS_LABELS.options.units.modal.apply

  const _unitSelectedValue =
    unitSelectedValue.system === system ? unitSelectedValue : _units[0]

  return (
    <Dialog
      open={open}
      component={Box}
      onClose={onClose}
      maxWidth='xs'
      PaperProps={{
        component: 'form',
        onSubmit: (e: FormEvent<HTMLFormElement>) => {
          e.preventDefault()
          const formData = new FormData(e.currentTarget)
          onSubmit(formData.get('unit-value') as string)
        },
      }}
    >
      <DialogTitle sx={styles.options.modal.title}>
        {modalUnitTitle}
        <IconButton onClick={onClose}>
          <Close />
        </IconButton>
      </DialogTitle>
      <DialogContent sx={styles.options.modal.content}>
        <Typography sx={styles.options.modal.subtitle}>
          {modalUnitSubtitle}
        </Typography>
        <ToggleButtonGroup
          color='primary'
          value={system}
          exclusive={true}
          fullWidth={true}
          onChange={handleToggleChange}
          aria-label='system'
          sx={styles.options.modal.toggle}
        >
          <ToggleButton value='metric'>{_labels.metric.title}</ToggleButton>
          <ToggleButton value='imperial'>{_labels.imperial.title}</ToggleButton>
        </ToggleButtonGroup>
        <FormControl key={_unitSelectedValue?.value}>
          <RadioGroup
            aria-labelledby='unit-value'
            defaultValue={_unitSelectedValue?.value}
            name='unit-value'
            sx={styles.options.modal.optionsGroup}
          >
            {_units.map((unit) => {
              const label =
                labelsBySystem[
                  unit.value as MeasurementUnitMetricDistance &
                    MeasurementUnitImperialDistance
                ]

              return (
                <FormControlLabel
                  key={unit.value}
                  value={unit.value}
                  control={<Radio />}
                  label={label}
                />
              )
            })}
          </RadioGroup>
        </FormControl>
      </DialogContent>
      <DialogActions sx={styles.options.modal.actions}>
        <Button variant='contained' type='submit'>
          {modalUnitApply}
        </Button>
      </DialogActions>
    </Dialog>
  )
}
