import { useEffect, useRef, useState } from "react";
import { flushSync } from "react-dom";
import type { ReactNode } from "react";
import clsx from "clsx";

export interface Position {
  top?: number | string;
  left?: number | string;
  right?: number | string;
  bottom?: number | string;
  transform?: string;
}

export type Corner = "top-left" | "top-right" | "bottom-left" | "bottom-right";

interface DraggableContainerProps {
  /** Содержимое контейнера */
  children: ReactNode;
  /** Включить весь функционал компонента (по умолчанию true). Если false, компонент просто рендерит children без стилей и позиционирования */
  enabled?: boolean;
  /** Включить возможность перетаскивания (по умолчанию true) */
  draggable?: boolean;
  /** Ref элемента-хэндла для перетаскивания. Если указан, перетаскивание будет работать только при клике на этот элемент */
  dragHandleRef?: React.RefObject<HTMLElement | null>;
  /** Включить снэпинг к ближайшей четверти экрана при отпускании (по умолчанию true) */
  enableSnapping?: boolean;
  /** Автоматическое flex-выравнивание в зависимости от прижатого угла (по умолчанию false) */
  autoAlign?: boolean;
  /** Ограничить перетаскивание границами окна (по умолчанию false) */
  constrainToBounds?: boolean;
  /** Центрировать контейнер по вертикали (имеет приоритет над initialCorner и initialPosition) */
  centerVertical?: boolean;
  /** Центрировать контейнер по горизонтали (имеет приоритет над initialCorner и initialPosition) */
  centerHorizontal?: boolean;
  /** Начальный угол экрана (имеет приоритет над initialPosition) */
  initialCorner?: Corner;
  /** Начальная позиция контейнера (используется если не указан initialCorner) */
  initialPosition?: Position;
  /** Отступ от краев экрана при снэпинге и начальной позиции (по умолчанию "20px"). Можно указать в px, %, vw, vh */
  padding?: number | string;
  /** Дополнительные CSS классы */
  className?: string;
  /** Колбэк при изменении позиции */
  onPositionChange?: (position: Position) => void;
}

/**
 * Draggable Container - перетаскиваемый контейнер с опциональным снэпингом
 *
 * Логика снэпинга:
 * - Экран делится на 4 части (пополам по ширине и высоте)
 * - При отпускании определяется в какой четверти находится центр контейнера
 * - Контейнер прилипает к соответствующему углу с использованием top/bottom + left/right
 *
 * @example
 * // Базовое использование с автоматическим выравниванием
 * <DraggableContainer enableSnapping={true} autoAlign={true}>
 *   <YourContent />
 * </DraggableContainer>
 *
 * @example
 * // Размещение по центру экрана (вертикально и горизонтально)
 * <DraggableContainer centerVertical={true} centerHorizontal={true}>
 *   <YourContent />
 * </DraggableContainer>
 *
 * @example
 * // Центрирование только по вертикали
 * <DraggableContainer centerVertical={true}>
 *   <YourContent />
 * </DraggableContainer>
 *
 * @example
 * // Центрирование только по горизонтали
 * <DraggableContainer centerHorizontal={true}>
 *   <YourContent />
 * </DraggableContainer>
 *
 * @example
 * // Центрирование по вертикали с указанием позиции справа
 * <DraggableContainer centerVertical={true} initialPosition={{ right: "4.444vw" }}>
 *   <YourContent />
 * </DraggableContainer>
 *
 * @example
 * // Отключение перетаскивания (статичный контейнер)
 * <DraggableContainer draggable={false} initialPosition={{ top: "20px", right: "20px" }}>
 *   <YourContent />
 * </DraggableContainer>
 *
 * @example
 * // Полное отключение функционала (компонент не применяет никаких стилей)
 * <DraggableContainer enabled={false}>
 *   <YourContent />
 * </DraggableContainer>
 *
 * @example
 * // С указанием начального угла и отступами в процентах
 * <DraggableContainer initialCorner="bottom-right" padding="2%">
 *   <YourContent />
 * </DraggableContainer>
 *
 * @example
 * // С отступами в vw
 * <DraggableContainer initialCorner="top-left" padding="5vw">
 *   <YourContent />
 * </DraggableContainer>
 *
 * @example
 * // С ограничением перетаскивания границами окна
 * <DraggableContainer constrainToBounds={true} initialCorner="top-left">
 *   <YourContent />
 * </DraggableContainer>
 *
 * @example
 * // Без автоматического выравнивания (управляется вручную через className)
 * <DraggableContainer enableSnapping={true} className="flex flex-col gap-4">
 *   <YourContent />
 * </DraggableContainer>
 *
 * @example
 * // С указанием элемента-хэндла для перетаскивания (например, только за шапку)
 * const headerRef = useRef<HTMLDivElement>(null);
 * <DraggableContainer dragHandleRef={headerRef}>
 *   <div>
 *     <div ref={headerRef}>Header (drag me!)</div>
 *     <div>Content</div>
 *   </div>
 * </DraggableContainer>
 */
export default function DraggableContainer({
  children,
  enabled = true,
  draggable = true,
  dragHandleRef,
  enableSnapping = false,
  autoAlign = false,
  constrainToBounds = false,
  centerVertical = false,
  centerHorizontal = false,
  initialCorner,
  initialPosition,
  padding = "1.111vw",
  className = "",
  onPositionChange,
}: DraggableContainerProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const dragRef = useRef({
    isDragging: false,
    startX: 0,
    startY: 0,
    initialPosition: { top: 0, left: 0 },
  });

  // Функция для преобразования угла в позицию
  const getPositionFromCorner = (corner: Corner): Position => {
    switch (corner) {
      case "top-left":
        return { top: padding, left: padding };
      case "top-right":
        return { top: padding, right: padding };
      case "bottom-left":
        return { bottom: padding, left: padding };
      case "bottom-right":
        return { bottom: padding, right: padding };
    }
  };

  // Определяем начальную позицию
  const getInitialPosition = (): Position => {
    // Если используется центрирование
    if (centerVertical || centerHorizontal) {
      const position: Position = {};
      const transforms: string[] = [];

      // Вертикальное позиционирование
      if (centerVertical) {
        position.top = "50%";
        transforms.push("translateY(-50%)");
      } else if (initialPosition?.top !== undefined) {
        position.top = initialPosition.top;
      } else if (initialPosition?.bottom !== undefined) {
        position.bottom = initialPosition.bottom;
      } else {
        position.top = padding;
      }

      // Горизонтальное позиционирование
      if (centerHorizontal) {
        position.left = "50%";
        transforms.push("translateX(-50%)");
      } else if (initialPosition?.left !== undefined) {
        position.left = initialPosition.left;
      } else if (initialPosition?.right !== undefined) {
        position.right = initialPosition.right;
      } else {
        position.left = padding;
      }

      if (transforms.length > 0) {
        position.transform = transforms.join(" ");
      }

      return position;
    }

    if (initialCorner) {
      return getPositionFromCorner(initialCorner);
    }
    if (initialPosition) {
      return initialPosition;
    }
    // По умолчанию - верхний правый угол
    return { top: padding, left: padding };
  };

  const [position, setPosition] = useState<Position>(getInitialPosition());
  const [isDragging, setIsDragging] = useState(false);
  const [dragStartAlignment, setDragStartAlignment] = useState<string>("");

  // Проверяет, что событие началось на элементе-хэндле или его потомках
  const isEventOnDragHandle = (target: EventTarget | null): boolean => {
    if (!dragHandleRef?.current || !target) return true; // Если хэндл не указан, разрешаем перетаскивание откуда угодно

    const element = target as Node;
    return (
      dragHandleRef.current === element ||
      dragHandleRef.current.contains(element)
    );
  };

  const getAlignmentClassesFromPosition = (pos: Position): string => {
    if (!autoAlign) return "";

    const vertical = pos.bottom !== undefined ? "items-end" : "items-start";
    const horizontal =
      pos.right !== undefined ? "justify-end" : "justify-start";

    return `${vertical} ${horizontal}`;
  };

  const snapToQuadrant = (x: number, y: number): Position => {
    const windowWidth = window.innerWidth;
    const windowHeight = window.innerHeight;
    const containerWidth = containerRef.current?.offsetWidth || 0;
    const containerHeight = containerRef.current?.offsetHeight || 0;

    // Делим экран пополам по ширине и высоте
    const halfWidth = windowWidth / 2;
    const halfHeight = windowHeight / 2;

    // Определяем центр контейнера
    const centerX = x + containerWidth / 2;
    const centerY = y + containerHeight / 2;

    // Определяем в какой четверти находится центр контейнера
    const isLeft = centerX < halfWidth;
    const isTop = centerY < halfHeight;

    // Возвращаем позицию в зависимости от четверти
    if (isTop && isLeft) {
      // Верхняя левая четверть
      return { top: padding, left: padding };
    } else if (isTop && !isLeft) {
      // Верхняя правая четверть
      return { top: padding, right: padding };
    } else if (!isTop && isLeft) {
      // Нижняя левая четверть
      return { bottom: padding, left: padding };
    } else {
      // Нижняя правая четверть
      return { bottom: padding, right: padding };
    }
  };

  const startDrag = (clientX: number, clientY: number) => {
    if (!containerRef.current) return;

    const rect = containerRef.current.getBoundingClientRect();

    // Сохраняем текущие классы выравнивания перед началом драга
    if (autoAlign) {
      setDragStartAlignment(getAlignmentClassesFromPosition(position));
    }

    // Конвертируем позицию в left/top перед началом драга
    // Это нужно чтобы избежать скачка при переходе с right/bottom на left/top
    // Используем flushSync для синхронного обновления DOM
    if (
      position.transform ||
      position.right !== undefined ||
      position.bottom !== undefined
    ) {
      flushSync(() => {
        setPosition({
          top: rect.top,
          left: rect.left,
        });
      });
    }

    dragRef.current = {
      isDragging: true,
      startX: clientX,
      startY: clientY,
      initialPosition: {
        top: rect.top,
        left: rect.left,
      },
    };

    setIsDragging(true);
  };

  const handleMouseDown = (e: React.MouseEvent) => {
    if (!draggable) return;
    if (!isEventOnDragHandle(e.target)) return;
    startDrag(e.clientX, e.clientY);
  };

  const handleTouchStart = (e: React.TouchEvent) => {
    if (!draggable) return;
    if (!isEventOnDragHandle(e.target)) return;
    if (e.touches.length > 0) {
      startDrag(e.touches[0].clientX, e.touches[0].clientY);
    }
  };

  const updateDragPosition = (clientX: number, clientY: number) => {
    if (!dragRef.current.isDragging) return;

    const deltaX = clientX - dragRef.current.startX;
    const deltaY = clientY - dragRef.current.startY;

    // Вычисляем новую позицию
    let newTop = dragRef.current.initialPosition.top + deltaY;
    let newLeft = dragRef.current.initialPosition.left + deltaX;

    // Ограничиваем позицию границами окна, если включено
    if (constrainToBounds && containerRef.current) {
      const containerWidth = containerRef.current.offsetWidth;
      const containerHeight = containerRef.current.offsetHeight;
      const windowWidth = window.innerWidth;
      const windowHeight = window.innerHeight;

      newTop = Math.max(0, Math.min(newTop, windowHeight - containerHeight));
      newLeft = Math.max(0, Math.min(newLeft, windowWidth - containerWidth));
    }

    // Во время перетаскивания используем только top и left
    const newPosition = {
      top: newTop,
      left: newLeft,
    };

    setPosition(newPosition);
  };

  const handleMouseMove = (e: MouseEvent) => {
    updateDragPosition(e.clientX, e.clientY);
  };

  const handleTouchMove = (e: TouchEvent) => {
    if (e.touches.length > 0) {
      e.preventDefault(); // Предотвращаем скролл на мобильных
      updateDragPosition(e.touches[0].clientX, e.touches[0].clientY);
    }
  };

  const endDrag = () => {
    if (!dragRef.current.isDragging || !containerRef.current) return;

    dragRef.current.isDragging = false;
    setIsDragging(false);
    setDragStartAlignment(""); // Очищаем сохраненное выравнивание

    if (enableSnapping) {
      const rect = containerRef.current.getBoundingClientRect();
      const snappedPosition = snapToQuadrant(rect.left, rect.top);

      // Конвертируем текущую позицию в те же свойства, что будут в финальной
      // чтобы transition работал правильно
      const windowWidth = window.innerWidth;
      const windowHeight = window.innerHeight;

      const intermediatePosition: Position = {};

      // Определяем какие свойства будут в финальной позиции и устанавливаем текущие значения
      if (snappedPosition.top !== undefined) {
        intermediatePosition.top = rect.top;
      }
      if (snappedPosition.bottom !== undefined) {
        intermediatePosition.bottom = windowHeight - rect.bottom;
      }
      if (snappedPosition.left !== undefined) {
        intermediatePosition.left = rect.left;
      }
      if (snappedPosition.right !== undefined) {
        intermediatePosition.right = windowWidth - rect.right;
      }

      // Устанавливаем промежуточную позицию без transition
      setPosition(intermediatePosition);

      // Через минимальную задержку устанавливаем финальную позицию с transition
      setTimeout(() => {
        setPosition(snappedPosition);
        onPositionChange?.(snappedPosition);
      }, 0);
    } else {
      const rect = containerRef.current.getBoundingClientRect();
      const currentPosition = {
        top: rect.top,
        left: rect.left,
      };
      onPositionChange?.(currentPosition);
    }
  };

  const handleMouseUp = () => {
    endDrag();
  };

  const handleTouchEnd = () => {
    endDrag();
  };

  useEffect(() => {
    if (isDragging) {
      document.addEventListener("mousemove", handleMouseMove);
      document.addEventListener("mouseup", handleMouseUp);
      document.addEventListener("touchmove", handleTouchMove, {
        passive: false,
      });
      document.addEventListener("touchend", handleTouchEnd);
      document.addEventListener("touchcancel", handleTouchEnd);

      return () => {
        document.removeEventListener("mousemove", handleMouseMove);
        document.removeEventListener("mouseup", handleMouseUp);
        document.removeEventListener("touchmove", handleTouchMove);
        document.removeEventListener("touchend", handleTouchEnd);
        document.removeEventListener("touchcancel", handleTouchEnd);
      };
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isDragging]);

  // Устанавливаем cursor стили на элемент-хэндл
  useEffect(() => {
    if (!draggable || !dragHandleRef?.current) return;

    const handleElement = dragHandleRef.current;
    handleElement.style.cursor = isDragging ? "grabbing" : "grab";
    handleElement.style.userSelect = "none";
    handleElement.style.touchAction = "none";

    return () => {
      handleElement.style.cursor = "";
      handleElement.style.userSelect = "";
      handleElement.style.touchAction = "";
    };
  }, [draggable, dragHandleRef, isDragging]);

  const getContainerStyle = (): React.CSSProperties => {
    const style: React.CSSProperties = {
      position: "fixed",
      zIndex: 1000,
    };

    const formatValue = (value: number | string): string => {
      return typeof value === "number" ? `${value}px` : value;
    };

    if (position.top !== undefined) style.top = formatValue(position.top);
    if (position.left !== undefined) style.left = formatValue(position.left);
    if (position.right !== undefined) style.right = formatValue(position.right);
    if (position.bottom !== undefined)
      style.bottom = formatValue(position.bottom);
    if (position.transform !== undefined) style.transform = position.transform;

    return style;
  };

  const getAlignmentClasses = () => {
    if (!autoAlign) return "";

    // Во время драга используем сохраненное выравнивание
    if (isDragging) {
      return dragStartAlignment;
    }

    // В обычном состоянии вычисляем выравнивание на основе текущей позиции
    return getAlignmentClassesFromPosition(position);
  };

  // Если компонент отключен, просто рендерим children без стилей и логики
  if (!enabled) {
    return <div className={className}>{children}</div>;
  }

  return (
    <div
      ref={containerRef}
      className={clsx(
        "pointer-events-auto",
        // draggable && "touch-none",
        !isDragging && enableSnapping && "transition-[inset] duration-500 ease-out",
        draggable &&
          !dragHandleRef &&
          (isDragging ? "cursor-grabbing" : "cursor-grab"),
        getAlignmentClasses(),
        className
      )}
      style={getContainerStyle()}
      onMouseDown={draggable ? handleMouseDown : undefined}
      onTouchStart={draggable ? handleTouchStart : undefined}
    >
      {children}
    </div>
  );
}
