import React, { ReactNode, useEffect, useRef, useState } from 'react';
import classNames from 'clsx';

import { getNumberInRange } from '@/modules/@common/helpers/number';

import styles from './ViewportDraggable.module.scss';

type Props = {
  className?: string;
  children: ReactNode;
};

type PositionType = {
  x: number;
  y: number;
};

const SENSITIVE_DISTANCE = 20;

function ViewportDraggable({ className = '', children }: Props) {
  const ref = useRef<HTMLDivElement>(null);
  const [draggable, setDraggable] = useState(false);
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const target = ref.current as HTMLDivElement;

    let axis: PositionType | null = null;
    let initialPos: PositionType | null = null;
    let dragDistance = 0;

    const { offsetTop, offsetLeft } = target;

    const fnStartDrag = (e: MouseEvent) => {
      if (e.button === 0) {
        setDraggable(true);
      }
    };

    const fn = (event: MouseEvent) => {
      const { clientWidth, clientHeight } = target;

      if (!axis) {
        axis = {
          x: event.clientX,
          y: event.clientY,
        };
      }

      setPosition((prev) => {
        if (!initialPos) {
          initialPos = prev;
        }

        const newX = getNumberInRange(
          initialPos.x + event.clientX - (axis?.x ?? 0),
          -offsetLeft,
          window.innerWidth - offsetLeft - clientWidth,
        );
        const newY = getNumberInRange(
          initialPos.y + event.clientY - (axis?.y ?? 0),
          -offsetTop,
          window.innerHeight - offsetTop - clientHeight,
        );

        dragDistance = Math.sqrt((newX - initialPos.x) ** 2 + (newY - initialPos.y) ** 2);

        return { x: newX, y: newY };
      });
    };

    const fnEnd = (event: MouseEvent) => {
      document.removeEventListener('mousemove', fn);
      document.removeEventListener('mouseup', fnEnd);

      setDraggable(false);

      if (dragDistance > SENSITIVE_DISTANCE) {
        event.stopPropagation();
      }
    };

    if (draggable) {
      document.addEventListener('mousemove', fn);
      document.addEventListener('mouseup', fnEnd);
    } else {
      target.addEventListener('mousedown', fnStartDrag);
    }

    return () => {
      document.removeEventListener('mousemove', fn);
      document.removeEventListener('mouseup', fnEnd);
      target.removeEventListener('mousedown', fnStartDrag);
    };
  }, [draggable]);

  return (
    <div
      ref={ref}
      onKeyUp={undefined}
      tabIndex={-1}
      role="button"
      className={classNames(styles.Container, className)}
      style={{
        transform: `translate(${position.x}px, ${position.y}px)`,
      }}
      onClick={(e) => {
        if (draggable) {
          e.stopPropagation();
        }
      }}
    >
      {children}
    </div>
  );
}

export default ViewportDraggable;
