import { Position, useVueFlow } from "@vue-flow/core";
import { ref, watch } from "vue";
import type { Node, Edge } from "@vue-flow/core";
import { HJInterNodeData, HJPosition } from "../types/comTypes";

import { v4 as uuidv4 } from "uuid";

/**
 * @returns {string} - A unique id.
 */
function getId(): string {
  return uuidv4();
}

/**
 * In a real world scenario you'd want to avoid creating refs in a global scope like this as they might not be cleaned up properly.
 * @type {{draggedType: Ref<string|null>, isDragOver: Ref<boolean>, isDragging: Ref<boolean>}}
 */
const state = {
  /**
   * The type of the node being dragged.
   */
  dragData: ref<HJInterNodeData | null>(null),
  draggedType: ref<string | null>(null),
  isDragOver: ref(false),
  isDragging: ref(false),
};

export default function useDragAndDrop() {
  const { draggedType, isDragOver, isDragging, dragData } = state;

  const { addNodes, screenToFlowCoordinate, onNodesInitialized, updateNode } =
    useVueFlow();

  watch(isDragging, (dragging) => {
    document.body.style.userSelect = dragging ? "none" : "";
  });

  // 第二个类型是HJInterNodeData
  function onDragStart(event: DragEvent, data: HJInterNodeData) {
    if (event.dataTransfer) {
      event.dataTransfer.setData("application/vueflow", data.type!);
      event.dataTransfer.effectAllowed = "move";
    }

    draggedType.value = data.type!;
    dragData.value = JSON.parse(JSON.stringify(data));
    isDragging.value = true;

    document.addEventListener("drop", onDragEnd);
  }

  /**
   * Handles the drag over event.
   *
   * @param {DragEvent} event
   */
  function onDragOver(event: DragEvent) {
    event.preventDefault();

    if (draggedType.value) {
      isDragOver.value = true;

      if (event.dataTransfer) {
        event.dataTransfer.dropEffect = "move";
      }
    }
  }

  function onDragLeave() {
    isDragOver.value = false;
  }

  function onDragEnd() {
    isDragging.value = false;
    isDragOver.value = false;
    draggedType.value = null;
    dragData.value = null;
    document.removeEventListener("drop", onDragEnd);
  }

  /**
   * Handles the drop event.
   *
   * @param {DragEvent} event
   */
  function onDrop(event: DragEvent) {
    const position = screenToFlowCoordinate({
      x: event.clientX,
      y: event.clientY,
    });

    const nodeId = getId();

    const newNode: Node = {
      id: nodeId,
      position,
      data: JSON.parse(JSON.stringify(dragData.value)).data,
      type: JSON.parse(JSON.stringify(dragData.value)).type,
      targetPosition: Position.Top,
      sourcePosition: Position.Bottom,
    };

    console.log("从左侧拖拽的添加操作", newNode);
    // 如果没有给node的data设置handles属性，这默认上下结构
    if (!newNode.data.handles) {
      newNode.data.handles = [
        {
          type: "source",
          position: HJPosition.Bottom,
        },
        {
          type: "target",
          position: HJPosition.Top,
        },
      ];
    }

    /**
     * Align node position after drop, so it's centered to the mouse
     *
     * We can hook into events even in a callback, and we can remove the event listener after it's been called.
     */
    const { off } = onNodesInitialized(() => {
      updateNode(nodeId, (node) => ({
        position: {
          x: node.position.x - 100,
          y: node.position.y - node.dimensions.height / 2,
        },
      }));

      off();
    });

    addNodes(newNode);
    draggedType.value = null;
  }

  return {
    draggedType,
    isDragOver,
    isDragging,
    onDragStart,
    onDragLeave,
    onDragOver,
    onDrop,
  };
}
