import {useDraggable} from '@neodrag/react';
import {
  useReactFlow,
  type XYPosition
} from '@xyflow/react';
import {
  type ReactNode,
  useCallback,
  useRef,
  useState
} from 'react';

// Is used to make sure each subsequent node gets a unique id, so the ReactFlow implementation
// can distinguish between different nodes.
let id = 0;
const getId = () => `dndnode_${id++}`;

/**
 * DraggableNodeProps dictates the type properties of a DraggableNode
 */
interface DraggableNodeProps {
  className?: string;
  children: ReactNode;
  nodeType: string;
  onDrop: (nodeType: string, position: XYPosition) => void;
}

/**
 * Definition of a node inside the drag and drop toolbar,
 * these nodes require an onDrop function to be supplied
 * that dictates how the node is created in the graph.
 *
 * @param className
 * @param children
 * @param nodeType
 * @param onDrop
 * @constructor
 */
function DraggableNode({className, children, nodeType, onDrop}: DraggableNodeProps) {
  const draggableRef = useRef<HTMLDivElement>(null);
  const [position, setPosition] = useState<XYPosition>({x: 0, y: 0});

  // @ts-ignore
  useDraggable(draggableRef, {
    position: position,
    onDrag: ({offsetX, offsetY}) => {
      // Calculate position relative to the viewport
      setPosition({
        x: offsetX,
        y: offsetY,
      });
    },
    onDragEnd: ({event}) => {
      setPosition({x: 0, y: 0});
      onDrop(nodeType, {
        x: event.clientX,
        y: event.clientY,
      });
    },
  });

  return (
    <div className={className === "default" ? "draggable-node" : "draggable-node" + "__" + className}
         ref={draggableRef}>
      {children}
    </div>
  );
}

/**
 * the DndToolbar defines how the drag and drop toolbar component works
 * and includes the default onDrop behavior through handleNodeDrop
 * @constructor
 */
export function DndToolbar() {
  const {setNodes, screenToFlowPosition} = useReactFlow();

  /**
   * handleNodeDrop implements the default onDrop behavior
   */
  const handleNodeDrop = useCallback(
    (nodeType: string, screenPosition: XYPosition) => {
      const flow = document.querySelector('.react-flow');
      const flowRect = flow?.getBoundingClientRect();
      const isInFlow =
        flowRect &&
        screenPosition.x >= flowRect.left &&
        screenPosition.x <= flowRect.right &&
        screenPosition.y >= flowRect.top &&
        screenPosition.y <= flowRect.bottom;

      // Create a new node and add it to the flow
      if (isInFlow) {
        const position = screenToFlowPosition(screenPosition);

        const newNode = () => {
          switch (nodeType) {
            case "phase":
              return {
                id: getId(),
                type: nodeType,
                position,
                data: {label: `"new"`, number: (-1)},
              };
            case "start":
              return {
                id: getId(),
                type: nodeType,
                position,
                data: {label: `new start node`},
              };
            case "end":
              return {
                id: getId(),
                type: nodeType,
                position,
                data: {label: `new end node`},
              };
            case "norm":
              return {
                id: getId(),
                type: nodeType,
                position,
                data: {label: `new norm node`},
              };
            default: {
              return {
                id: getId(),
                type: nodeType,
                position,
                data: {label: `new default node`},
              };
            }
          }
        }

        setNodes((nds) => nds.concat(newNode()));
      }
    },
    [setNodes, screenToFlowPosition],
  );

  return (
    <div style={{
      padding: '10px 10px',
      outline: '2.5pt solid black',
      borderRadius: '0pt 0pt 5pt 5pt',
      borderColor: 'dimgrey',
      backgroundColor: 'canvas',
      display: 'flex',
      flexDirection: 'column',
      gap: '1rem'
    }}>
      <div className="description">
        You can drag these nodes to the pane to create new nodes.
      </div>
      <div className="DraggableNodeContainer" style={{
        backgroundColor: 'canvas',
        display: 'flex',
        flexDirection: 'row',
        gap: '1rem',
        justifyContent: 'center'
      }}>
        <DraggableNode className="phase" nodeType="phase" onDrop={handleNodeDrop}>
          phase Node
        </DraggableNode>
        <DraggableNode className="norm" nodeType="norm" onDrop={handleNodeDrop}>
          norm Node
        </DraggableNode>
      </div>
    </div>
  );
}