<script lang="ts" setup>
import { ref } from 'vue'
import { toRef } from '@vueuse/core'
import UserSelection from '../../components/UserSelection/UserSelection.vue'
import NodesSelection from '../../components/NodesSelection/NodesSelection.vue'
import type { GraphNode } from '../../types'
import { SelectionMode } from '../../types'
import { useKeyPress, useVueFlow } from '../../composables'
import { getConnectedEdges, getNodesInside } from '../../utils'
import { getMousePosition } from './utils'

const { isSelecting } = defineProps<{ isSelecting: boolean }>()

const {
  vueFlowRef,
  getNodes,
  getEdges,
  viewport,
  emits,
  userSelectionActive,
  removeSelectedElements,
  panOnDrag,
  userSelectionRect,
  elementsSelectable,
  nodesSelectionActive,
  addSelectedElements,
  getSelectedEdges,
  getSelectedNodes,
  removeNodes,
  removeEdges,
  selectionMode,
  deleteKeyCode,
  multiSelectionKeyCode,
  multiSelectionActive,
} = useVueFlow()

const container = ref<HTMLDivElement | null>(null)

const prevSelectedNodesCount = ref(0)

const prevSelectedEdgesCount = ref(0)

const containerBounds = ref<DOMRect>()

const hasActiveSelection = toRef(() => elementsSelectable.value && (isSelecting || userSelectionActive.value))

useKeyPress(deleteKeyCode, (keyPressed) => {
  if (!keyPressed) {
    return
  }

  const nodesToRemove = getNodes.value.reduce<GraphNode[]>((res, node) => {
    if (!node.selected && node.parentNode && res.find((n) => n.id === node.parentNode)) {
      res.push(node)
    } else if (node.selected) {
      res.push(node)
    }

    return res
  }, [])

  if (nodesToRemove || getSelectedEdges.value) {
    if (getSelectedEdges.value.length > 0) {
      removeEdges(getSelectedEdges.value)
    }

    if (nodesToRemove.length > 0) {
      removeNodes(nodesToRemove)
    }

    nodesSelectionActive.value = false

    removeSelectedElements()
  }
})

useKeyPress(multiSelectionKeyCode, (keyPressed) => {
  multiSelectionActive.value = keyPressed
})

function resetUserSelection() {
  userSelectionActive.value = false
  userSelectionRect.value = null

  prevSelectedNodesCount.value = 0
  prevSelectedEdgesCount.value = 0
}

function onClick(event: MouseEvent) {
  if (event.target !== container.value || hasActiveSelection.value) {
    return
  }

  emits.paneClick(event)

  removeSelectedElements()

  nodesSelectionActive.value = false
}

function onContextMenu(event: MouseEvent) {
  if (event.target !== container.value) {
    return
  }

  if (Array.isArray(panOnDrag.value) && panOnDrag.value?.includes(2)) {
    event.preventDefault()
    return
  }

  emits.paneContextMenu(event)
}

function onWheel(event: WheelEvent) {
  if (event.target !== container.value) {
    return
  }

  emits.paneScroll(event)
}

function onMouseDown(event: MouseEvent) {
  containerBounds.value = vueFlowRef.value!.getBoundingClientRect()

  if (
    !hasActiveSelection.value ||
    !elementsSelectable ||
    !isSelecting ||
    event.button !== 0 ||
    event.target !== container.value ||
    !containerBounds.value
  ) {
    return
  }

  const { x, y } = getMousePosition(event, containerBounds.value)

  removeSelectedElements()

  userSelectionRect.value = {
    width: 0,
    height: 0,
    startX: x,
    startY: y,
    x,
    y,
  }

  userSelectionActive.value = true

  emits.selectionStart(event)
}

function onMouseMove(event: MouseEvent) {
  if (!hasActiveSelection.value) {
    return emits.paneMouseMove(event)
  }

  if (!isSelecting || !containerBounds.value || !userSelectionRect.value) {
    return
  }

  if (!userSelectionActive.value) {
    userSelectionActive.value = true
  }

  if (nodesSelectionActive.value) {
    nodesSelectionActive.value = false
  }

  const mousePos = getMousePosition(event, containerBounds.value)
  const startX = userSelectionRect.value.startX ?? 0
  const startY = userSelectionRect.value.startY ?? 0

  const nextUserSelectRect = {
    ...userSelectionRect.value,
    x: mousePos.x < startX ? mousePos.x : startX,
    y: mousePos.y < startY ? mousePos.y : startY,
    width: Math.abs(mousePos.x - startX),
    height: Math.abs(mousePos.y - startY),
  }

  const selectedNodes = getNodesInside(
    getNodes.value,
    userSelectionRect.value,
    viewport.value,
    selectionMode.value === SelectionMode.Partial,
  )

  const selectedEdges = getConnectedEdges(selectedNodes, getEdges.value)

  prevSelectedNodesCount.value = selectedNodes.length
  prevSelectedEdgesCount.value = selectedEdges.length

  userSelectionRect.value = nextUserSelectRect

  addSelectedElements([...selectedNodes, ...selectedEdges])
}

function onMouseUp(event: MouseEvent) {
  if (!hasActiveSelection.value) {
    return
  }

  if (event.button !== 0) {
    return
  }

  // We only want to trigger click functions when in selection mode if
  // the user did not move the mouse.
  if (!userSelectionActive.value && userSelectionRect.value && event.target === container.value) {
    onClick(event)
  }

  nodesSelectionActive.value = prevSelectedNodesCount.value > 0

  resetUserSelection()

  emits.selectionEnd(event)
}

function onMouseLeave(event: MouseEvent) {
  if (!hasActiveSelection.value) {
    return emits.paneMouseLeave(event)
  }

  if (userSelectionActive.value) {
    nodesSelectionActive.value = prevSelectedNodesCount.value > 0
    emits.selectionEnd?.(event)
  }

  resetUserSelection()
}

function onMouseEnter(event: MouseEvent) {
  if (hasActiveSelection.value) {
    return
  }

  emits.paneMouseEnter(event)
}
</script>

<script lang="ts">
export default {
  name: 'Pane',
  compatConfig: { MODE: 3 },
}
</script>

<template>
  <div
    ref="container"
    class="vue-flow__pane vue-flow__container"
    :class="{ selection: isSelecting }"
    @click="onClick"
    @contextmenu="onContextMenu"
    @wheel.passive="onWheel"
    @mouseenter="onMouseEnter"
    @mousedown="onMouseDown"
    @mousemove="onMouseMove"
    @mouseup="onMouseUp"
    @mouseleave="onMouseLeave"
  >
    <slot />
    <UserSelection v-if="userSelectionActive && userSelectionRect" :user-selection-rect="userSelectionRect" />
    <NodesSelection v-if="nodesSelectionActive && getSelectedNodes.length" />
  </div>
</template>
