import { CheckIcon, DragHandleHorizontalIcon, MixerHorizontalIcon } from '@radix-ui/react-icons'
import type { Table } from '@tanstack/react-table'

import * as React from 'react'

import { Button } from '@components/ui/button/button'
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from '@components/ui/command/command'
import { Popover, PopoverContent, PopoverTrigger } from '@components/ui/popover/popover'
import { cn } from '@lib/utils'

type DataGridViewMenuProps<TData> = {
  table: Table<TData>
} & React.ComponentProps<typeof PopoverContent>

export function DataGridViewMenu<TData>({ table, ...props }: DataGridViewMenuProps<TData>) {
  // Columns that are shown in the view menu. We include any column with an accessor
  // so users can see the full column list, even when a column cannot be hidden.
  const viewableColumns = React.useMemo(
    () => table.getAllColumns().filter((column) => typeof column.accessorFn !== 'undefined'),
    [table],
  )

  // Local order state for displaying items in the menu
  const [order, setOrder] = React.useState<string[]>(() => {
    const stateOrder = table.getState().columnOrder ?? []
    if (stateOrder && stateOrder.length > 0) {
      return stateOrder
    }
    return table.getAllColumns().map((c) => c.id)
  })

  // Search state for the CommandInput — used to conditionally show the empty state
  const [searchQuery, setSearchQuery] = React.useState<string>('')

  // Drag state
  const [isDragging, setIsDragging] = React.useState(false)
  const [insertionIndex, setInsertionIndex] = React.useState<number | null>(null)

  // Keep order in sync with external changes to table state
  React.useEffect(() => {
    const stateOrder = table.getState().columnOrder ?? []
    if (stateOrder && stateOrder.length > 0) {
      setOrder(stateOrder)
      return
    }
    setOrder(table.getAllColumns().map((c) => c.id))
  }, [table])

  const visibleSet = React.useMemo(() => new Set(viewableColumns.map((c) => c.id)), [viewableColumns])

  // Derive the ordered columns we will show in the menu (filter to hideable ones)
  const orderedColumns = React.useMemo(() => {
    const ordered: ReturnType<typeof table.getAllColumns>[0][] = []
    for (const id of order) {
      const col = viewableColumns.find((c) => c.id === id)
      if (col) {
        ordered.push(col)
      }
    }
    // Add any missing viewable columns at the end
    for (const col of viewableColumns) {
      if (!ordered.find((c) => c.id === col.id)) {
        ordered.push(col)
      }
    }
    return ordered
  }, [order, viewableColumns, table])

  const draggingRef = React.useRef<string | null>(null)
  const groupRef = React.useRef<HTMLDivElement | null>(null)

  const getBaseOrder = React.useCallback((): string[] => {
    return (
      table.getState().columnOrder && table.getState().columnOrder.length
        ? table.getState().columnOrder.slice()
        : table.getAllColumns().map((c) => c.id)
    ) as string[]
  }, [table])

  const computeInsertionIndex = React.useCallback(
    (clientY: number) => {
      const baseOrder = getBaseOrder()

      const el = groupRef.current
      if (!el) {
        return baseOrder.length
      }

      const nodes = Array.from(el.querySelectorAll('[data-col-id]')) as HTMLElement[]
      for (const node of nodes) {
        const rect = node.getBoundingClientRect()
        const mid = rect.top + rect.height / 2
        if (clientY < mid) {
          const id = node.dataset.colId as string
          const pos = baseOrder.indexOf(id)
          const newIndex = pos === -1 ? baseOrder.length : pos
          return newIndex
        }
      }
      return baseOrder.length
    },
    [getBaseOrder],
  )

  // moveItemInArray was removed; we compute insertion index directly on pointer drag

  function onDragEnd() {
    draggingRef.current = null
    setIsDragging(false)
    setInsertionIndex(null)
  }

  const doDrop = React.useCallback(
    (dragId: string, toIndex: number) => {
      // Build a base order that includes all columns (so hidden columns are preserved)
      const baseOrder = (
        table.getState().columnOrder && table.getState().columnOrder.length
          ? table.getState().columnOrder.slice()
          : table.getAllColumns().map((c) => c.id)
      ) as string[]

      const fromIndex = baseOrder.indexOf(dragId)
      if (fromIndex === -1) {
        onDragEnd()
        return
      }

      const copy = baseOrder.slice()
      copy.splice(fromIndex, 1)

      // If the dragged element came from before the target index,
      // removing it shifts indices left by 1, so adjust insertion index.
      let insertAt = toIndex
      if (fromIndex < toIndex) {
        insertAt = toIndex - 1
      }
      insertAt = Math.min(Math.max(0, insertAt), copy.length)
      copy.splice(insertAt, 0, dragId)

      table.setColumnOrder(copy)

      // Update our local order view (only for hideable columns)
      setOrder(copy.filter((id) => visibleSet.has(id)))
      onDragEnd()
    },
    [table, visibleSet],
  )

  const startPointerDrag = React.useCallback(
    (e: React.PointerEvent, id: string) => {
      e.preventDefault()
      draggingRef.current = id
      setIsDragging(true)
      // initialize insertion index
      setInsertionIndex(computeInsertionIndex(e.clientY))

      const onMove = (ev: PointerEvent) => {
        const idx = computeInsertionIndex(ev.clientY)
        setInsertionIndex(idx)
      }

      const onUp = (ev: PointerEvent) => {
        window.removeEventListener('pointermove', onMove)
        window.removeEventListener('pointerup', onUp)
        const dragId = draggingRef.current
        const toIndex = computeInsertionIndex(ev.clientY)
        if (dragId) {
          doDrop(dragId, toIndex)
        }
        draggingRef.current = null
        setIsDragging(false)
        setInsertionIndex(null)
      }

      window.addEventListener('pointermove', onMove)
      window.addEventListener('pointerup', onUp)
    },
    [computeInsertionIndex, doDrop],
  )

  function insertionWouldBeNoOp(targetIndex: number | null) {
    if (targetIndex === null) {
      return false
    }
    const dragId = draggingRef.current
    if (!dragId) {
      return false
    }
    const base = getBaseOrder()
    const fromIndex = base.indexOf(dragId)
    if (fromIndex === -1) {
      return false
    }
    let insertAt = targetIndex
    if (fromIndex < targetIndex) {
      insertAt = targetIndex - 1
    }
    return insertAt === fromIndex
  }

  const dragFromHandle = React.useRef(false)

  function resetTableView() {
    const defaultOrder = table.getAllColumns().map((c) => c.id)

    try {
      table.reset()
    } finally {
      // Clear persisted localStorage keys for this path
      try {
        if (typeof window !== 'undefined') {
          const path = window.location.pathname || 'unknown'
          const prefix = `pxl.dataGrid:${path}:`
          const keys: string[] = []
          for (let i = 0; i < localStorage.length; i++) {
            const k = localStorage.key(i)
            if (k && k.startsWith(prefix)) {
              keys.push(k)
            }
          }
          for (const k of keys) {
            localStorage.removeItem(k)
          }
          // Also update the local menu order so the UI reflects the reset immediately
          try {
            setOrder(defaultOrder.filter((id) => visibleSet.has(id)))
          } catch (_) {
            // ignore
          }
        }
      } catch (_) {
        // ignore storage errors
      }
    }
  }

  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button
          aria-label="Toggle columns"
          role="combobox"
          variant="outline"
          size="sm"
          className="ml-auto hidden h-8 font-normal lg:flex"
        >
          <MixerHorizontalIcon className="text-neutral-500 dark:text-neutral-400" />
          View
        </Button>
      </PopoverTrigger>
      <PopoverContent className={cn('w-56 p-0 m-4 mt-0', isDragging ? 'cursor-grabbing' : '')} {...props}>
        <Command>
          <CommandInput placeholder="Search columns..." onValueChange={setSearchQuery} />
          <CommandList>
            {searchQuery.trim() ? <CommandEmpty>No columns found.</CommandEmpty> : null}
            <div ref={groupRef}>
              <CommandGroup className="p-1">
                {/* Insertion indicator at top */}
                {isDragging && insertionIndex === 0 && !insertionWouldBeNoOp(0) ? (
                  <div className="h-0.5 bg-accent w-full my-1 rounded" />
                ) : null}
                {orderedColumns.map((column) => {
                  const currentBase = (
                    table.getState().columnOrder && table.getState().columnOrder.length
                      ? table.getState().columnOrder.slice()
                      : table.getAllColumns().map((c) => c.id)
                  ) as string[]
                  const pos = currentBase.indexOf(column.id)
                  const isHideable = column.getCanHide()
                  return (
                    <React.Fragment key={column.id}>
                      <CommandItem
                        value={column.id}
                        data-col-id={column.id}
                        className={cn(
                          'flex items-center gap-2 px-2 py-1 rounded',
                          !isDragging
                            ? 'data-[selected=true]:bg-accent'
                            : 'data-[selected=true]:bg-transparent cursor-grabbing',
                          draggingRef.current === column.id && isDragging && 'bg-accent data-[selected=true]:bg-accent',
                        )}
                        onSelect={() => {
                          if (dragFromHandle.current) {
                            dragFromHandle.current = false
                            return
                          }
                          if (!isHideable) {
                            // Column cannot be hidden; do nothing when clicked.
                            return
                          }
                          column.toggleVisibility(!column.getIsVisible())
                        }}
                      >
                        <span
                          onPointerDown={(e) => {
                            if (searchQuery.trim() !== '') {
                              return
                            }

                            dragFromHandle.current = true
                            e.stopPropagation()
                            e.preventDefault()
                            startPointerDrag(e, column.id)
                          }}
                          className={cn(
                            'size-4 text-neutral-400',
                            searchQuery.trim() !== '' && 'opacity-10',
                            searchQuery.trim() === '' && (isDragging ? 'cursor-grabbing' : 'cursor-grab'),
                          )}
                        >
                          <DragHandleHorizontalIcon />
                        </span>
                        <span className="truncate">{(column.columnDef.meta as any)?.label ?? column.id}</span>
                        <CheckIcon
                          className={cn(
                            'ml-auto size-4 shrink-0',
                            // Visible columns show the check; non-hideable columns show a muted check
                            isHideable
                              ? column.getIsVisible()
                                ? 'opacity-100'
                                : 'opacity-0'
                              : 'opacity-50 text-neutral-400',
                          )}
                        />
                      </CommandItem>

                      {/* Insertion indicator after the item (between items) */}
                      {isDragging && insertionIndex === pos + 1 && !insertionWouldBeNoOp(pos + 1) ? (
                        <div className="h-0.5 bg-accent w-full my-1 rounded" />
                      ) : null}
                    </React.Fragment>
                  )
                })}
              </CommandGroup>
            </div>
          </CommandList>
        </Command>
        <div className="border-t px-3 py-2">
          <Button
            variant="ghost"
            size="sm"
            onClick={() => {
              // Reset table state: order, visibility, pinning and sizing
              resetTableView()
            }}
          >
            Reset view
          </Button>
        </div>
      </PopoverContent>
    </Popover>
  )
}
