import { useDraggable } from '@neodrag/react';
import { useReactFlow, type XYPosition } from '@xyflow/react';
import { type ReactNode, useCallback, useRef, useState } from 'react';
import styles from '../../VisProg.module.css';
import { NodeDefaults, type NodeTypes } from '../NodeRegistry'
import addNode from '../utils/AddNode';

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

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

  // @ts-expect-error from the neodrag package — safe to ignore
  useDraggable(draggableRef, {
    position,
    onDrag: ({ offsetX, offsetY }) => {
      setPosition({ x: offsetX, y: offsetY });
    },
    onDragEnd: ({ event }) => {
      setPosition({ x: 0, y: 0 });
      onDrop(nodeType, { x: event.clientX, y: event.clientY });
    },
  });

  return (
    <div className={className} 
    ref={draggableRef} 
    id={`draggable-${nodeType}`}
    data-testid={`draggable-${nodeType}`}
    >
      {children}
    </div>
  );
}

/**
 * DndToolbar defines how the drag and drop toolbar component works
 * and includes the default onDrop behavior.
 */
export function DndToolbar() {
  const { screenToFlowPosition } = useReactFlow();

  const handleNodeDrop = useCallback(
    (nodeType: keyof typeof NodeTypes, 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;

      if (isInFlow) {
        const position = screenToFlowPosition(screenPosition);
        addNode(nodeType, position);
      }
    },
    [screenToFlowPosition],
  );


  // Map over our default settings to see which of them have their droppable data set to true
  const droppableNodes = Object.entries(NodeDefaults)
  .filter(([, data]) => data.droppable)
  .map(([type, data]) => ({ 
    type: type as DraggableNodeProps['nodeType'], 
    data 
  }));

  return (
    <div className={`flex-col gap-lg padding-md ${styles.innerDndPanel}`}>
      <div className="description">
        You can drag these nodes to the pane to create new nodes.
      </div>
      <div className={`flex-row gap-lg ${styles.dndNodeContainer}`}>
        {/* Maps over all the nodes that are droppable, and puts them in the panel */}
        {droppableNodes.map(({type, data}) => (
          <DraggableNode
            key={type}
            className={styles[`draggable-node-${type}`]} // Our current style signature for nodes
            nodeType={type}
            onDrop={handleNodeDrop}
          >
            {data.label}
          </DraggableNode>
        ))}
      </div>
    </div>
  );
}
