import { useEffect, type ComponentType } from 'react'
import { IconButton, type SvgIconProps } from '@mui/material'
import { HighlightAlt as HighlightAltIcon } from '@mui/icons-material'
import { Tooltip } from '../../../components'
import {
  useEchartInstance,
  useSingleTransform,
  useWidgetId,
} from '../../stores'
import { addBrush } from './transforms'
import { DEFAULT_BRUSH_TOGGLE_LABELS, type BrushToggleLabels } from './labels'
import { styles } from './style'

const BRUSH_DESCRIPTOR = {
  id: 'brush-toggle',
  type: 'config' as const,
  order: 25,
}

export interface BrushToggleProps {
  initialEnabled?: boolean
  /**
   * ECharts brush type. `'lineX'` (default) selects a range on the
   * x-axis — appropriate for bar / histogram / timeseries. `'rect'`
   * selects a 2-D rectangle — use for scatterplot. Other ECharts
   * brush types (`'lineY'`, `'polygon'`) are also accepted.
   */
  brushType?: 'lineX' | 'lineY' | 'rect' | 'polygon'
  /**
   * `'multiple'` (default) lets the user draw additional brushes on
   * top of existing ones. `'single'` replaces the previous brush on
   * each new draw.
   */
  brushMode?: 'multiple' | 'single'
  /**
   * Optional view of the consumer-owned selection. When provided and
   * the array is empty (`[]`), any brush rectangles currently drawn on
   * the chart are wiped — keeps the UI in sync when the parent clears
   * the selection externally (e.g. via `Widget.SelectionSummary`'s
   * `onClear`). Pass the same array that drives `useChartSelection` /
   * the option factory's `selection` input.
   *
   * Leave `undefined` to opt out — the sync effect becomes a no-op.
   */
  selection?: readonly unknown[] | null
  labels?: Partial<BrushToggleLabels>
  icon?: ComponentType<SvgIconProps>
  iconProps?: SvgIconProps
}

/**
 * Toggle ECharts' brush selection mode. Two-part wiring:
 *
 *   1. The config transform (`addBrush`) installs the `brush`
 *      component on the option so brushed items get the
 *      `outOfBrush` dim treatment.
 *   2. ECharts only enters "brush cursor" mode when something
 *      dispatches `takeGlobalCursor({ key: 'brush', ... })`. This
 *      component drives that dispatch via {@link useEchartInstance},
 *      reacting when the chart instance becomes available (the
 *      `BrushToggle` mounts before `Widget.Echart` in tree order, so
 *      on first mount the chart isn't ready yet).
 *
 *   3. ECharts clears the global cursor on every `setOption`, so we
 *      re-dispatch on each `'finished'` event — required for
 *      `brushMode: 'multiple'` to keep working across selection-driven
 *      re-renders, RelativeData toggles, etc.
 *
 * Selection events (`brushSelected` / `brushEnd`) flow through the
 * existing `useChartSelection` wiring at the consumer level — no extra
 * plumbing here.
 */
export function BrushToggle({
  initialEnabled = false,
  brushType = 'lineX',
  brushMode = 'multiple',
  selection,
  labels,
  icon: Icon = HighlightAltIcon,
  iconProps,
}: BrushToggleProps) {
  const id = useWidgetId()
  const _labels = { ...DEFAULT_BRUSH_TOGGLE_LABELS, ...labels }
  const { enabled, toggle } = useSingleTransform(
    id,
    BRUSH_DESCRIPTOR,
    addBrush,
    { initialEnabled },
  )

  // Reactive read of the live instance — the effect below re-runs when
  // the chart arrives, departs, or re-inits, instead of polling.
  const chart = useEchartInstance(id)

  useEffect(() => {
    if (!chart) return undefined
    if (!enabled) {
      // Disabled: exit brush cursor and wipe any drawn rectangles.
      chart.dispatchAction({ type: 'takeGlobalCursor' })
      chart.dispatchAction({ type: 'brush', areas: [] })
      return undefined
    }

    const enterBrush = (): void => {
      chart.dispatchAction({
        type: 'takeGlobalCursor',
        key: 'brush',
        brushOption: { brushType, brushMode },
      })
    }

    // Every `setOption` clears the global cursor. Re-dispatch after
    // each chart render so `brushMode: 'multiple'` stays usable across
    // selection-driven re-renders, RelativeData toggles, etc.
    const onFinished = (): void => enterBrush()
    chart.on('finished', onFinished)
    enterBrush()

    return () => {
      chart.off('finished', onFinished)
    }
  }, [chart, enabled, brushType, brushMode])

  // Sync the drawn rectangles with the external selection. When the
  // parent flips `selection` to `[]` (e.g. SelectionSummary's "Clear"
  // button), wipe the brush areas so the chart UI matches the data.
  // Dispatching with an empty `areas` array when nothing is drawn is a
  // safe no-op on ECharts, so no extra "previous value" bookkeeping is
  // needed. Kept as a separate effect so a parent selection change
  // doesn't tear down / re-attach the `'finished'` listener above.
  useEffect(() => {
    if (!chart || !enabled) return
    if (selection?.length === 0) {
      chart.dispatchAction({ type: 'brush', areas: [] })
    }
  }, [chart, enabled, selection])

  const tooltip = enabled ? _labels.on : _labels.off

  return (
    <Tooltip title={tooltip}>
      <IconButton
        size='small'
        aria-label={tooltip}
        aria-pressed={enabled}
        onClick={toggle}
        className={enabled ? 'active' : undefined}
        sx={{ ...styles.toggle, ...(enabled && styles.toggleActive) }}
      >
        <Icon fontSize='small' {...iconProps} />
      </IconButton>
    </Tooltip>
  )
}
