"use client";
import {
  createContext,
  FC,
  Fragment,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import ReactDOM from "react-dom";
import invariant from "tiny-invariant";

import { triggerPostMoveFlash } from "@atlaskit/pragmatic-drag-and-drop-flourish/trigger-post-move-flash";
import {
  attachClosestEdge,
  type Edge,
  extractClosestEdge,
} from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
import { getReorderDestinationIndex } from "@atlaskit/pragmatic-drag-and-drop-hitbox/util/get-reorder-destination-index";
import * as liveRegion from "@atlaskit/pragmatic-drag-and-drop-live-region";
import { DropIndicator } from "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box";
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
import {
  draggable,
  dropTargetForElements,
  monitorForElements,
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
import { pointerOutsideOfPreview } from "@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview";
import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview";
import { reorder } from "@atlaskit/pragmatic-drag-and-drop/reorder";
import { Box, xcss } from "@atlaskit/primitives";
import { token } from "@atlaskit/tokens";
import { cn } from "@/lib/utils";
import { ButtonBetter } from "./button";
import { RiDraggable } from "react-icons/ri";

type ItemPosition = "first" | "last" | "middle" | "only";

type CleanupFn = () => void;

type ItemEntry = { itemId: string; element: HTMLElement };

type ListContextValue = {
  getListLength: () => number;
  registerItem: (entry: ItemEntry) => CleanupFn;
  reorderItem: (args: {
    startIndex: number;
    indexOfTarget: number;
    closestEdgeOfTarget: Edge | null;
  }) => void;
  instanceId: symbol;
};

const ListContext = createContext<ListContextValue | null>(null);

function useListContext() {
  const listContext = useContext(ListContext);
  invariant(listContext !== null);
  return listContext;
}

type Item = {
  id: string;
  label: string;
};

const itemKey = Symbol("item");
type ItemData = {
  [itemKey]: true;
  item: Item;
  index: number;
  instanceId: symbol;
};

function getItemData({
  item,
  index,
  instanceId,
}: {
  item: Item;
  index: number;
  instanceId: symbol;
}): ItemData {
  return {
    [itemKey]: true,
    item,
    index,
    instanceId,
  };
}

function isItemData(data: Record<string | symbol, unknown>): data is ItemData {
  return data[itemKey] === true;
}

function getItemPosition({
  index,
  items,
}: {
  index: number;
  items: Item[];
}): ItemPosition {
  if (items.length === 1) {
    return "only";
  }

  if (index === 0) {
    return "first";
  }

  if (index === items.length - 1) {
    return "last";
  }

  return "middle";
}

const listItemContainerStyles = xcss({
  position: "relative",
  backgroundColor: "elevation.surface",
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-selectors -- Ignored via go/DSP-18766
});

const listItemStyles = xcss({
  position: "relative",
  padding: "space.100",
});

const listItemDisabledStyles = xcss({ opacity: 0.4 });

type DraggableState =
  | { type: "idle" }
  | { type: "preview"; container: HTMLElement }
  | { type: "dragging" };

const idleState: DraggableState = { type: "idle" };
const draggingState: DraggableState = { type: "dragging" };

const listItemPreviewStyles = xcss({
  paddingBlock: "space.050",
  paddingInline: "space.100",
  borderRadius: "border.radius.100",
  backgroundColor: "elevation.surface.overlay",
  maxWidth: "360px",
  whiteSpace: "nowrap",
  overflow: "hidden",
  textOverflow: "ellipsis",
});

const itemLabelStyles = xcss({
  flexGrow: 1,
  whiteSpace: "nowrap",
  textOverflow: "ellipsis",
  overflow: "hidden",
});

function ListItem({
  item,
  index,
  position,
  children,
  className,
  classDragBtn,
}: {
  item: Item;
  index: number;
  position: ItemPosition;
  children: (data?: any, index?: number) => any;
  className?: string;
  classDragBtn?: string;
}) {
  const { registerItem, instanceId } = useListContext();

  const ref = useRef<HTMLDivElement>(null);
  const [closestEdge, setClosestEdge] = useState<Edge | null>(null);

  const dragHandleRef = useRef<HTMLButtonElement>(null);

  const [draggableState, setDraggableState] =
    useState<DraggableState>(idleState);

  useEffect(() => {
    const element = ref.current;
    const dragHandle = dragHandleRef?.current;
    invariant(element);
    invariant(dragHandle);

    const data = getItemData({ item, index, instanceId });

    return combine(
      registerItem({ itemId: item.id, element }),
      draggable({
        element: dragHandle,
        getInitialData: () => data,
        onGenerateDragPreview({ nativeSetDragImage }) {
          setCustomNativeDragPreview({
            nativeSetDragImage,
            getOffset: pointerOutsideOfPreview({
              x: token("space.200", "16px"),
              y: token("space.100", "8px"),
            }),
            render({ container }) {
              setDraggableState({ type: "preview", container });

              return () => setDraggableState(draggingState);
            },
          });
        },
        onDragStart() {
          setDraggableState(draggingState);
        },
        onDrop() {
          setDraggableState(idleState);
        },
      }),
      dropTargetForElements({
        element,
        canDrop({ source }) {
          return (
            isItemData(source.data) && source.data.instanceId === instanceId
          );
        },
        getData({ input }) {
          return attachClosestEdge(data, {
            element,
            input,
            allowedEdges: ["top", "bottom"],
          });
        },
        onDrag({ self, source }) {
          const isSource = source.element === element;
          if (isSource) {
            setClosestEdge(null);
            return;
          }

          const closestEdge = extractClosestEdge(self.data);

          const sourceIndex = source.data.index;
          invariant(typeof sourceIndex === "number");

          const isItemBeforeSource = index === sourceIndex - 1;
          const isItemAfterSource = index === sourceIndex + 1;

          const isDropIndicatorHidden =
            (isItemBeforeSource && closestEdge === "bottom") ||
            (isItemAfterSource && closestEdge === "top");

          if (isDropIndicatorHidden) {
            setClosestEdge(null);
            return;
          }

          setClosestEdge(closestEdge);
        },
        onDragLeave() {
          setClosestEdge(null);
        },
        onDrop() {
          setClosestEdge(null);
        },
      })
    );
  }, [instanceId, item, index, registerItem]);

  return (
    <Fragment>
      <Box ref={ref} xcss={listItemContainerStyles}>
        <div
          className={cn(
            "group relative flex flex-row items-center gap-x-1 pt-2",
            className
          )}
        >
          <div
            className={cn(
              "drag-grab hidden group-hover:flex absolute top-0",
              css`
                left: 50%;
                transform: translateX(-50%);
              `
            )}
          >
            <ButtonBetter
              ref={dragHandleRef}
              variant={"outline"}
              className={cn(
                classDragBtn,
                "p-0 bg-transparent h-auto shadow-none hover:shadow-none hover:bg-transparent border-none "
              )}
            >
              <RiDraggable className="text-gray-500 rotate-90" />
            </ButtonBetter>
          </div>
          <div className="flex flex-col flex-grow">
            {typeof children === "function" ? children(item, index) : <></>}
          </div>
        </div>
        {closestEdge && <DropIndicator edge={closestEdge} gap="1px" />}
      </Box>
      {draggableState.type === "preview" &&
        ReactDOM.createPortal(
          <Box xcss={listItemPreviewStyles}>{item.label}</Box>,
          draggableState.container
        )}
    </Fragment>
  );
}

function getItemRegistry() {
  const registry = new Map<string, HTMLElement>();

  function register({ itemId, element }: ItemEntry) {
    registry.set(itemId, element);

    return function unregister() {
      registry.delete(itemId);
    };
  }

  function getElement(itemId: string): HTMLElement | null {
    return registry.get(itemId) ?? null;
  }

  return { register, getElement };
}

type ListState = {
  items: Item[];
  lastCardMoved: {
    item: Item;
    previousIndex: number;
    currentIndex: number;
    numberOfItems: number;
  } | null;
};
export const ListBetterDragDrop: FC<{
  data: any[];
  onChange: (data: any) => void | Promise<void>;
  className?: string;
  classContainer?: string;
  children: (data?: any, index?: number) => any;
  classDragBtn?: string;
}> = ({
  children,
  data,
  onChange,
  className,
  classContainer,
  classDragBtn,
}) => {
  const [item, setItem] = useState(data);

  const [{ items, lastCardMoved }, setListState] = useState<ListState>({
    items: data,
    lastCardMoved: null,
  });
  const [registry] = useState(getItemRegistry);

  // Isolated instances of this component from one another
  const [instanceId] = useState(() => Symbol("instance-id"));

  const reorderItem = useCallback(
    ({
      startIndex,
      indexOfTarget,
      closestEdgeOfTarget,
    }: {
      startIndex: number;
      indexOfTarget: number;
      closestEdgeOfTarget: Edge | null;
    }) => {
      const finishIndex = getReorderDestinationIndex({
        startIndex,
        closestEdgeOfTarget,
        indexOfTarget,
        axis: "vertical",
      });

      if (finishIndex === startIndex) {
        // If there would be no change, we skip the update
        return;
      }
      setListState((listState) => {
        const item = listState.items[startIndex];

        return {
          items: reorder({
            list: listState.items,
            startIndex,
            finishIndex,
          }),
          lastCardMoved: {
            item,
            previousIndex: startIndex,
            currentIndex: finishIndex,
            numberOfItems: listState.items.length,
          },
        };
      });
      const moveItem = (fromIndex: any, toIndex: any) => {
        setItem((prevItems) => {
          const updatedItems = [...prevItems];
          const [movedItem] = updatedItems.splice(fromIndex, 1); // Hapus elemen
          updatedItems.splice(toIndex, 0, movedItem); // Masukkan ke target index
          return updatedItems;
        });
      };
      // finishIndex === startIndex
      moveItem(startIndex, finishIndex);
      onChange(items);
    },
    []
  );

  useEffect(() => {
    console.log(data);
    return monitorForElements({
      canMonitor({ source }) {
        return isItemData(source.data) && source.data.instanceId === instanceId;
      },
      onDrop({ location, source }) {
        const target = location.current.dropTargets[0];
        if (!target) {
          return;
        }

        const sourceData = source.data;
        const targetData = target.data;
        if (!isItemData(sourceData) || !isItemData(targetData)) {
          return;
        }

        const indexOfTarget = items.findIndex(
          (item) => item.id === targetData.item.id
        );
        if (indexOfTarget < 0) {
          return;
        }

        const closestEdgeOfTarget = extractClosestEdge(targetData);

        reorderItem({
          startIndex: sourceData.index,
          indexOfTarget,
          closestEdgeOfTarget,
        });
      },
    });
  }, [instanceId, items, reorderItem]);

  // once a drag is finished, we have some post drop actions to take
  useEffect(() => {
    if (lastCardMoved === null) {
      return;
    }

    const { item, previousIndex, currentIndex, numberOfItems } = lastCardMoved;
    const element = registry.getElement(item.id);
    if (element) {
      triggerPostMoveFlash(element);
    }
  }, [lastCardMoved, registry]);

  // cleanup the live region when this component is finished
  useEffect(() => {
    return function cleanup() {
      liveRegion.cleanup();
    };
  }, []);

  const getListLength = useCallback(() => items.length, [items.length]);

  const contextValue: ListContextValue = useMemo(() => {
    return {
      registerItem: registry.register,
      reorderItem,
      instanceId,
      getListLength,
    };
  }, [registry.register, reorderItem, instanceId, getListLength]);
  if (!items?.length) return <></>;
  return (
    <ListContext.Provider value={contextValue}>
      <div className={cn("flex flex-col gap-y-1 w-full", classContainer)}>
        {items.map((item, index) => (
          <ListItem
            key={item.id}
            item={item}
            index={index}
            position={getItemPosition({ index, items })}
            children={children}
            className={className}
            classDragBtn={classDragBtn}
          />
        ))}
      </div>
    </ListContext.Provider>
  );
};
