import type { ClonedDraggableItem, DroppableConfig } from "./types"

const MAX_GHOST_HEIGHT = 200

export const createGhostElement = (
  element: HTMLElement,
  isResizing = false,
) => {
  // clone the element
  const ghostElement = element.cloneNode(true) as HTMLElement

  const transitionsTimings = {
    top: 15,
    left: 15,
    width: 300,
    transform: 300,
  }

  const transitionString = Object.entries(transitionsTimings)
    .map(([key, value]) => `${key} ${value}ms ease`)
    .join(", ")

  // get the dimensions and position of the original element
  const rect = element.getBoundingClientRect()
  const inlineStyles: Partial<CSSStyleDeclaration> = {
    position: "absolute",
    pointerEvents: "none",
    opacity: ".7",
    zIndex: "999999999",
    // copy the size of the original element
    width: `${rect.width}px`,
    height: `${rect.height}px`,
    // copy the position of the original element (place it exactly there)
    left: `${rect.left + window.scrollX}px`,
    top: `${rect.top + window.scrollY}px`,
    transition: transitionString,
    transformOrigin: "top left",
  }

  // assign all the styles to the element
  Object.assign(ghostElement.style, inlineStyles)

  ghostElement.dataset.ghost = "true"

  if (!isResizing && rect.height > MAX_GHOST_HEIGHT) {
    const scale = MAX_GHOST_HEIGHT / rect.height

    // scale the element timed out, to ensure it gets animated
    setTimeout(() => (ghostElement.style.transform = `scale(${scale})`), 0)
  }

  element.dataset.isBeingDragged = "true"
  element.style.display = "none"

  document.body.appendChild(ghostElement)

  return ghostElement
}

export const removeGhostElement = (
  element: HTMLElement,
  draggableElement: HTMLElement,
) => {
  // animate the ghost element back to its origin
  // element.style.transition = "transform 0.3s ease"
  // element.style.transform = `translate(0, 0)`

  // remove the ghost element and
  // setTimeout(() => {
  element?.remove()
  delete draggableElement.dataset.isBeingDragged
  draggableElement.style.display = ""
  // }, 300)
}

export const findClosestElement = (
  position: {
    x: number
    y: number
  },
  draggableElements: { rect: DOMRect; element: Element }[],
  direction: DroppableConfig["sortable"] = "vertical",
): { element: Element; index: number } | null => {
  let closestElement: { rect: DOMRect; element: Element } | null = null
  let closestIndex = -1
  let minDistance = Infinity

  // check for each element if the mouse is over or under it
  draggableElements.forEach((draggable, index) => {
    const rect = draggable.rect

    let isInsideBounds = false

    if (direction === "vertical") {
      // check if the mouse is inside the element vertically
      isInsideBounds = position.x >= rect.left && position.x <= rect.right
    }
    if (direction === "horizontal") {
      // check if the mouse is inside the element horizontally
      isInsideBounds = position.y >= rect.top && position.y <= rect.bottom
    }

    if (isInsideBounds) {
      let distanceToClosestEdge = 0
      let indexAdjustment = 0

      if (direction === "vertical") {
        // calculate the vertical distance of the mouse to the top and bottom of the element
        const distanceAbove = Math.abs(position.y - rect.top)
        const distanceBelow = Math.abs(position.y - rect.bottom)

        distanceToClosestEdge = Math.min(distanceAbove, distanceBelow)

        if (distanceBelow <= distanceAbove) {
          indexAdjustment = 1
        }
      }
      if (direction === "horizontal") {
        // calculate the horizontal distance of the mouse to the left and right of the element
        const distanceLeft = Math.abs(position.x - rect.left)
        const distanceRight = Math.abs(position.x - rect.right)

        distanceToClosestEdge = Math.min(distanceLeft, distanceRight)

        if (distanceRight <= distanceLeft) {
          indexAdjustment = 1
        }
      }

      // update if this element is closer to the mouse than previous elements
      if (distanceToClosestEdge < minDistance) {
        minDistance = distanceToClosestEdge
        closestElement = draggable
        closestIndex = index + indexAdjustment // Speichere den Index
      }
    }
  })

  // return null if no element was found
  if (closestElement) {
    return { element: closestElement.element, index: closestIndex }
  } else {
    return null
  }
}

export const animateElements = (
  index: number,
  elements: ClonedDraggableItem[],
  direction: DroppableConfig["sortable"] = "vertical",
  averageElementSize: { width: number; height: number },
) => {
  let translateX = 0
  let translateY = 0

  if (direction === "horizontal") {
    translateX = averageElementSize.width
  }
  if (direction === "vertical") {
    translateY = averageElementSize.height
  }

  elements.forEach(({ element }, i) => {
    if (i < index) {
      element.style.transform = ""
    } else {
      element.style.transition = "transform 0.3s ease"
      element.style.transform = `translate(${translateX}px, ${translateY}px)`
    }
  })
}

export const collectDraggableElements = (target: HTMLElement) => {
  const draggableChildren = target.querySelectorAll(":scope [data-draggable]")
  const draggableElements: Element[] = []

  draggableChildren.forEach((entry) => {
    if (entry.dataset.isBeingDragged) {
      return
    }
    // check if the parent is the correct parent (exclude children in deeper dropzones)
    if (entry.closest("[data-dropzone]") === target) {
      draggableElements.push(entry)
    }
  })

  return draggableElements
}

export const cloneDraggableElements = (
  draggableElements: Element[],
  cloneZIndex?: number,
) => {
  const clones: ClonedDraggableItem[] = []

  draggableElements.forEach((element) => {
    const clone = element.cloneNode(true) as HTMLElement
    const rect = element.getBoundingClientRect()

    clone.style.position = "absolute"
    clone.style.width = `${rect.width}px`
    clone.style.height = `${rect.height}px`
    clone.style.left = `${rect.left + window.scrollX}px`
    clone.style.top = `${rect.top + window.scrollY}px`
    clone.style.pointerEvents = "none"
    clone.dataset.clone = "true"
    if (cloneZIndex) {
      clone.style.zIndex = cloneZIndex.toString()
    }

    element.style.visibility = "hidden"

    document.body.appendChild(clone)
    clones.push({ element: clone, rect: clone.getBoundingClientRect() })
  })

  return clones
}

export const getAverageSizeOfClonedElements = (
  clonedDraggableElements: ClonedDraggableItem[],
) => {
  let width = 0
  let height = 0

  clonedDraggableElements.forEach(({ rect }) => {
    width += rect.width
    height += rect.height
  })

  width = width / clonedDraggableElements.length || 0
  height = height / clonedDraggableElements.length || 0

  if (height > MAX_GHOST_HEIGHT) {
    height = MAX_GHOST_HEIGHT
  }

  return { width, height }
}

export const clearClonedElements = (
  elements: ClonedDraggableItem[],
  draggableElements: Element[],
) => {
  elements.forEach((element) => element.element.remove())
  draggableElements.forEach((element) => (element.style.visibility = "visible"))
}
