import { useCallback, useEffect, useRef } from 'react'
import { useReactFlow, type Node, type Edge } from '@xyflow/react'

interface ClipboardData {
  nodes: Array<{
    type: string
    data: Record<string, unknown>
    style?: React.CSSProperties
    relativePosition: { x: number; y: number }
  }>
  edges: Array<{
    sourceIndex: number
    targetIndex: number
    type?: string
    data?: Record<string, unknown>
    label?: string
  }>
}

function generateId(prefix: string): string {
  return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`
}

function isInputFocused(): boolean {
  const tag = document.activeElement?.tagName
  return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT'
}

export function useCanvasShortcuts({
  nodes: _nodes, // eslint-disable-line @typescript-eslint/no-unused-vars
  edges,
  setNodes,
  setEdges,
  setIsDirty,
  canvasRef,
  onUndo,
  onRedo,
  onNudge,
  onSetMode,
  onToggleShortcuts,
}: {
  nodes: Node[]
  edges: Edge[]
  setNodes: React.Dispatch<React.SetStateAction<Node[]>>
  setEdges: React.Dispatch<React.SetStateAction<Edge[]>>
  setIsDirty: (dirty: boolean) => void
  canvasRef: React.RefObject<HTMLDivElement | null>
  onUndo: () => void
  onRedo: () => void
  onNudge: (dx: number, dy: number) => void
  onSetMode: (mode: 'select' | 'pan' | 'connect') => void
  onToggleShortcuts: () => void
}) {
  const { getNodes, fitView, screenToFlowPosition, setNodes: rfSetNodes } = useReactFlow()
  const clipboardRef = useRef<ClipboardData | null>(null)

  const getSelectedNodes = useCallback((): Node[] => {
    return getNodes().filter(n => n.selected)
  }, [getNodes])

  const copyNodes = useCallback(() => {
    const selected = getSelectedNodes()
    if (selected.length === 0) return

    const centroid = {
      x: selected.reduce((sum, n) => sum + n.position.x, 0) / selected.length,
      y: selected.reduce((sum, n) => sum + n.position.y, 0) / selected.length,
    }

    const selectedIds = new Set(selected.map(n => n.id))

    const clipNodes = selected.map(n => ({
      type: n.type || 'device',
      data: structuredClone(n.data),
      style: n.style ? { ...n.style } : undefined,
      relativePosition: {
        x: n.position.x - centroid.x,
        y: n.position.y - centroid.y,
      },
    }))

    const selectedList = selected.map(n => n.id)
    const clipEdges = edges
      .filter(e => selectedIds.has(e.source) && selectedIds.has(e.target))
      .map(e => ({
        sourceIndex: selectedList.indexOf(e.source),
        targetIndex: selectedList.indexOf(e.target),
        type: e.type,
        data: e.data ? structuredClone(e.data) as Record<string, unknown> : undefined,
        label: typeof e.label === 'string' ? e.label : undefined,
      }))

    clipboardRef.current = { nodes: clipNodes, edges: clipEdges }
  }, [getSelectedNodes, edges])

  const pasteNodes = useCallback(() => {
    const clipboard = clipboardRef.current
    if (!clipboard || clipboard.nodes.length === 0) return

    const canvasEl = canvasRef.current
    if (!canvasEl) return
    const rect = canvasEl.getBoundingClientRect()
    const center = screenToFlowPosition({
      x: rect.left + rect.width / 2,
      y: rect.top + rect.height / 2,
    })

    const newNodeIds: string[] = []
    const newNodes: Node[] = clipboard.nodes.map(cn => {
      const prefix = cn.type === 'group' ? 'group' : 'device'
      const id = generateId(prefix)
      newNodeIds.push(id)
      return {
        id,
        type: cn.type,
        position: {
          x: center.x + cn.relativePosition.x,
          y: center.y + cn.relativePosition.y,
        },
        data: structuredClone(cn.data) as Record<string, unknown>,
        style: cn.style ? { ...cn.style } : undefined,
        selected: true,
      }
    })

    const newEdges: Edge[] = clipboard.edges.map(ce => ({
      id: generateId('edge'),
      source: newNodeIds[ce.sourceIndex],
      target: newNodeIds[ce.targetIndex],
      type: ce.type,
      data: ce.data ? structuredClone(ce.data) as Record<string, unknown> : undefined,
      label: ce.label,
    }))

    setNodes(nds => [
      ...nds.map(n => ({ ...n, selected: false })),
      ...newNodes,
    ])
    setEdges(eds => [...eds, ...newEdges])
    setIsDirty(true)
  }, [canvasRef, screenToFlowPosition, setNodes, setEdges, setIsDirty])

  const duplicateNodes = useCallback(() => {
    const selected = getSelectedNodes()
    if (selected.length === 0) return

    const selectedIds = new Set(selected.map(n => n.id))
    const idMap = new Map<string, string>()

    const newNodes: Node[] = selected.map(n => {
      const prefix = n.type === 'group' ? 'group' : 'device'
      const newId = generateId(prefix)
      idMap.set(n.id, newId)
      return {
        id: newId,
        type: n.type,
        position: { x: n.position.x + 30, y: n.position.y + 30 },
        data: structuredClone(n.data) as Record<string, unknown>,
        style: n.style ? { ...n.style } : undefined,
        selected: true,
      }
    })

    const newEdges: Edge[] = edges
      .filter(e => selectedIds.has(e.source) && selectedIds.has(e.target))
      .map(e => ({
        id: generateId('edge'),
        source: idMap.get(e.source)!,
        target: idMap.get(e.target)!,
        type: e.type,
        data: e.data ? structuredClone(e.data) as Record<string, unknown> : undefined,
        label: e.label,
      }))

    setNodes(nds => [
      ...nds.map(n => ({ ...n, selected: false })),
      ...newNodes,
    ])
    setEdges(eds => [...eds, ...newEdges])
    setIsDirty(true)
  }, [getSelectedNodes, edges, setNodes, setEdges, setIsDirty])

  const selectAll = useCallback(() => {
    rfSetNodes(nds => nds.map(n => ({ ...n, selected: true })))
  }, [rfSetNodes])

  const deleteSelected = useCallback(() => {
    const selected = getSelectedNodes()
    if (selected.length === 0) return
    const selectedIds = new Set(selected.map(n => n.id))
    setNodes(nds => nds.filter(n => !selectedIds.has(n.id)))
    setEdges(eds => eds.filter(e => !selectedIds.has(e.source) && !selectedIds.has(e.target)))
    setIsDirty(true)
  }, [getSelectedNodes, setNodes, setEdges, setIsDirty])

  const bringSelectedToFront = useCallback(() => {
    const selected = getSelectedNodes()
    if (!selected.length) return
    const selectedIds = new Set(selected.map(n => n.id))
    setNodes(nds => {
      const maxZ = Math.max(0, ...nds.map(n => n.zIndex ?? 0))
      return nds.map(n => selectedIds.has(n.id) ? { ...n, zIndex: maxZ + 1 } : n)
    })
    setIsDirty(true)
  }, [getSelectedNodes, setNodes, setIsDirty])

  const sendSelectedToBack = useCallback(() => {
    const selected = getSelectedNodes()
    if (!selected.length) return
    const selectedIds = new Set(selected.map(n => n.id))
    setNodes(nds => {
      const minZ = Math.min(0, ...nds.map(n => n.zIndex ?? 0))
      return nds.map(n => selectedIds.has(n.id) ? { ...n, zIndex: minZ - 1 } : n)
    })
    setIsDirty(true)
  }, [getSelectedNodes, setNodes, setIsDirty])

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (isInputFocused()) return

      const ctrl = e.ctrlKey || e.metaKey

      // Undo: Ctrl+Z / Cmd+Z
      if (e.key === 'z' && ctrl && !e.shiftKey) {
        e.preventDefault()
        onUndo()
        return
      }
      // Redo: Ctrl+Y or Ctrl+Shift+Z
      if ((e.key === 'y' && ctrl) || (e.key === 'z' && ctrl && e.shiftKey)) {
        e.preventDefault()
        onRedo()
        return
      }
      // Arrow key nudging
      if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
        e.preventDefault()
        const delta = e.shiftKey ? 10 : 1
        switch (e.key) {
          case 'ArrowUp':    onNudge(0, -delta); break
          case 'ArrowDown':  onNudge(0,  delta); break
          case 'ArrowLeft':  onNudge(-delta, 0); break
          case 'ArrowRight': onNudge( delta, 0); break
        }
        return
      }

      // Mode shortcuts: V = select, H = pan, C = connect
      if (!ctrl && e.key === 'v') {
        onSetMode('select')
        return
      }
      if (!ctrl && e.key === 'h') {
        onSetMode('pan')
        return
      }
      if (!ctrl && e.key === 'c') {
        onSetMode('connect')
        return
      }

      if (ctrl && e.key === 'c') {
        e.preventDefault()
        copyNodes()
      } else if (ctrl && e.key === 'v') {
        e.preventDefault()
        pasteNodes()
      } else if (ctrl && e.key === 'd') {
        e.preventDefault()
        duplicateNodes()
      } else if (ctrl && e.key === 'a') {
        e.preventDefault()
        selectAll()
      } else if (ctrl && e.shiftKey && (e.key === 'f' || e.key === 'F')) {
        e.preventDefault()
        fitView({ padding: 0.2 })
      } else if (e.key === ']' && !ctrl) {
        e.preventDefault()
        bringSelectedToFront()
      } else if (e.key === '[' && !ctrl) {
        e.preventDefault()
        sendSelectedToBack()
      } else if (e.key === '?' && !ctrl) {
        e.preventDefault()
        onToggleShortcuts()
      }
    }

    document.addEventListener('keydown', handleKeyDown)
    return () => document.removeEventListener('keydown', handleKeyDown)
  }, [copyNodes, pasteNodes, duplicateNodes, selectAll, fitView, bringSelectedToFront, sendSelectedToBack, onUndo, onRedo, onNudge, onSetMode, onToggleShortcuts])

  return {
    copyNodes,
    pasteNodes,
    duplicateNodes,
    selectAll,
    deleteSelected,
    bringSelectedToFront,
    sendSelectedToBack,
    hasClipboard: () => clipboardRef.current !== null && clipboardRef.current.nodes.length > 0,
  }
}
