import * as d3 from 'd3';
import { 

  D3Node, 
  D3Link, 

} from '../../graphTypes/graphTypes';
import { 
  getNodeDisplayName, 

} from '../../graphUtils/graphUtils';

import { CONSTANTS } from './constant';

class DragManager {
  static createDragBehavior(
    simulation: d3.Simulation<D3Node, D3Link>,
    onNodeDragStart?: (node: D3Node) => void,
    onNodeDrag?: (node: D3Node) => void,
    onNodeDragEnd?: (node: D3Node) => void
  ): d3.DragBehavior<SVGCircleElement, D3Node, D3Node | d3.SubjectPosition> {
    return d3.drag<SVGCircleElement, D3Node>()
      .on('start', (event: d3.D3DragEvent<SVGCircleElement, D3Node, D3Node>, d: D3Node) => {
        if (!event.active) simulation.alphaTarget(0.3).restart();
        d.fx = d.x;
        d.fy = d.y;
        
        // Visual feedback for drag start
        const target = event.sourceEvent?.target as SVGCircleElement;
        if (target) {
          d3.select(target)
            .attr('stroke', CONSTANTS.COLORS.SELECTION)
            .attr('stroke-width', CONSTANTS.SIZES.STROKE_WIDTH.selected)
            .attr('filter', 'url(#glow)');
        }
        
        onNodeDragStart?.(d);
      })
      .on('drag', (event: d3.D3DragEvent<SVGCircleElement, D3Node, D3Node>, d: D3Node) => {
        d.fx = event.x;
        d.fy = event.y;
        onNodeDrag?.(d);
      })
      .on('end', (event: d3.D3DragEvent<SVGCircleElement, D3Node, D3Node>, d: D3Node) => {
        if (!event.active) simulation.alphaTarget(0);
        
        // Option to keep node fixed or release it
        // Uncomment next lines to make nodes "sticky" after dragging
        // d.fx = null;
        // d.fy = null;
        
        // Remove drag visual feedback
        const target = event.sourceEvent?.target as SVGCircleElement;
        if (target) {
          d3.select(target)
            .attr('stroke', CONSTANTS.COLORS.NODE_STROKE)
            .attr('stroke-width', CONSTANTS.SIZES.STROKE_WIDTH.default)
            .attr('filter', null);
        }
        
        onNodeDragEnd?.(d);
      });
  }

  static enableNodeDragging(
    nodeElements: d3.Selection<SVGCircleElement, D3Node, SVGGElement, unknown>,
    simulation: d3.Simulation<D3Node, D3Link>,
    isAddingNode: boolean
  ): void {
    if (isAddingNode) {
      // Disable dragging when in add node mode
      nodeElements.on('.drag', null);
      return;
    }

    const dragBehavior = this.createDragBehavior(
      simulation,
      (node) => {
        console.log(`Started dragging node: ${getNodeDisplayName(node)}`);
        // You can add custom logic here for drag start
      },
      (node) => {
        // Custom logic during drag (optional)
        // console.log(`Dragging node: ${getNodeDisplayName(node)}`);
      },
      (node) => {
        console.log(`Finished dragging node: ${getNodeDisplayName(node)}`);
        // You can add custom logic here for drag end
      }
    );

    nodeElements.call(dragBehavior);
  }

  static updateCursorForDragging(
    nodeElements: d3.Selection<any, D3Node, any, any>,
    isAddingNode: boolean
  ): void {
    nodeElements.style('cursor', isAddingNode ? 'crosshair' : 'grab');
    
    // Update cursor during drag
    nodeElements
      .on('mousedown.cursor', function() {
        if (!isAddingNode) {
          d3.select(this).style('cursor', 'grabbing');
        }
      })
      .on('mouseup.cursor', function() {
        if (!isAddingNode) {
          d3.select(this).style('cursor', 'grab');
        }
      })
      .on('mouseleave.cursor', function() {
        if (!isAddingNode) {
          d3.select(this).style('cursor', 'grab');
        }
      });
  }
}

export default DragManager;