/**
 * useDraggable Hook
 *
 * Generic draggable panel management with pointer tracking.
 * Used in constructor.tsx for draggable controls, menu, and editor panels.
 */

import { useState, useRef, useEffect, useCallback } from 'react';

interface Position {
  x: number;
  y: number;
}

interface DragState {
  pointerOffsetX: number;
  pointerOffsetY: number;
}

interface UseDraggableOptions {
  /** Initial position */
  initialPosition?: Position;
  /** Minimum x position (default: 0) */
  minX?: number;
  /** Minimum y position (default: 0) */
  minY?: number;
  /** Maximum x position (calculated from window if not provided) */
  maxX?: number;
  /** Maximum y position (calculated from window if not provided) */
  maxY?: number;
  /** Element width for calculating max bounds */
  elementWidth?: number;
  /** Element height for calculating max bounds */
  elementHeight?: number;
}

interface UseDraggableResult {
  /** Current position */
  position: Position;
  /** Set position directly */
  setPosition: (position: Position) => void;
  /** Whether currently dragging */
  isDragging: boolean;
  /** Handler to attach to drag handle's onMouseDown */
  onDragStart: (event: React.MouseEvent) => void;
  /** Handler to attach to drag handle (alternative that ignores button clicks) */
  onDragStartIgnoreButtons: (event: React.MouseEvent) => void;
}

/**
 * Clamp a value between min and max
 */
const clamp = (value: number, min: number, max: number): number =>
  Math.min(Math.max(value, min), max);

/**
 * Hook for making elements draggable with mouse.
 * Handles pointer tracking and bounds clamping.
 *
 * @example
 * const { position, onDragStart, isDragging } = useDraggable({
 *   initialPosition: { x: 20, y: 20 },
 *   elementWidth: 400,
 * });
 *
 * return (
 *   <div style={{ left: position.x, top: position.y }}>
 *     <div onMouseDown={onDragStart}>Drag Handle</div>
 *     <div>Content</div>
 *   </div>
 * );
 */
export function useDraggable({
  initialPosition = { x: 0, y: 0 },
  minX = 0,
  minY = 0,
  maxX,
  maxY,
  elementWidth = 0,
  elementHeight = 0,
}: UseDraggableOptions = {}): UseDraggableResult {
  const [position, setPosition] = useState<Position>(initialPosition);
  const [isDragging, setIsDragging] = useState(false);
  const dragRef = useRef<DragState | null>(null);

  // Calculate max bounds
  const getMaxX = useCallback(() => {
    if (maxX !== undefined) return maxX;
    if (typeof window === 'undefined') return Infinity;
    return Math.max(window.innerWidth - elementWidth, 0);
  }, [maxX, elementWidth]);

  const getMaxY = useCallback(() => {
    if (maxY !== undefined) return maxY;
    if (typeof window === 'undefined') return Infinity;
    return Math.max(window.innerHeight - elementHeight, 0);
  }, [maxY, elementHeight]);

  // Pointer move handler
  useEffect(() => {
    const onPointerMove = (event: MouseEvent) => {
      if (!dragRef.current) return;

      const nextX = clamp(
        event.clientX - dragRef.current.pointerOffsetX,
        minX,
        getMaxX(),
      );
      const nextY = clamp(
        event.clientY - dragRef.current.pointerOffsetY,
        minY,
        getMaxY(),
      );

      setPosition({ x: nextX, y: nextY });
    };

    const onPointerUp = () => {
      if (dragRef.current) {
        dragRef.current = null;
        setIsDragging(false);
      }
    };

    window.addEventListener('mousemove', onPointerMove);
    window.addEventListener('mouseup', onPointerUp);

    return () => {
      window.removeEventListener('mousemove', onPointerMove);
      window.removeEventListener('mouseup', onPointerUp);
    };
  }, [minX, minY, getMaxX, getMaxY]);

  // Initialize position from window dimensions and clamp to bounds
  useEffect(() => {
    if (typeof window === 'undefined') return;

    // Clamp initial position to viewport bounds
    const clampedX = clamp(initialPosition.x, minX, getMaxX());
    const clampedY = clamp(initialPosition.y, minY, getMaxY());

    // Update if position needs adjustment
    if (position.x !== clampedX || position.y !== clampedY) {
      setPosition({ x: clampedX, y: clampedY });
    }
    // Only run on mount
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const onDragStart = useCallback((event: React.MouseEvent) => {
    const targetRect = (
      event.currentTarget as HTMLElement
    ).getBoundingClientRect();
    dragRef.current = {
      pointerOffsetX: event.clientX - targetRect.left,
      pointerOffsetY: event.clientY - targetRect.top,
    };
    setIsDragging(true);
  }, []);

  const onDragStartIgnoreButtons = useCallback(
    (event: React.MouseEvent) => {
      const target = event.target as HTMLElement;
      if (target.closest('button')) return;

      onDragStart(event);
    },
    [onDragStart],
  );

  return {
    position,
    setPosition,
    isDragging,
    onDragStart,
    onDragStartIgnoreButtons,
  };
}

/**
 * Create multiple draggable instances with shared pointer tracking.
 * More efficient than multiple useDraggable hooks when only one can drag at a time.
 */
export function useMultipleDraggables(
  configs: Record<string, UseDraggableOptions>,
): Record<string, UseDraggableResult> {
  // This is a convenience wrapper - in practice, constructor.tsx
  // manages multiple drag refs manually for efficiency.
  // Individual useDraggable hooks work fine for most cases.
  const results: Record<string, UseDraggableResult> = {};

  for (const [key, config] of Object.entries(configs)) {
    // eslint-disable-next-line react-hooks/rules-of-hooks
    results[key] = useDraggable(config);
  }

  return results;
}

export default useDraggable;
