import { useCallback, useMemo, useState, type ComponentType } from 'react'
import {
  DndContext,
  KeyboardSensor,
  PointerSensor,
  closestCenter,
  useSensor,
  useSensors,
  type DragEndEvent,
} from '@dnd-kit/core'
import {
  SortableContext,
  arrayMove,
  sortableKeyboardCoordinates,
  verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { IconButton, Menu, type SvgIconProps } from '@mui/material'
import { Tooltip } from '../../../components'
import { getWidgetStore, useWidget, useWidgetId } from '../../stores'
import { ChangeColumnIcon } from './change-column-icon'
import { DEFAULT_CHANGE_COLUMN_LABELS, type ChangeColumnLabels } from './labels'
import { SortableColumnItem } from './sortable-column-item'
import { styles } from './style'
import type { ChangeColumnWidgetState } from './types'
import type { ReactNode } from 'react'

export interface ChangeColumnItem {
  id: string
  label: ReactNode
}

export interface ChangeColumnProps {
  /**
   * Master list of columns (id + label) in their default order. The current
   * effective order is derived by combining this list with the widget's
   * `columnOrder` extension state.
   */
  columns: readonly ChangeColumnItem[]
  labels?: Partial<ChangeColumnLabels>
  icon?: ComponentType<SvgIconProps>
  iconProps?: SvgIconProps
  dragHandleIcon?: ComponentType<SvgIconProps>
}

/**
 * Action that lets users reorder columns via drag-and-drop. The new order is
 * persisted on the widget store as `columnOrder` (an array of ids) on the
 * {@link ChangeColumnWidgetState} extension. `<Table>` (and other column-aware
 * bridges) merge that order with their own `columns` prop via
 * `resolveColumns()` from the table primitives.
 *
 * Returns `null` when there are fewer than two columns to reorder.
 */
export function ChangeColumn({
  columns,
  labels,
  icon: Icon = ChangeColumnIcon,
  iconProps,
  dragHandleIcon,
}: ChangeColumnProps) {
  const id = useWidgetId()
  const _labels = { ...DEFAULT_CHANGE_COLUMN_LABELS, ...labels }
  const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)

  const columnOrder = useWidget<
    readonly string[] | undefined,
    ChangeColumnWidgetState
  >(id, (s) => s.columnOrder)

  const orderedIds = useMemo(() => {
    const masterIds = columns.map((c) => c.id)
    if (!columnOrder || columnOrder.length === 0) return masterIds
    // Set lookup so membership tests stay O(1) regardless of column count.
    const masterIdSet = new Set(masterIds)
    const seen = new Set<string>()
    const out: string[] = []
    for (const cid of columnOrder) {
      if (masterIdSet.has(cid) && !seen.has(cid)) {
        out.push(cid)
        seen.add(cid)
      }
    }
    for (const cid of masterIds) {
      if (!seen.has(cid)) out.push(cid)
    }
    return out
  }, [columnOrder, columns])

  const orderedColumns = useMemo(() => {
    const byId = new Map(columns.map((c) => [c.id, c]))
    // Single pass instead of map().filter() — skips unknown ids inline.
    const out: ChangeColumnItem[] = []
    for (const cid of orderedIds) {
      const c = byId.get(cid)
      if (c != null) out.push(c)
    }
    return out
  }, [orderedIds, columns])

  const sensors = useSensors(
    useSensor(PointerSensor),
    useSensor(KeyboardSensor, {
      coordinateGetter: sortableKeyboardCoordinates,
    }),
  )

  const handleOpen = useCallback((e: React.MouseEvent<HTMLElement>) => {
    e.stopPropagation()
    setAnchorEl(e.currentTarget)
  }, [])
  const handleClose = useCallback(() => setAnchorEl(null), [])

  const handleDragEnd = useCallback(
    (event: DragEndEvent) => {
      const { active, over } = event
      if (!over || active.id === over.id) return
      const oldIndex = orderedIds.indexOf(String(active.id))
      const newIndex = orderedIds.indexOf(String(over.id))
      if (oldIndex === -1 || newIndex === -1) return
      const next = arrayMove([...orderedIds], oldIndex, newIndex)
      getWidgetStore(id).setState({
        columnOrder: next,
      } as Partial<ChangeColumnWidgetState>)
    },
    [id, orderedIds],
  )

  if (columns.length < 2) return null

  const isOpen = Boolean(anchorEl)

  return (
    <>
      <Tooltip title={_labels.trigger}>
        <IconButton
          size='small'
          aria-label={_labels.trigger}
          aria-pressed={isOpen}
          aria-haspopup='true'
          aria-controls={isOpen ? 'change-column-menu' : undefined}
          aria-expanded={isOpen ? 'true' : undefined}
          onClick={handleOpen}
          className={isOpen ? 'active' : undefined}
          sx={{ ...styles.toggle, ...(isOpen && styles.toggleActive) }}
        >
          <Icon fontSize='small' {...iconProps} />
        </IconButton>
      </Tooltip>
      <DndContext
        sensors={sensors}
        collisionDetection={closestCenter}
        onDragEnd={handleDragEnd}
      >
        <Menu
          id='change-column-menu'
          anchorEl={anchorEl}
          open={isOpen}
          onClose={handleClose}
          anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
          transformOrigin={{ vertical: 'top', horizontal: 'right' }}
          slotProps={{ paper: { sx: styles.menuPaper } }}
        >
          <SortableContext
            items={orderedIds}
            strategy={verticalListSortingStrategy}
          >
            {orderedColumns.map((column) => (
              <SortableColumnItem
                key={column.id}
                id={column.id}
                label={column.label}
                dragHandleIcon={dragHandleIcon}
              />
            ))}
          </SortableContext>
        </Menu>
      </DndContext>
    </>
  )
}
