import { FederatedMouseEvent, Graphics } from "pixi.js";
import { useCallback, useState, useEffect, useRef, FC, useMemo } from "react";
import { observer } from "mobx-react-lite";

import {
  BACKGROUND_COLOR,
  PATH_COLOR,
  STATION_RADIUS,
  STATION_OUTLINE_WIDTH,
  UP_SCALE,
} from "./Constants";
import { useTransform } from "./TransformContext";
import { StationData } from "./types";
import { useMapData } from "./MapDataContext";
import { coordinatesToLocal } from "./utils";
import { languageStore } from "@shared";

declare const pixiContainer: any;
declare const pixiGraphics: any;
declare const pixiText: any;

type HorizontalAlign = "left" | "center" | "right";
type VerticalAlign = "top" | "center" | "bottom";
type TextAlign = HorizontalAlign | `${HorizontalAlign} ${VerticalAlign}`;
type LabelAlign = "left" | "center" | "right";

/**
 * Преобразует текстовое позиционирование в anchor координаты.
 */

/**
 * Получает координату anchor.x из типа выравнивания.
 */

interface StationProps {
  station: StationData;
  ruLabel: string | null;
  anchorPoint?: { x: number; y: number };
  /** Anchor для всего блока с текстом. По умолчанию: `"right center"` */
  labelBlockAnchor?: TextAlign | { x: number; y: number };
  /** Внутреннее выравнивание текста в блоке. По умолчанию: `"left"` */
  labelAlign?: LabelAlign;
  /** Callback для изменения внутреннего выравнивания */
  onLabelAlignChange?: (align: LabelAlign) => void;
  /** Callback для отслеживания наведения на текст */
  onTextHover?: (isHovered: boolean) => void;
}

interface LabelAlignmentControlProps {
  scale: number;
  currentAlign: LabelAlign;
  onAlignChange: (align: LabelAlign) => void;
  onPointerOver: () => void;
  onPointerOut: () => void;
  onControlPointerEnter: () => void;
  onControlPointerLeave: () => void;
}

interface StationLabelProps
  extends Omit<StationProps, "ruLabelAnchor" | "nameLabelAnchor"> {}

const getAnchorFromOffset = (
  offsetX: number,
  offsetY: number
): { x: number; y: number } => {
  if (offsetX === 0 && offsetY === 0) {
    return { x: 0.5, y: 0.5 };
  }

  const length = Math.hypot(offsetX, offsetY);
  const nx = offsetX / length;
  const ny = offsetY / length;

  return { x: (1 - nx) / 2, y: (1 - ny) / 2 };
};

const LabelAlignmentControl: FC<LabelAlignmentControlProps> = ({
  scale,
  currentAlign,
  onAlignChange,

  onControlPointerEnter,
  onControlPointerLeave,
}) => {
  const controlHeight = 50 / scale;
  const controlWidth = 200 / scale;
  const fontSize = 18 / scale;
  const borderRadius = 8 / scale;
  const compensatedRuFontSize = (26 * 0.75) / scale;
  const buttonWidth = controlWidth / 3;
  const strokeWidth = 2 / scale;

  const drawBg = useCallback(
    (g: Graphics) => {
      g.clear();

      g.roundRect(
        -controlWidth / 2,
        0,
        controlWidth,
        controlHeight,
        borderRadius
      );
      g.fill({ color: "#1a1a1a" });

      g.roundRect(
        -controlWidth / 2,
        0,
        controlWidth,
        controlHeight,
        borderRadius
      );
      g.stroke({ color: "#333333", width: strokeWidth });

      for (let i = 1; i < 3; i++) {
        const x = -controlWidth / 2 + buttonWidth * i;
        g.moveTo(x, strokeWidth);
        g.lineTo(x, controlHeight - strokeWidth);
        g.stroke({ color: "#333333", width: strokeWidth });
      }
    },
    [controlWidth, controlHeight, borderRadius, buttonWidth, strokeWidth]
  );

  const drawButtonHighlight = useCallback(
    (g: Graphics, index: number, isActive: boolean) => {
      g.clear();

      if (isActive) {
        const x = -controlWidth / 2 + buttonWidth * index;
        g.roundRect(
          x + strokeWidth,
          strokeWidth,
          buttonWidth - strokeWidth * 2,
          controlHeight - strokeWidth * 2,
          borderRadius / 2
        );
        g.fill({ color: "#0066cc", alpha: 0.8 });
      }
    },
    [controlWidth, controlHeight, buttonWidth, strokeWidth, borderRadius]
  );

  const getTextStyle = (isActive: boolean) => ({
    fontSize,
    fontWeight: isActive ? ("bold" as const) : ("normal" as const),
    fill: isActive ? "#ffffff" : "#cccccc",
    fontFamily: "Arial, sans-serif",
  });

  const alignOptions = [
    { key: "left" as const, label: "Left" },
    { key: "center" as const, label: "Center" },
    { key: "right" as const, label: "Right" },
  ];

  return (
    <pixiContainer
      position={{ x: 0, y: compensatedRuFontSize * 1.1 + 15 / scale }}
      zIndex={999999999999999999}
      eventMode="static"
      onPointerOver={(e: FederatedMouseEvent) => {
        e.stopPropagation();
        onControlPointerEnter();
      }}
      onPointerOut={(e: FederatedMouseEvent) => {
        e.stopPropagation();
        onControlPointerLeave();
      }}
      onPointerDown={(e: FederatedMouseEvent) => {
        e.stopPropagation();
      }}
    >
      {/* Основной фон */}
      <pixiGraphics draw={drawBg} />

      {/* Кнопки с подсветкой */}
      {alignOptions.map((option, index) => (
        <pixiContainer key={option.key}>
          {/* Подсветка активной кнопки */}
          <pixiGraphics
            draw={(g: Graphics) =>
              drawButtonHighlight(g, index, option.key === currentAlign)
            }
          />

          {/* Текст кнопки */}
          <pixiText
            text={option.label}
            anchor={{ x: 0.5, y: 0.5 }}
            position={{
              x: -controlWidth / 2 + buttonWidth * (index + 0.5),
              y: controlHeight / 2,
            }}
            style={getTextStyle(option.key === currentAlign)}
            eventMode="static"
            cursor="pointer"
            onClick={(e: FederatedMouseEvent) => {
              e.stopPropagation();
              onAlignChange(option.key);
            }}
            onPointerDown={(e: FederatedMouseEvent) => {
              e.stopPropagation();
              onAlignChange(option.key);
            }}
            onPointerOver={(e: FederatedMouseEvent) => {
              e.stopPropagation();
              onControlPointerEnter();
            }}
          />
        </pixiContainer>
      ))}
    </pixiContainer>
  );
};

const StationLabel = observer(
  ({
    station,
    ruLabel,

    labelAlign: labelAlignProp = "center",
    onLabelAlignChange,
    onTextHover,
  }: Readonly<StationLabelProps>) => {
    const { language } = languageStore;
    const { rotation, scale } = useTransform();
    const { setStationOffset, setStationAlign } = useMapData();

    const [position, setPosition] = useState({ x: 0, y: 0 });
    const [isDragging, setIsDragging] = useState(false);
    const [isPointerDown, setIsPointerDown] = useState(false);
    const [isHovered, setIsHovered] = useState(false);
    const [isControlHovered, setIsControlHovered] = useState(false);
    const [currentLabelAlign, setCurrentLabelAlign] = useState(labelAlignProp);
    const [ruLabelWidth, setRuLabelWidth] = useState(0);

    const dragStartPos = useRef({ x: 0, y: 0 });
    const mouseStartPos = useRef({ x: 0, y: 0 });
    const hideTimer = useRef<NodeJS.Timeout | null>(null);
    const ruLabelRef = useRef<any>(null);

    useEffect(() => {
      return () => {
        if (hideTimer.current) {
          clearTimeout(hideTimer.current);
        }
      };
    }, []);

    const handlePointerEnter = () => {
      if (hideTimer.current) {
        clearTimeout(hideTimer.current);
        hideTimer.current = null;
      }
      setIsHovered(true);
      onTextHover?.(true);
    };

    const handleControlPointerEnter = () => {
      if (hideTimer.current) {
        clearTimeout(hideTimer.current);
        hideTimer.current = null;
      }
      setIsControlHovered(true);
      setIsHovered(true);
      onTextHover?.(true);
    };

    const handleControlPointerLeave = () => {
      setIsControlHovered(false);

      if (!isHovered) {
        hideTimer.current = setTimeout(() => {
          setIsHovered(false);
          onTextHover?.(false);
        }, 0);
      }
    };

    const handlePointerLeave = () => {
      hideTimer.current = setTimeout(() => {
        setIsHovered(false);

        if (!isControlHovered) {
          setIsControlHovered(false);
        }
        onTextHover?.(false);
      }, 100);
    };

    useEffect(() => {
      setPosition({ x: station.offset_x ?? 0, y: station.offset_y ?? 0 });
    }, [station.offset_x, station.offset_y, station.id]);

    const convertNumericAlign = (align: number): LabelAlign => {
      switch (align) {
        case 0:
          return "left";
        case 1:
          return "center";
        case 2:
          return "right";
        default:
          return "center";
      }
    };

    const convertStringAlign = (align: LabelAlign): number => {
      switch (align) {
        case "left":
          return 0;
        case "center":
          return 1;
        case "right":
          return 2;
        default:
          return 1;
      }
    };

    useEffect(() => {
      setCurrentLabelAlign(convertNumericAlign(station.align ?? 1));
    }, [station.align]);

    if (!station) return null;

    const coordinates = coordinatesToLocal(station.latitude, station.longitude);
    const compensatedRuFontSize = (26 * 0.75) / scale;
    const compensatedNameFontSize = (16 * 0.75) / scale;

    useEffect(() => {
      if (ruLabelRef.current && ruLabel) {
        setRuLabelWidth(ruLabelRef.current.width);
      }
    }, [ruLabel, compensatedRuFontSize]);

    const handlePointerDown = (e: FederatedMouseEvent) => {
      setIsPointerDown(true);
      setIsDragging(false);
      dragStartPos.current = { ...position };
      mouseStartPos.current = { x: e.global.x, y: e.global.y };
      e.stopPropagation();
    };

    const handlePointerMove = (e: FederatedMouseEvent) => {
      if (!isPointerDown) return;

      if (!isDragging) {
        const dx = e.global.x - mouseStartPos.current.x;
        const dy = e.global.y - mouseStartPos.current.y;
        if (Math.hypot(dx, dy) > 3) setIsDragging(true);
        else return;
      }

      const dx_screen = e.global.x - mouseStartPos.current.x;
      const dy_screen = e.global.y - mouseStartPos.current.y;

      const newPosition = {
        x: dragStartPos.current.x + dx_screen,
        y: dragStartPos.current.y + dy_screen,
      };

      if (
        Math.abs(newPosition.x - position.x) > 0.01 ||
        Math.abs(newPosition.y - position.y) > 0.01
      ) {
        setPosition(newPosition);
        setStationOffset(station.id, newPosition.x, newPosition.y);
      }
      e.stopPropagation();
    };

    const handlePointerUp = (e: FederatedMouseEvent) => {
      setIsPointerDown(false);
      setTimeout(() => setIsDragging(false), 50);
      e.stopPropagation();
    };

    const handleAlignChange = async (align: LabelAlign) => {
      setCurrentLabelAlign(align);
      onLabelAlignChange?.(align);

      const numericAlign = convertStringAlign(align);
      setStationAlign(station.id, numericAlign);
    };

    const dynamicAnchor = useMemo(
      () => getAnchorFromOffset(position.x, position.y),
      [position.x, position.y]
    );

    const getSecondLabelPosition = (): number => {
      if (!ruLabelWidth) return 0;

      switch (currentLabelAlign) {
        case "left":
          return -ruLabelWidth / 2;
        case "center":
          return 0;
        case "right":
          return ruLabelWidth / 2;
        default:
          return 0;
      }
    };

    const getSecondLabelAnchor = (): number => {
      switch (currentLabelAlign) {
        case "left":
          return 0;
        case "center":
          return 0.5;
        case "right":
          return 1;
        default:
          return 0.5;
      }
    };

    return (
      <pixiContainer
        x={coordinates.x * UP_SCALE}
        y={coordinates.y * UP_SCALE}
        rotation={-rotation}
        zIndex={isHovered || isControlHovered ? 1000 : 0}
        eventMode="static"
        interactive
        cursor={isDragging ? "grabbing" : "grab"}
        onPointerOver={handlePointerEnter}
        onPointerOut={handlePointerLeave}
        onPointerDown={handlePointerDown}
        onPointerUp={handlePointerUp}
        onPointerUpOutside={handlePointerUp}
        onGlobalPointerMove={handlePointerMove}
      >
        <pixiContainer
          position={{
            x:
              (position.x + Math.cos(Math.atan2(position.y, position.x))) /
              scale,
            y:
              (position.y + Math.sin(Math.atan2(position.y, position.x))) /
              scale,
          }}
          anchor={dynamicAnchor}
          zIndex={isHovered || isControlHovered ? 1000 : 0}
        >
          {ruLabel && (
            <pixiText
              ref={ruLabelRef}
              text={ruLabel}
              position={{ x: 0, y: 0 }}
              anchor={{ x: 0.5, y: 0.5 }}
              style={{
                fontSize: compensatedRuFontSize,
                fontWeight: "bold",
                fill: "#ffffff",
              }}
            />
          )}
          {station.name && language !== "ru" && ruLabel && (
            <pixiText
              text={station.name}
              position={{
                x: getSecondLabelPosition(),
                y: compensatedRuFontSize * 1.1,
              }}
              anchor={{ x: getSecondLabelAnchor(), y: 0.5 }}
              style={{
                fontSize: compensatedNameFontSize,
                fontWeight: "bold",
                fill: "#CCCCCC",
              }}
            />
          )}
          {(isHovered || isControlHovered) && !isDragging && (
            <LabelAlignmentControl
              scale={scale}
              currentAlign={currentLabelAlign}
              onAlignChange={handleAlignChange}
              onPointerOver={handlePointerEnter}
              onPointerOut={handlePointerLeave}
              onControlPointerEnter={handleControlPointerEnter}
              onControlPointerLeave={handleControlPointerLeave}
            />
          )}
        </pixiContainer>
      </pixiContainer>
    );
  }
);

export const Station = ({
  station,
  ruLabel,

  labelAlign,
  onLabelAlignChange,
}: Readonly<StationProps>) => {
  const [isTextHovered, setIsTextHovered] = useState(false);

  const draw = useCallback(
    (g: Graphics) => {
      g.clear();
      const coordinates = coordinatesToLocal(
        station.latitude,
        station.longitude
      );

      const radius = STATION_RADIUS;
      const strokeWidth = STATION_OUTLINE_WIDTH;

      g.circle(coordinates.x * UP_SCALE, coordinates.y * UP_SCALE, radius);

      if (isTextHovered) {
        g.fill({ color: 0x00aaff });
        g.stroke({ color: 0xffffff, width: strokeWidth + 1 });
      } else {
        g.fill({ color: PATH_COLOR });
        g.stroke({ color: BACKGROUND_COLOR, width: strokeWidth });
      }
    },
    [station.latitude, station.longitude, isTextHovered]
  );

  return (
    <pixiContainer zIndex={isTextHovered ? 1000 : 0}>
      <pixiGraphics draw={draw} />
      <StationLabel
        station={station}
        ruLabel={ruLabel}
        labelAlign={labelAlign}
        onLabelAlignChange={onLabelAlignChange}
        onTextHover={setIsTextHovered}
      />
    </pixiContainer>
  );
};
