/* eslint-disable react-hooks/exhaustive-deps */
import { AnimatePresence, motion } from "motion/react";
import clsx from "clsx";
import { useRef, useState, useEffect } from "react";
import Marker from "./Marker";
import IMarker from "../types/IMarker";
import { markers } from "../data/markers";
import SearchIcon from "./icons/map/SearchIcon";
import MoveIcon from "./icons/map/MoveIcon";
import WeatherWidget from "./WeatherWidget";
import BottomPanel from "./BottomPanel";

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

interface Size {
  width: number;
  height: number;
}

interface MapProps {
  maxZoom?: number;
}

const constrainPosition = (
  position: Position,
  containerSize: Size,
  imageSize: Size,
  zoom: number
): Position => {
  const scaledWidth = imageSize.width * zoom;
  const scaledHeight = imageSize.height * zoom;
  const minX = containerSize.width - scaledWidth;
  const minY = containerSize.height - scaledHeight;

  return {
    x: Math.min(0, Math.max(minX, position.x)),
    y: Math.min(0, Math.max(minY, position.y)),
  };
};

const getEventPosition = (
  e: MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent
): Position => {
  if ("touches" in e) {
    return {
      x: e.touches[0].clientX,
      y: e.touches[0].clientY,
    };
  }
  return {
    x: e.clientX,
    y: e.clientY,
  };
};

const calculateMinZoom = (containerSize: Size, imageSize: Size): number => {
  if (imageSize.width === 0 || imageSize.height === 0) {
    return 0.1;
  }

  const widthRatio = containerSize.width / imageSize.width;
  const heightRatio = containerSize.height / imageSize.height;

  return Math.max(widthRatio, heightRatio);
};

function Map({ maxZoom = 0.8 }: MapProps) {
  const [isDragging, setIsDragging] = useState(false);
  const [startPosition, setStartPosition] = useState<Position>({ x: 0, y: 0 });
  const [position, setPosition] = useState<Position>({ x: 0, y: 0 });
  const containerRef = useRef<HTMLDivElement>(null);
  const mapRef = useRef<HTMLImageElement>(null);
  const [zoom, setZoom] = useState(0);
  const [originalSize, setOriginalSize] = useState<Size>({
    width: 0,
    height: 0,
  });
  const previousTouchDistance = useRef<number | null>(null);
  const initialTouchDistance = useRef<number | null>(null);
  const containerSizeRef = useRef<Size>({ width: 0, height: 0 });
  const minZoomRef = useRef<number>(1);
  const markersContainerRef = useRef<HTMLDivElement>(null);
  const [hoveredMarker, setHoveredMarker] = useState<IMarker | null>(null);
  const [isImageLoaded, setIsImageLoaded] = useState(false);
  const animationRef = useRef<number | null>(null);
  const [lastClickTime, setLastClickTime] = useState(0);
  const [isShowInstruction, setIsShowInstruction] = useState(true);

  useEffect(() => {
    if (!containerRef.current || !isImageLoaded || originalSize.width === 0)
      return;

    const containerRect = containerRef.current.getBoundingClientRect();
    const scaledWidth = originalSize.width * zoom;
    const scaledHeight = originalSize.height * zoom;

    const maxOffsetX = Math.max(0, scaledWidth - containerRect.width);
    const maxOffsetY = Math.max(0, scaledHeight - containerRect.height);

    const desiredOffsetX = containerRect.width * 0.46;
    const desiredOffsetY = containerRect.height * 0.5;

    const boundedOffsetX = Math.min(desiredOffsetX, maxOffsetX);
    const boundedOffsetY = Math.min(desiredOffsetY, maxOffsetY);

    setPosition({
      x: -boundedOffsetX,
      y: -boundedOffsetY,
    });
  }, [originalSize, isImageLoaded]);

  function handleLoad() {
    if (!mapRef.current || !containerRef.current) return;

    // Создаем временное изображение для гарантированного получения размеров
    const img = new Image();
    img.src = mapRef.current.src;

    img.onload = () => {
      const newOriginalSize = {
        width: img.naturalWidth || img.width,
        height: img.naturalHeight || img.height,
      };

      setOriginalSize(newOriginalSize);

      // Рассчитываем минимальный зум после получения размеров
      const containerRect = containerRef.current!.getBoundingClientRect();
      const minZoom = calculateMinZoom(
        {
          width: containerRect.width,
          height: containerRect.height,
        },
        newOriginalSize
      );

      minZoomRef.current = minZoom;
      setZoom(minZoom);
      setIsImageLoaded(true);
    };
  }

  // Update container size and min zoom on resize
  useEffect(() => {
    const updateContainerSize = () => {
      if (!containerRef.current) return;

      const containerRect = containerRef.current.getBoundingClientRect();
      const newContainerSize = {
        width: containerRect.width,
        height: containerRect.height,
      };
      containerSizeRef.current = newContainerSize;

      // Recalculate min zoom when container size changes
      const newMinZoom = calculateMinZoom(newContainerSize, originalSize);
      minZoomRef.current = newMinZoom;

      // Adjust zoom if it's below new minimum
      if (zoom < newMinZoom) {
        setZoom(newMinZoom);
        updatePosition(position, newMinZoom);
      }
    };

    updateContainerSize();

    window.addEventListener("resize", updateContainerSize);

    return () => {
      window.removeEventListener("resize", updateContainerSize);
    };
  }, [originalSize, zoom]);

  const getContainerSize = (): Size => {
    return containerSizeRef.current;
  };

  const updatePosition = (newPosition: Position, newZoom: number = zoom) => {
    if (!containerRef.current) return;

    const containerSize = getContainerSize();
    const constrainedPosition = constrainPosition(
      newPosition,
      containerSize,
      originalSize,
      newZoom
    );

    setPosition(constrainedPosition);
  };

  const zoomToPoint = (point: Position, targetZoom: number) => {
    if (!containerRef.current) return;

    // Ensure zoom is within bounds
    const boundedZoom = Math.min(
      maxZoom,
      Math.max(minZoomRef.current, targetZoom)
    );

    const containerRect = containerRef.current.getBoundingClientRect();
    const mouseX = point.x - containerRect.left;
    const mouseY = point.y - containerRect.top;

    const scale = boundedZoom / zoom;
    const dx = mouseX - position.x;
    const dy = mouseY - position.y;
    const newPosition = {
      x: mouseX - dx * scale,
      y: mouseY - dy * scale,
    };

    setZoom(boundedZoom);
    updatePosition(newPosition, boundedZoom);
  };

  const animateZoom = (
    point: Position,
    startZoom: number,
    endZoom: number,
    startTime: number,
    duration: number = 300
  ) => {
    const currentTime = Date.now();
    const elapsed = currentTime - startTime;

    if (elapsed >= duration) {
      zoomToPoint(point, endZoom);
      animationRef.current = null;
      return;
    }

    // Используем easeOutCubic для плавной анимации
    const progress = 1 - Math.pow(1 - elapsed / duration, 3);
    const currentZoom = startZoom + (endZoom - startZoom) * progress;

    zoomToPoint(point, currentZoom);
    animationRef.current = requestAnimationFrame(() =>
      animateZoom(point, startZoom, endZoom, startTime, duration)
    );
  };

  useEffect(() => {
    return () => {
      if (animationRef.current) {
        cancelAnimationFrame(animationRef.current);
      }
    };
  }, []);

  const handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
    if (isShowInstruction) {
      setIsShowInstruction(false);
    }

    if (e.touches.length === 2) {
      // Для щипка сразу сохраняем начальную дистанцию
      const touch1 = e.touches[0];
      const touch2 = e.touches[1];
      const distance = Math.hypot(
        touch1.clientX - touch2.clientX,
        touch1.clientY - touch2.clientY
      );
      initialTouchDistance.current = distance;
      previousTouchDistance.current = distance;
      return;
    }

    if (e.touches.length === 1) {
      handleStart(e);
    }
  };

  const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => {
    if (!containerRef.current) return;

    if (e.touches.length === 2) {
      e.preventDefault(); // Предотвращаем скролл страницы при щипке
      const touch1 = e.touches[0];
      const touch2 = e.touches[1];
      const distance = Math.hypot(
        touch1.clientX - touch2.clientX,
        touch1.clientY - touch2.clientY
      );

      if (initialTouchDistance.current === null) {
        initialTouchDistance.current = distance;
        previousTouchDistance.current = distance;
        return;
      }

      if (previousTouchDistance.current === null) {
        previousTouchDistance.current = distance;
        return;
      }

      // Увеличиваем порог изменения для более плавного зума
      const distanceChange = Math.abs(distance - previousTouchDistance.current);
      const changePercentage =
        (distanceChange / previousTouchDistance.current) * 100;

      if (changePercentage >= 5) {
        // Уменьшаем порог с 10 до 5 для более отзывчивого зума
        const zoomFactor = distance > previousTouchDistance.current ? 1.1 : 0.9;
        const centerX = (touch1.clientX + touch2.clientX) / 2;
        const centerY = (touch1.clientY + touch2.clientY) / 2;

        const newZoom = Math.min(
          maxZoom,
          Math.max(minZoomRef.current, zoom * zoomFactor)
        );

        // Проверяем, действительно ли изменился зум
        if (Math.abs(newZoom - zoom) > 0.001) {
          setZoom(newZoom);

          const containerRect = containerRef.current.getBoundingClientRect();
          const mouseX = centerX - containerRect.left;
          const mouseY = centerY - containerRect.top;

          const scale = newZoom / zoom;
          const dx = mouseX - position.x;
          const dy = mouseY - position.y;
          const newPosition = {
            x: mouseX - dx * scale,
            y: mouseY - dy * scale,
          };

          updatePosition(newPosition, newZoom);
        }

        previousTouchDistance.current = distance;
      }
      return;
    }

    // Обработка перетаскивания одним пальцем только если не в режиме щипка
    if (isDragging && e.touches.length === 1) {
      const { x, y } = getEventPosition(e);
      const newPosition = {
        x: x - startPosition.x,
        y: y - startPosition.y,
      };

      updatePosition(newPosition);
    }
  };

  const handleEnd = () => {
    setIsDragging(false);
  };

  const handleTouchEnd = () => {
    setIsDragging(false);
    previousTouchDistance.current = null;
    initialTouchDistance.current = null;
  };

  const handleWheel = (e: WheelEvent) => {
    e.preventDefault();
    setIsShowInstruction(false);

    if (!containerRef.current || !mapRef.current) return;

    const containerRect = containerRef.current.getBoundingClientRect();
    const mouseX = e.clientX - containerRect.left;
    const mouseY = e.clientY - containerRect.top;

    const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
    const newZoom = Math.min(
      maxZoom,
      Math.max(minZoomRef.current, zoom * zoomFactor)
    );

    if (Math.abs(newZoom - zoom) < 0.01) return;

    const scale = newZoom / zoom;
    const dx = mouseX - position.x;
    const dy = mouseY - position.y;
    const newPosition = {
      x: mouseX - dx * scale,
      y: mouseY - dy * scale,
    };

    if (isDragging) {
      const eventPosition = getEventPosition(e);
      setStartPosition({
        x: eventPosition.x - newPosition.x,
        y: eventPosition.y - newPosition.y,
      });
    }

    setZoom(newZoom);
    updatePosition(newPosition, newZoom);
  };

  const handleMouseMove = (
    e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>
  ) => {
    if (!isDragging || !containerRef.current) return;

    const { x, y } = getEventPosition(e);
    const newPosition = {
      x: x - startPosition.x,
      y: y - startPosition.y,
    };

    updatePosition(newPosition);
  };

  const handleStart = (
    e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>
  ) => {
    if (!mapRef.current) return;

    if (isShowInstruction) {
      setIsShowInstruction(false);
    }

    setIsDragging(true);
    const { x, y } = getEventPosition(e);
    setStartPosition({
      x: x - position.x,
      y: y - position.y,
    });
  };

  const handleClick = (
    e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>
  ) => {
    const currentTime = Date.now();

    if (currentTime - lastClickTime < 200) {
      if (animationRef.current) cancelAnimationFrame(animationRef.current);

      const targetZoom =
        Math.abs(zoom - maxZoom) < 0.01 ? minZoomRef.current : maxZoom;
      const point = getEventPosition(e);

      animationRef.current = requestAnimationFrame(() =>
        animateZoom(point, zoom, targetZoom, Date.now())
      );
      setLastClickTime(0);
    } else {
      setLastClickTime(currentTime);
    }
  };

  useEffect(() => {
    document.addEventListener("wheel", handleWheel, { passive: false });

    return () => {
      document.removeEventListener("wheel", handleWheel);
    };
  }, [isDragging, position]);

  // Разделяем стили трансформации для изображения и контейнера маркеров
  const imageStyle = {
    translate: `${position.x}px ${position.y}px`,
    scale: zoom,
    ...originalSize,
    transformOrigin: "0 0",
  };

  return (
    <div
      ref={containerRef}
      className="overflow-hidden h-screen w-screen relative select-none touch-none"
      style={{ cursor: isDragging ? "grabbing" : "grab" }}
      onTouchStart={handleTouchStart}
      onTouchEnd={handleTouchEnd}
      onTouchCancel={handleTouchEnd}
      onTouchMove={handleTouchMove}
      onMouseDown={handleStart}
      onMouseUp={handleEnd}
      onMouseLeave={handleEnd}
      onMouseMove={handleMouseMove}
      onClick={handleClick}
    >
      <img
        ref={mapRef}
        src="/images/map.jpg"
        alt="map"
        className={clsx(
          "pointer-events-none absolute max-w-none will-change-[transform,scale,opacity,filter]",
          "transition-[opacity,filter] duration-300",
          isImageLoaded && originalSize.width !== 0
            ? "opacity-100"
            : "opacity-0",
          hoveredMarker && "brightness-[80%]"
        )}
        style={imageStyle}
        onLoad={handleLoad}
      />
      <div ref={markersContainerRef} className="absolute" style={imageStyle}>
        <div className="relative h-full">
          {markers.map((marker) => (
            <Marker
              key={marker.id}
              marker={marker}
              zoom={zoom}
              onHover={setHoveredMarker}
              hoveredMarker={hoveredMarker}
            />
          ))}
        </div>
      </div>
      <WeatherWidget />
      <AnimatePresence>
        {isShowInstruction && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="absolute inset-0 z-30 flex items-center justify-center pointer-events-none"
          >
            <div className="w-fit bg-[#0D1922]/40 rounded-lg backdrop-blur-sm space-y-3 p-4 text-white">
              <div className="flex items-center justify-center gap-4">
                <SearchIcon />
                <div className="h-4 w-px bg-white"></div>
                <MoveIcon />
              </div>
              <div className="">
                <p className="text-sm">Zoom and Move to select a location</p>
              </div>
            </div>
          </motion.div>
        )}
      </AnimatePresence>
      <BottomPanel />
    </div>
  );
}

export default Map;
