import React, { type ReactNode, type RefObject, useEffect, useRef } from 'react'
import Sortable from 'sortablejs'

import styles from './_draggable-list.module.scss'
import { c } from '../../translations/LibraryTranslationService'

export type DraggableListProps<DataItem> = {
  /** Callout that returns the newly re-ordered list items. */
  callout: (newList: DataItem[]) => void
  /** Array of DOM elements that need drag and drop support. */
  children: Array<ReactNode>
  /** Array of options. */
  listItems: DataItem[]
  /** Optional container selector. Use this if we are not directly wrapping around a list and instead we want to specify some other container nested deep inside. */
  containerSelector?: RefObject<HTMLDivElement>
  /** Optional prop to disable default behavior of SortableJS. True by default as this fixes SortableJS buggy behavior. For some cases there won't be any issues and in such cases this can be set to false. */
  overrideDOMManipulation?: boolean
  /** Custom class to style the wrapper */
  wrapperClass?: string
  /** Optional prop to add a test id to the DraggableList for QA testing */
  qaTestId?: string
}

const DraggableList = <DataItem,>({
  callout,
  children,
  listItems,
  containerSelector,
  overrideDOMManipulation = true,
  wrapperClass,
  qaTestId = 'draggable-list',
}: DraggableListProps<DataItem>) => {
  const draggableContainerRef = useRef<HTMLDivElement>(null)
  useEffect(() => {
    let sortable: Sortable | null = null
    const draggableContainer = containerSelector
      ? containerSelector.current
      : draggableContainerRef.current
    if (draggableContainer) {
      sortable = draggableContainer
        ? new Sortable(draggableContainer, {
            animation: 250,
            multiDrag: true,
            // If this class is changed, then changes in DragHandle.tsx must be made to match this.
            handle: '.drag-handle', // Selector for the handle
            dragClass: styles.dragging,
            scroll: true,
            direction: 'vertical',
            onMove: (evt) => {
              // Logic to add the drop location and its styling while we are dragging any item.
              evt.from.querySelector(`.${styles.ghostMask}`)?.remove()
              const element = evt.dragged
              element.classList.add(styles.draggingContainer)
              const ghostElement = document.createElement('div')
              ghostElement.textContent = c('dropHere')
              ghostElement.classList.add(styles.ghostMask)
              evt.dragged.insertBefore(
                ghostElement,
                evt.dragged.children[evt.dragged.children.length],
              )
            },
            onEnd: (evt) => {
              // Remove the drop placeholder and its styling once the item is dropped.
              evt.from.querySelector(`.${styles.ghostMask}`)?.remove()
              const { oldIndex, newIndex } = evt
              const newList = [...listItems]
              if (
                oldIndex !== undefined &&
                newIndex !== undefined &&
                oldIndex !== newIndex // Manipulate only if the item is moved to a different position
              ) {
                // Logic to alter the DOM - Start
                if (overrideDOMManipulation) {
                  evt.item.remove()
                  evt.from.insertBefore(evt.item, evt.from.children[oldIndex])
                }
                // - End -
                // Logic to alter the order of the selectedList
                const draggedOut = newList[oldIndex]
                // Insert a copy of the dragged out item to its new position
                oldIndex > newIndex
                  ? newList.splice(newIndex, 0, draggedOut)
                  : newList.splice(newIndex + 1, 0, draggedOut)
                // Remove the dragged out item from the old position
                oldIndex > newIndex
                  ? newList.splice(oldIndex + 1, 1)
                  : newList.splice(oldIndex, 1)
                callout(newList)
              }
            },
          })
        : null
    }
    return () => {
      sortable?.destroy()
    }
  }, [callout, containerSelector, listItems, overrideDOMManipulation])

  return (
    <div
      className={wrapperClass}
      ref={draggableContainerRef}
      qa-test-id={qaTestId}
    >
      {children}
    </div>
  )
}

export default DraggableList
