import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { flushSync } from "react-dom";
import type { PointerEvent as ReactPointerEvent, CSSProperties } from "react";
import { observer } from "mobx-react-lite";
import { useMapData } from "../MapDataContext";
import { AlignLeft, AlignCenter, AlignRight } from "lucide-react";
import { useTransform } from "../TransformContext";
import { coordinatesToLocal, localToCoordinates } from "../utils";
import { BACKGROUND_COLOR, SCALE_FACTOR, UP_SCALE } from "../Constants";
import { languageStore } from "@shared";
import { SightData } from "../types";
import { isMediaIdEmpty } from "../../../../shared/lib/index";

const SIGHT_ICON_URL = "/sight_icon.svg";

const buttons = [
  { label: <AlignLeft size={16} />, value: 1, align: "left" },
  {
    label: <AlignCenter size={16} />,
    value: 2,
    align: "center",
  },
  { label: <AlignRight size={16} />, value: 3, align: "right" },
];

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

type Transform = {
  scale: number;
  translation: Vec2;
};

type ProgramBundle = {
  program: WebGLProgram;
  attribLocations: {
    a_position: number;
  };
  uniformLocations: {
    u_translation: WebGLUniformLocation | null;
    u_scale: WebGLUniformLocation | null;
    u_resolution: WebGLUniformLocation | null;
    u_color: WebGLUniformLocation | null;
    u_pointSize?: WebGLUniformLocation | null;
  };
};

const clamp = (value: number, min: number, max: number) =>
  Math.min(max, Math.max(min, value));

const toColor = (value: number): [number, number, number] => [
  ((value >> 16) & 0xff) / 255,
  ((value >> 8) & 0xff) / 255,
  (value & 0xff) / 255,
];

const lineVertexSource = `
  attribute vec2 a_position;
  uniform vec2 u_translation;
  uniform float u_scale;
  uniform vec2 u_resolution;

  void main() {
    vec2 screen = a_position * u_scale + u_translation;
    vec2 zeroToOne = screen / u_resolution;
    vec2 clip = zeroToOne * 2.0 - 1.0;
    gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
  }
`;

const lineFragmentSource = `
  precision mediump float;
  uniform vec4 u_color;

  void main() {
    gl_FragColor = u_color;
  }
`;

const pointVertexSource = `
  attribute vec2 a_position;
  uniform vec2 u_translation;
  uniform float u_scale;
  uniform vec2 u_resolution;
  uniform float u_pointSize;

  void main() {
    vec2 screen = a_position * u_scale + u_translation;
    vec2 zeroToOne = screen / u_resolution;
    vec2 clip = zeroToOne * 2.0 - 1.0;
    gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
    gl_PointSize = u_pointSize;
  }
`;

const pointFragmentSource = `
  precision mediump float;
  uniform vec4 u_color;

  void main() {
    vec2 centered = gl_PointCoord * 2.0 - 1.0;
    float dist = dot(centered, centered);
    if (dist > 1.0) {
      discard;
    }
    gl_FragColor = u_color;
  }
`;

function compileShader(
  gl: WebGLRenderingContext,
  type: number,
  source: string,
) {
  const shader = gl.createShader(type);
  if (!shader) {
    throw new Error("Failed to create shader");
  }
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    const info = gl.getShaderInfoLog(shader);
    gl.deleteShader(shader);
    throw new Error(`Shader compilation failed: ${info ?? "unknown error"}`);
  }
  return shader;
}

function createProgram(
  gl: WebGLRenderingContext,
  vertexSource: string,
  fragmentSource: string,
): ProgramBundle {
  const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexSource);
  const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSource);

  const program = gl.createProgram();
  if (!program) {
    throw new Error("Failed to create program");
  }

  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);

  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    const info = gl.getProgramInfoLog(program);
    gl.deleteProgram(program);
    throw new Error(`Program link failed: ${info ?? "unknown error"}`);
  }

  const attribLocations = {
    a_position: gl.getAttribLocation(program, "a_position"),
  };

  const uniformLocations = {
    u_translation: gl.getUniformLocation(program, "u_translation"),
    u_scale: gl.getUniformLocation(program, "u_scale"),
    u_resolution: gl.getUniformLocation(program, "u_resolution"),
    u_color: gl.getUniformLocation(program, "u_color"),
    u_pointSize: gl.getUniformLocation(program, "u_pointSize"),
  };

  return { program, attribLocations, uniformLocations };
}

const computeWorldVertices = (path?: [number, number][]): Float32Array => {
  if (!path || path.length === 0) {
    return new Float32Array();
  }

  const verts: number[] = [];
  for (const [latitude, longitude] of path) {
    const local = coordinatesToLocal(latitude, longitude);
    verts.push(local.x * UP_SCALE, local.y * UP_SCALE);
  }
  return new Float32Array(verts);
};

const computeStationVertices = (
  stations?: Array<{ latitude: number; longitude: number }>,
): Float32Array => {
  if (!stations || stations.length === 0) {
    return new Float32Array();
  }

  const verts: number[] = [];
  for (const station of stations) {
    const local = coordinatesToLocal(station.latitude, station.longitude);
    verts.push(local.x * UP_SCALE, local.y * UP_SCALE);
  }
  return new Float32Array(verts);
};

const rotateVertices = (
  vertices: Float32Array,
  angle: number,
): Float32Array => {
  if (!vertices || vertices.length === 0 || Math.abs(angle) < 1e-6) {
    return vertices;
  }

  const cos = Math.cos(angle);
  const sin = Math.sin(angle);
  const rotated = new Float32Array(vertices.length);

  for (let i = 0; i < vertices.length; i += 2) {
    const x = vertices[i];
    const y = vertices[i + 1];
    rotated[i] = x * cos - y * sin;
    rotated[i + 1] = x * sin + y * cos;
  }

  return rotated;
};

const DRAG_THRESHOLD_PX = 4;
const ICON_SIZE_MIN = 1;
const ICON_SIZE_MAX = 300;
const DEBUG_WEBGL_ROUTE_MAP = true;

const debugWebglLog = (...args: unknown[]) => {
  if (!DEBUG_WEBGL_ROUTE_MAP) return;
  console.log("[WebGLRouteMapPrototype]", ...args);
};

type StationDragState = {
  stationId: number;
  pointerId: number;
  rotatedBase: Vec2;
  camera: Transform;
  captureTarget: HTMLElement | null;
  lastOffset: Vec2;
  initialPointer: Vec2;
  hasMoved: boolean;
  pointerDelta: Vec2;
};

type SightDragState = {
  sight: SightData;
  pointerId: number;
  camera: Transform;
  offset: Vec2;
  rotation: number;
  initialClient: Vec2;
  hasMoved: boolean;
  captureTarget: HTMLElement | null;
  lastCoordinates: { latitude: number; longitude: number };
  rotatedBase: Vec2;
  pointerDelta: Vec2;
};

type SightLivePosition = {
  latitude: number;
  longitude: number;
  offsetX: number;
  offsetY: number;
};

type StationIconResizeState = {
  stationId: number;
  pointerId: number;
  initialPointer: Vec2;
  baseIconSizePx: number;
  initialPercent: number;
  lastPercent: number;
  captureTarget: HTMLElement | null;
};

type SightIconResizeState = {
  sightId: number;
  pointerId: number;
  initialPointer: Vec2;
  initialPercent: number;
  initialSizePx: number;
  baseSizePxAt100: number;
  lastPercent: number;
  captureTarget: HTMLElement | null;
};

const generateThickLineGeometry = (
  points: Float32Array,
  width: number,
): Float32Array => {
  if (points.length < 4 || width <= 0) {
    return new Float32Array();
  }

  const halfWidth = width / 2;
  const vertices: number[] = [];

  for (let i = 0; i < points.length - 2; i += 2) {
    const x1 = points[i];
    const y1 = points[i + 1];
    const x2 = points[i + 2];
    const y2 = points[i + 3];

    const dx = x2 - x1;
    const dy = y2 - y1;
    const length = Math.hypot(dx, dy);
    if (length === 0) {
      continue;
    }

    const ux = dx / length;
    const uy = dy / length;
    const px = -uy * halfWidth;
    const py = ux * halfWidth;

    const v1x = x1 + px;
    const v1y = y1 + py;
    const v2x = x1 - px;
    const v2y = y1 - py;
    const v3x = x2 + px;
    const v3y = y2 + py;
    const v4x = x2 - px;
    const v4y = y2 - py;

    vertices.push(v1x, v1y, v2x, v2y, v3x, v3y);
    vertices.push(v2x, v2y, v4x, v4y, v3x, v3y);

    if (i < points.length - 4) {
      const x3 = points[i + 4];
      const y3 = points[i + 5];
      const dx2 = x3 - x2;
      const dy2 = y3 - y2;
      const length2 = Math.hypot(dx2, dy2);
      if (length2 > 0) {
        const ux2 = dx2 / length2;
        const uy2 = dy2 / length2;
        const px2 = -uy2 * halfWidth;
        const py2 = ux2 * halfWidth;

        const joint1x = x2 + px;
        const joint1y = y2 + py;
        const joint2x = x2 - px;
        const joint2y = y2 - py;
        const joint3x = x2 + px2;
        const joint3y = y2 + py2;
        const joint4x = x2 - px2;
        const joint4y = y2 - py2;

        vertices.push(joint1x, joint1y, joint2x, joint2y, joint3x, joint3y);
        vertices.push(joint2x, joint2y, joint4x, joint4y, joint3x, joint3y);
      }
    }
  }

  return new Float32Array(vertices);
};

const computeViewTransform = (
  vertices: Float32Array,
  width: number,
  height: number,
): Transform => {
  if (vertices.length < 4) {
    return {
      scale: 1,
      translation: { x: width * 0.5, y: height * 0.5 },
    };
  }

  let minX = Infinity;
  let minY = Infinity;
  let maxX = -Infinity;
  let maxY = -Infinity;

  for (let i = 0; i < vertices.length; i += 2) {
    const x = vertices[i];
    const y = vertices[i + 1];
    if (x < minX) minX = x;
    if (y < minY) minY = y;
    if (x > maxX) maxX = x;
    if (y > maxY) maxY = y;
  }

  const worldWidth = Math.max(1, maxX - minX);
  const worldHeight = Math.max(1, maxY - minY);

  const padding = 0.1;
  const scale = Math.min(
    (width * (1 - padding)) / worldWidth,
    (height * (1 - padding)) / worldHeight,
  );

  const centerX = (minX + maxX) * 0.5;
  const centerY = (minY + maxY) * 0.5;

  const translation = {
    x: width * 0.5 - centerX * scale,
    y: height * 0.5 - centerY * scale,
  };

  return { scale, translation };
};

const getAnchorFromOffset = (align: number): { x: number; y: number } => {
  let anchorX: number;
  if (align === 1) {
    anchorX = 0;
  } else if (align === 3) {
    anchorX = 1;
  } else {
    anchorX = 0.5;
  }

  const anchorY = 0.5;

  return { x: anchorX, y: anchorY };
};

const backgroundColor = toColor(BACKGROUND_COLOR);

const pathColor = toColor(0xed1c24);

export const WebGLRouteMapPrototype = observer(() => {
  const {
    originalRouteData,
    routeData,
    stationData,
    sightData,
    selectedSight,
    setSelectedSight,
    setStationOffset,
    setStationAlign,
    setStationIconSize,
    setSightCoordinates,
    setSightIconSize,
    setMapCenter,
  } = useMapData();
  const { language } = languageStore;
  const { setScale: setSharedScale, scale: sharedScale } = useTransform();

  const containerRef = useRef<HTMLDivElement | null>(null);
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const glRef = useRef<WebGLRenderingContext | null>(null);

  const lineProgramRef = useRef<ProgramBundle | null>(null);
  const lineBufferRef = useRef<WebGLBuffer | null>(null);
  const pointProgramRef = useRef<ProgramBundle | null>(null);
  const pointBufferRef = useRef<WebGLBuffer | null>(null);

  const transformRef = useRef<Transform | null>(null);
  const lastTransformRef = useRef<Transform | null>(null);
  const [transformState, setTransformState] = useState<Transform | null>(null);

  const clampTransformScale = useCallback((transform: Transform): Transform => {
    const { min, max } = scaleLimitsRef.current;
    const clampedScale = clamp(transform.scale, min, max);
    if (clampedScale === transform.scale) {
      lastTransformRef.current = transform;
      return transform;
    }

    const canvas = canvasRef.current;
    if (!canvas || canvas.width === 0 || canvas.height === 0) {
      const adjusted = { ...transform, scale: clampedScale };
      lastTransformRef.current = adjusted;
      return adjusted;
    }

    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;
    const worldCenterX = (centerX - transform.translation.x) / transform.scale;
    const worldCenterY = (centerY - transform.translation.y) / transform.scale;

    const adjusted = {
      scale: clampedScale,
      translation: {
        x: centerX - worldCenterX * clampedScale,
        y: centerY - worldCenterY * clampedScale,
      },
    };

    lastTransformRef.current = adjusted;
    return adjusted;
  }, []);

  const scaleLimitsRef = useRef({ min: 0.1, max: 100 });
  const drawSceneRef = useRef<() => void>(() => {});

  const activePointersRef = useRef<Map<number, Vec2>>(new Map());
  const dragStateRef = useRef<{ lastPos: Vec2 } | null>(null);
  const pinchStateRef = useRef<{
    initialDistance: number;
    initialScale: number;
    worldMidpoint: Vec2;
  } | null>(null);

  const stationDragStateRef = useRef<StationDragState | null>(null);
  const sightDragStateRef = useRef<SightDragState | null>(null);
  const stationIconResizeStateRef = useRef<StationIconResizeState | null>(null);
  const sightIconResizeStateRef = useRef<SightIconResizeState | null>(null);
  const customSightIconBaseScaleRef = useRef<number | null>(null);
  const suppressAutoFitRef = useRef(false);
  const skipNextAutoFitRef = useRef(false);
  const [liveStationOffsets, setLiveStationOffsets] = useState<
    Map<number, Vec2>
  >(new Map());
  const [liveSightPositions, setLiveSightPositions] = useState<
    Map<number, SightLivePosition>
  >(new Map());
  const [liveStationIconSizes, setLiveStationIconSizes] = useState<
    Map<number, number>
  >(new Map());
  const [liveSightIconSizes, setLiveSightIconSizes] = useState<
    Map<number, number>
  >(new Map());
  const [hoveredStationIconId, setHoveredStationIconId] = useState<
    number | null
  >(null);
  const [hoveredSightIconId, setHoveredSightIconId] = useState<number | null>(
    null,
  );
  const [resizingStationIconId, setResizingStationIconId] = useState<
    number | null
  >(null);
  const [resizingSightIconId, setResizingSightIconId] = useState<number | null>(
    null,
  );
  type StationAlignment = "left" | "center" | "right";
  const [stationAlignments, setStationAlignments] = useState<
    Map<number, StationAlignment>
  >(new Map());
  const [hoveredStationId, setHoveredStationId] = useState<number | null>(null);
  const lastCenterRef = useRef<{
    latitude: number | null;
    longitude: number | null;
  }>({ latitude: null, longitude: null });
  const lastAppliedCenterRef = useRef<{
    latitude: number | null;
    longitude: number | null;
  }>({ latitude: null, longitude: null });
  const pendingCenterRef = useRef<{
    latitude: number;
    longitude: number;
  } | null>(null);
  const isUserInteractingRef = useRef(false);
  const commitCenterTimeoutRef = useRef<number | null>(null);

  const getRelativePointerPosition = useCallback(
    (clientX: number, clientY: number) => {
      const container = containerRef.current;
      if (!container) return null;
      const rect = container.getBoundingClientRect();
      return {
        x: clientX - rect.left,
        y: clientY - rect.top,
      };
    },
    [],
  );

  const getWorldPosition = useCallback(
    (clientX: number, clientY: number, camera: Transform) => {
      const relative = getRelativePointerPosition(clientX, clientY);
      if (!relative) return null;
      const dpr = Math.max(1, window.devicePixelRatio || 1);
      return {
        x: (relative.x * dpr - camera.translation.x) / camera.scale,
        y: (relative.y * dpr - camera.translation.y) / camera.scale,
      };
    },
    [getRelativePointerPosition],
  );

  const [canvasSize, setCanvasSize] = useState<{
    width: number;
    height: number;
  }>({ width: 0, height: 0 });

  const routeVertices = useMemo(
    () => computeWorldVertices(originalRouteData?.path),
    [originalRouteData?.path],
  );

  const stationVertices = useMemo(
    () =>
      computeStationVertices(
        stationData?.ru?.map((station) => ({
          latitude: station.latitude,
          longitude: station.longitude,
        })),
      ),
    [stationData?.ru],
  );

  const rotationAngle = useMemo(() => {
    const deg = routeData?.rotate ?? originalRouteData?.rotate ?? 0;
    return (deg * Math.PI) / 180;
  }, [routeData?.rotate, originalRouteData?.rotate]);
  const mediaBaseUrl = import.meta.env.VITE_KRBL_MEDIA;
  const mediaToken = localStorage.getItem("token") ?? "";

  useEffect(() => {
    customSightIconBaseScaleRef.current = null;
  }, [originalRouteData?.id]);

  useEffect(() => {
    const camera =
      transformState ?? transformRef.current ?? lastTransformRef.current;
    if (
      customSightIconBaseScaleRef.current == null &&
      camera?.scale &&
      Number.isFinite(camera.scale) &&
      camera.scale > 0
    ) {
      customSightIconBaseScaleRef.current = camera.scale;
    }
  }, [transformState]);

  const computeCenterCoordinates = useCallback(
    (transform: Transform) => {
      const canvas = canvasRef.current;
      if (!canvas) return;

      const width = canvas.width || canvas.clientWidth;
      const height = canvas.height || canvas.clientHeight;
      if (!width || !height) return;

      const worldX = (width / 2 - transform.translation.x) / transform.scale;
      const worldY = (height / 2 - transform.translation.y) / transform.scale;

      const cos = Math.cos(rotationAngle);
      const sin = Math.sin(rotationAngle);
      const unrotatedX = worldX * cos + worldY * sin;
      const unrotatedY = -worldX * sin + worldY * cos;

      const localX = unrotatedX / UP_SCALE;
      const localY = unrotatedY / UP_SCALE;

      const { latitude, longitude } = localToCoordinates(localX, localY);
      if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
        return;
      }

      const roundedLat = Math.round(latitude * 1e6) / 1e6;
      const roundedLon = Math.round(longitude * 1e6) / 1e6;

      lastCenterRef.current = {
        latitude: roundedLat,
        longitude: roundedLon,
      };
    },
    [rotationAngle],
  );

  const cancelScheduledCenterCommit = useCallback(() => {
    if (commitCenterTimeoutRef.current !== null) {
      window.clearTimeout(commitCenterTimeoutRef.current);
      commitCenterTimeoutRef.current = null;
    }
  }, []);

  const commitCenter = useCallback(() => {
    const center = lastCenterRef.current;
    if (
      !center ||
      center.latitude == null ||
      center.longitude == null ||
      !Number.isFinite(center.latitude) ||
      !Number.isFinite(center.longitude)
    ) {
      return;
    }

    const epsilon = 1e-7;
    const prev = lastAppliedCenterRef.current;
    if (
      prev.latitude != null &&
      prev.longitude != null &&
      Math.abs(prev.latitude - center.latitude) < epsilon &&
      Math.abs(prev.longitude - center.longitude) < epsilon
    ) {
      return;
    }

    lastAppliedCenterRef.current = {
      latitude: center.latitude,
      longitude: center.longitude,
    };
    setMapCenter(center.latitude, center.longitude);
  }, [setMapCenter]);

  const scheduleCenterCommit = useCallback(() => {
    cancelScheduledCenterCommit();
    commitCenterTimeoutRef.current = window.setTimeout(() => {
      commitCenterTimeoutRef.current = null;
      isUserInteractingRef.current = false;
      commitCenter();
    }, 120);
  }, [cancelScheduledCenterCommit, commitCenter]);

  useEffect(() => {
    if (hoveredStationId == null) {
      return;
    }

    const handleKeyDown = (event: KeyboardEvent) => {
      const key = event.key;
      let nextAlignment: StationAlignment | null = null;
      let alignNumber: number | null = null;

      if (key === "1") {
        nextAlignment = "left";
        alignNumber = 1;
      } else if (key === "2") {
        nextAlignment = "center";
        alignNumber = 2;
      } else if (key === "3") {
        nextAlignment = "right";
        alignNumber = 3;
      }

      if (!nextAlignment || alignNumber === null) {
        return;
      }

      setStationAlignments((prev) => {
        const next = new Map(prev);
        next.set(hoveredStationId, nextAlignment as StationAlignment);
        return next;
      });
      setStationAlign(hoveredStationId, alignNumber);
    };

    window.addEventListener("keydown", handleKeyDown);
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, [hoveredStationId, setStationAlignments, setStationAlign]);

  useEffect(() => {
    return () => {
      cancelScheduledCenterCommit();
    };
  }, [cancelScheduledCenterCommit]);

  const updateTransform = useCallback(
    (
      next: Transform,
      options?: { immediate?: boolean; skipClamp?: boolean },
    ) => {
      const prevTransform = transformRef.current;
      const adjusted = options?.skipClamp ? next : clampTransformScale(next);
      debugWebglLog("updateTransform", {
        immediate: Boolean(options?.immediate),
        skipClamp: Boolean(options?.skipClamp),
        prevScale: prevTransform?.scale ?? null,
        nextScale: adjusted.scale,
        prevTranslation: prevTransform?.translation ?? null,
        nextTranslation: adjusted.translation,
      });

      transformRef.current = adjusted;
      if (options?.immediate) {
        flushSync(() => {
          setTransformState(adjusted);
          setSharedScale(adjusted.scale);
        });
      } else {
        setTransformState(adjusted);
        setSharedScale(adjusted.scale);
      }
      computeCenterCoordinates(adjusted);
    },
    [clampTransformScale, setSharedScale, computeCenterCoordinates],
  );

  const rotatedRouteVertices = useMemo(
    () => rotateVertices(routeVertices, rotationAngle),
    [routeVertices, rotationAngle],
  );

  const rotatedStationVertices = useMemo(
    () => rotateVertices(stationVertices, rotationAngle),
    [stationVertices, rotationAngle],
  );

  const resetTransform = useCallback(() => {
    transformRef.current = null;
    setTransformState(null);
  }, []);

  const handleStationPointerMove = useCallback(
    (event: PointerEvent) => {
      const state = stationDragStateRef.current;
      if (!state || event.pointerId !== state.pointerId) return;

      if (!state.hasMoved) {
        const pointerDx = event.clientX - state.initialPointer.x;
        const pointerDy = event.clientY - state.initialPointer.y;
        if (Math.hypot(pointerDx, pointerDy) >= DRAG_THRESHOLD_PX) {
          state.hasMoved = true;
        }
      }

      if (!state.hasMoved) {
        return;
      }

      event.preventDefault();

      const stationScreenX =
        state.rotatedBase.x * state.camera.scale + state.camera.translation.x;
      const stationScreenY =
        state.rotatedBase.y * state.camera.scale + state.camera.translation.y;

      const canvas = canvasRef.current;
      if (!canvas) return;
      const rect = canvas.getBoundingClientRect();
      const scaleX = canvas.width / Math.max(rect.width, 1);
      const scaleY = canvas.height / Math.max(rect.height, 1);
      const pointerScreenX = (event.clientX - rect.left) * scaleX;
      const pointerScreenY = (event.clientY - rect.top) * scaleY;

      const newOffsetX = pointerScreenX - stationScreenX - state.pointerDelta.x;
      const newOffsetY = pointerScreenY - stationScreenY - state.pointerDelta.y;

      state.lastOffset = { x: newOffsetX, y: newOffsetY };
      setLiveStationOffsets((prev) => {
        const next = new Map(prev);
        next.set(state.stationId, { x: newOffsetX, y: newOffsetY });
        return next;
      });
    },
    [getWorldPosition, setLiveStationOffsets],
  );

  const handleStationPointerUp = useCallback(
    (event: PointerEvent) => {
      const state = stationDragStateRef.current;
      if (!state || event.pointerId !== state.pointerId) return;

      if (state.captureTarget && state.captureTarget.releasePointerCapture) {
        state.captureTarget.releasePointerCapture(event.pointerId);
        state.captureTarget.style.cursor = "grab";
      }

      setLiveStationOffsets((prev) => {
        if (!prev.has(state.stationId)) return prev;
        const next = new Map(prev);
        next.delete(state.stationId);
        return next;
      });

      if (state.hasMoved) {
        setStationOffset(
          state.stationId,
          state.lastOffset.x,
          state.lastOffset.y,
        );
      }

      suppressAutoFitRef.current = false;
      skipNextAutoFitRef.current = true;

      stationDragStateRef.current = null;
      window.removeEventListener("pointermove", handleStationPointerMove);
      window.removeEventListener("pointerup", handleStationPointerUp);
      window.removeEventListener("pointercancel", handleStationPointerUp);
      if (typeof document !== "undefined") {
        document.body.style.cursor = "";
      }
    },
    [handleStationPointerMove, setLiveStationOffsets, setStationOffset],
  );

  const handleStationPointerDown = useCallback(
    (
      event: ReactPointerEvent<HTMLDivElement>,
      stationId: number,
      rotatedBase: Vec2,
      currentOffset: Vec2,
    ) => {
      event.preventDefault();
      event.stopPropagation();

      const camera =
        transformState ?? transformRef.current ?? lastTransformRef.current;
      if (!camera) return;

      suppressAutoFitRef.current = true;

      const stationScreenX =
        rotatedBase.x * camera.scale + camera.translation.x;
      const stationScreenY =
        rotatedBase.y * camera.scale + camera.translation.y;
      const labelScreenX = stationScreenX + currentOffset.x;
      const labelScreenY = stationScreenY + currentOffset.y;

      const canvas = canvasRef.current;
      if (!canvas) return;
      const rect = canvas.getBoundingClientRect();
      const scaleX = canvas.width / Math.max(rect.width, 1);
      const scaleY = canvas.height / Math.max(rect.height, 1);
      const pointerScreenX = (event.clientX - rect.left) * scaleX;
      const pointerScreenY = (event.clientY - rect.top) * scaleY;

      const pointerDelta = {
        x: pointerScreenX - labelScreenX,
        y: pointerScreenY - labelScreenY,
      };

      const captureTarget = event.currentTarget;
      if (captureTarget.setPointerCapture) {
        captureTarget.setPointerCapture(event.pointerId);
      }
      captureTarget.style.cursor = "grabbing";

      stationDragStateRef.current = {
        stationId,
        pointerId: event.pointerId,
        rotatedBase,
        camera,
        captureTarget,
        lastOffset: currentOffset,
        initialPointer: { x: event.clientX, y: event.clientY },
        hasMoved: false,
        pointerDelta,
      };

      setLiveStationOffsets((prev) => {
        const next = new Map(prev);
        next.set(stationId, currentOffset);
        return next;
      });

      if (typeof document !== "undefined") {
        document.body.style.cursor = "grabbing";
      }
      window.addEventListener("pointermove", handleStationPointerMove);
      window.addEventListener("pointerup", handleStationPointerUp);
      window.addEventListener("pointercancel", handleStationPointerUp);
    },
    [
      handleStationPointerMove,
      handleStationPointerUp,
      transformState,
      getWorldPosition,
      setLiveStationOffsets,
    ],
  );

  const handleSightPointerMove = useCallback(
    (event: PointerEvent) => {
      const state = sightDragStateRef.current;
      if (!state || event.pointerId !== state.pointerId) return;

      event.preventDefault();

      const world = getWorldPosition(
        event.clientX,
        event.clientY,
        state.camera,
      );
      if (!world) return;

      if (!state.hasMoved) {
        const dx = event.clientX - state.initialClient.x;
        const dy = event.clientY - state.initialClient.y;
        if (Math.hypot(dx, dy) >= DRAG_THRESHOLD_PX) {
          state.hasMoved = true;
        }
      }

      if (!state.hasMoved) {
        return;
      }

      const adjustedWorldX = world.x - state.pointerDelta.x;
      const adjustedWorldY = world.y - state.pointerDelta.y;

      const newRotatedBaseX = adjustedWorldX - state.offset.x;
      const newRotatedBaseY = adjustedWorldY - state.offset.y;

      const cos = Math.cos(state.rotation);
      const sin = Math.sin(state.rotation);

      const baseX = newRotatedBaseX * cos + newRotatedBaseY * sin;
      const baseY = -newRotatedBaseX * sin + newRotatedBaseY * cos;

      const localX = baseX / UP_SCALE;
      const localY = baseY / UP_SCALE;

      const coords = localToCoordinates(localX, localY);
      state.lastCoordinates = coords;
      state.rotatedBase = { x: newRotatedBaseX, y: newRotatedBaseY };
      setLiveSightPositions((prev) => {
        const next = new Map(prev);
        next.set(state.sight.id, {
          latitude: coords.latitude,
          longitude: coords.longitude,
          offsetX: state.offset.x,
          offsetY: state.offset.y,
        });
        return next;
      });
    },
    [getWorldPosition, setLiveSightPositions],
  );

  const handleSightPointerUp = useCallback(
    (event: PointerEvent) => {
      const state = sightDragStateRef.current;
      if (!state || event.pointerId !== state.pointerId) return;

      if (state.captureTarget && state.captureTarget.releasePointerCapture) {
        state.captureTarget.releasePointerCapture(event.pointerId);
        state.captureTarget.style.cursor = "grab";
      }

      window.removeEventListener("pointermove", handleSightPointerMove);
      window.removeEventListener("pointerup", handleSightPointerUp);
      window.removeEventListener("pointercancel", handleSightPointerUp);
      if (typeof document !== "undefined") {
        document.body.style.cursor = "";
      }

      if (state.hasMoved) {
        setSightCoordinates(
          state.sight.id,
          state.lastCoordinates.latitude,
          state.lastCoordinates.longitude,
        );
      } else {
        setSelectedSight(state.sight);
      }

      suppressAutoFitRef.current = false;
      skipNextAutoFitRef.current = true;

      sightDragStateRef.current = null;
    },
    [handleSightPointerMove, setSelectedSight, setSightCoordinates],
  );

  const handleSightPointerDown = useCallback(
    (
      event: ReactPointerEvent<HTMLDivElement>,
      sight: SightData,
      offset: Vec2,
      rotatedBase: Vec2,
      currentCoords: { latitude: number; longitude: number },
    ) => {
      event.preventDefault();
      event.stopPropagation();

      const camera =
        transformState ?? transformRef.current ?? lastTransformRef.current;
      if (!camera) return;

      suppressAutoFitRef.current = true;

      const pointerWorld = getWorldPosition(
        event.clientX,
        event.clientY,
        camera,
      );
      const labelWorldX = rotatedBase.x + offset.x;
      const labelWorldY = rotatedBase.y + offset.y;
      const pointerDelta = pointerWorld
        ? {
            x: pointerWorld.x - labelWorldX,
            y: pointerWorld.y - labelWorldY,
          }
        : { x: 0, y: 0 };

      const captureTarget = event.currentTarget;
      if (captureTarget.setPointerCapture) {
        captureTarget.setPointerCapture(event.pointerId);
      }
      captureTarget.style.cursor = "grabbing";

      sightDragStateRef.current = {
        sight,
        pointerId: event.pointerId,
        camera,
        offset,
        rotation: rotationAngle,
        initialClient: { x: event.clientX, y: event.clientY },
        hasMoved: false,
        captureTarget,
        lastCoordinates: currentCoords,
        rotatedBase,
        pointerDelta,
      };

      if (typeof document !== "undefined") {
        document.body.style.cursor = "grabbing";
      }
      window.addEventListener("pointermove", handleSightPointerMove);
      window.addEventListener("pointerup", handleSightPointerUp);
      window.addEventListener("pointercancel", handleSightPointerUp);
    },
    [
      handleSightPointerMove,
      handleSightPointerUp,
      rotationAngle,
      transformState,
      getWorldPosition,
    ],
  );

  const handleStationIconResizeMove = useCallback((event: PointerEvent) => {
    const state = stationIconResizeStateRef.current;
    if (!state || event.pointerId !== state.pointerId) return;

    event.preventDefault();

    const dx = event.clientX - state.initialPointer.x;
    const dy = event.clientY - state.initialPointer.y;
    const deltaPx = (dx + dy) * 0.5;
    const initialSizePx = state.baseIconSizePx * (state.initialPercent / 100);
    const nextSizePx = Math.max(1, initialSizePx + deltaPx);
    const nextPercent = clamp(
      Math.round((nextSizePx / Math.max(1, state.baseIconSizePx)) * 100),
      ICON_SIZE_MIN,
      ICON_SIZE_MAX,
    );

    if (nextPercent === state.lastPercent) {
      return;
    }

    state.lastPercent = nextPercent;
    setLiveStationIconSizes((prev) => {
      const next = new Map(prev);
      next.set(state.stationId, nextPercent);
      return next;
    });
  }, []);

  const handleStationIconResizeUp = useCallback(
    (event: PointerEvent) => {
      const state = stationIconResizeStateRef.current;
      if (!state || event.pointerId !== state.pointerId) return;

      if (state.captureTarget?.releasePointerCapture) {
        state.captureTarget.releasePointerCapture(event.pointerId);
      }

      window.removeEventListener("pointermove", handleStationIconResizeMove);
      window.removeEventListener("pointerup", handleStationIconResizeUp);
      window.removeEventListener("pointercancel", handleStationIconResizeUp);

      setLiveStationIconSizes((prev) => {
        if (!prev.has(state.stationId)) return prev;
        const next = new Map(prev);
        next.delete(state.stationId);
        return next;
      });

      if (state.lastPercent !== state.initialPercent) {
        setStationIconSize(state.stationId, state.lastPercent);
      }

      stationIconResizeStateRef.current = null;
      setResizingStationIconId(null);
      if (typeof document !== "undefined") {
        document.body.style.cursor = "";
      }
    },
    [handleStationIconResizeMove, setStationIconSize],
  );

  const handleStationIconResizeDown = useCallback(
    (
      event: ReactPointerEvent<HTMLDivElement>,
      stationId: number,
      currentPercent: number,
      baseIconSizePx: number,
    ) => {
      event.preventDefault();
      event.stopPropagation();

      const captureTarget = event.currentTarget;
      if (captureTarget.setPointerCapture) {
        captureTarget.setPointerCapture(event.pointerId);
      }

      stationIconResizeStateRef.current = {
        stationId,
        pointerId: event.pointerId,
        initialPointer: { x: event.clientX, y: event.clientY },
        baseIconSizePx: Math.max(1, baseIconSizePx),
        initialPercent: currentPercent,
        lastPercent: currentPercent,
        captureTarget,
      };
      setResizingStationIconId(stationId);

      setLiveStationIconSizes((prev) => {
        const next = new Map(prev);
        next.set(stationId, currentPercent);
        return next;
      });

      if (typeof document !== "undefined") {
        document.body.style.cursor = "nwse-resize";
      }
      window.addEventListener("pointermove", handleStationIconResizeMove);
      window.addEventListener("pointerup", handleStationIconResizeUp);
      window.addEventListener("pointercancel", handleStationIconResizeUp);
    },
    [handleStationIconResizeMove, handleStationIconResizeUp],
  );

  const handleSightIconResizeMove = useCallback((event: PointerEvent) => {
    const state = sightIconResizeStateRef.current;
    if (!state || event.pointerId !== state.pointerId) return;

    event.preventDefault();

    const dx = event.clientX - state.initialPointer.x;
    const dy = event.clientY - state.initialPointer.y;
    const deltaPx = (dx + dy) * 0.5;
    const nextSizePx = Math.max(1, state.initialSizePx + deltaPx);
    const nextPercent = clamp(
      Math.round((nextSizePx / state.baseSizePxAt100) * 100),
      ICON_SIZE_MIN,
      ICON_SIZE_MAX,
    );

    if (nextPercent === state.lastPercent) {
      return;
    }

    state.lastPercent = nextPercent;
    setLiveSightIconSizes((prev) => {
      const next = new Map(prev);
      next.set(state.sightId, nextPercent);
      return next;
    });
  }, []);

  const handleSightIconResizeUp = useCallback(
    (event: PointerEvent) => {
      const state = sightIconResizeStateRef.current;
      if (!state || event.pointerId !== state.pointerId) return;

      if (state.captureTarget?.releasePointerCapture) {
        state.captureTarget.releasePointerCapture(event.pointerId);
      }

      window.removeEventListener("pointermove", handleSightIconResizeMove);
      window.removeEventListener("pointerup", handleSightIconResizeUp);
      window.removeEventListener("pointercancel", handleSightIconResizeUp);

      setLiveSightIconSizes((prev) => {
        if (!prev.has(state.sightId)) return prev;
        const next = new Map(prev);
        next.delete(state.sightId);
        return next;
      });

      if (state.lastPercent !== state.initialPercent) {
        setSightIconSize(state.sightId, state.lastPercent);
      }

      sightIconResizeStateRef.current = null;
      setResizingSightIconId(null);
      if (typeof document !== "undefined") {
        document.body.style.cursor = "";
      }
    },
    [handleSightIconResizeMove, setSightIconSize],
  );

  const handleSightIconResizeDown = useCallback(
    (
      event: ReactPointerEvent<HTMLDivElement>,
      sightId: number,
      currentPercent: number,
      initialSizePx: number,
    ) => {
      event.preventDefault();
      event.stopPropagation();

      const captureTarget = event.currentTarget;
      if (captureTarget.setPointerCapture) {
        captureTarget.setPointerCapture(event.pointerId);
      }

      const safePercent = Math.max(ICON_SIZE_MIN, currentPercent);
      const baseSizePxAt100 = Math.max(1, initialSizePx / (safePercent / 100));

      sightIconResizeStateRef.current = {
        sightId,
        pointerId: event.pointerId,
        initialPointer: { x: event.clientX, y: event.clientY },
        initialPercent: safePercent,
        initialSizePx,
        baseSizePxAt100,
        lastPercent: safePercent,
        captureTarget,
      };
      setResizingSightIconId(sightId);

      setLiveSightIconSizes((prev) => {
        const next = new Map(prev);
        next.set(sightId, safePercent);
        return next;
      });

      if (typeof document !== "undefined") {
        document.body.style.cursor = "nwse-resize";
      }
      window.addEventListener("pointermove", handleSightIconResizeMove);
      window.addEventListener("pointerup", handleSightIconResizeUp);
      window.addEventListener("pointercancel", handleSightIconResizeUp);
    },
    [handleSightIconResizeMove, handleSightIconResizeUp],
  );

  useEffect(() => {
    return () => {
      window.removeEventListener("pointermove", handleStationPointerMove);
      window.removeEventListener("pointerup", handleStationPointerUp);
      window.removeEventListener("pointercancel", handleStationPointerUp);
      window.removeEventListener("pointermove", handleSightPointerMove);
      window.removeEventListener("pointerup", handleSightPointerUp);
      window.removeEventListener("pointercancel", handleSightPointerUp);
      window.removeEventListener("pointermove", handleStationIconResizeMove);
      window.removeEventListener("pointerup", handleStationIconResizeUp);
      window.removeEventListener("pointercancel", handleStationIconResizeUp);
      window.removeEventListener("pointermove", handleSightIconResizeMove);
      window.removeEventListener("pointerup", handleSightIconResizeUp);
      window.removeEventListener("pointercancel", handleSightIconResizeUp);
    };
  }, [
    handleStationPointerMove,
    handleStationPointerUp,
    handleSightPointerMove,
    handleSightPointerUp,
    handleStationIconResizeMove,
    handleStationIconResizeUp,
    handleSightIconResizeMove,
    handleSightIconResizeUp,
  ]);

  const ensureContext = useCallback(() => {
    const canvas = canvasRef.current;
    if (!canvas || glRef.current) {
      return;
    }

    const gl =
      canvas.getContext("webgl") || canvas.getContext("experimental-webgl");

    if (!(gl instanceof WebGLRenderingContext)) {
      return;
    }

    glRef.current = gl;

    try {
      const lineProgram = createProgram(
        gl,
        lineVertexSource,
        lineFragmentSource,
      );
      const pointProgram = createProgram(
        gl,
        pointVertexSource,
        pointFragmentSource,
      );

      lineProgramRef.current = lineProgram;
      pointProgramRef.current = pointProgram;

      lineBufferRef.current = gl.createBuffer();
      pointBufferRef.current = gl.createBuffer();
    } catch {
      // console.error("Failed to initialize WebGL", error);
    }
  }, []);

  useEffect(() => {
    ensureContext();
  }, [ensureContext]);

  useEffect(() => {
    const container = containerRef.current;
    if (!container) {
      return;
    }

    if (typeof ResizeObserver === "undefined") {
      const handleResize = () => {
        debugWebglLog("container resize (fallback)", {
          width: container.clientWidth,
          height: container.clientHeight,
        });
        setCanvasSize({
          width: container.clientWidth,
          height: container.clientHeight,
        });
      };
      handleResize();
      window.addEventListener("resize", handleResize);
      return () => window.removeEventListener("resize", handleResize);
    }

    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        debugWebglLog("container resize", { width, height });
        setCanvasSize({ width, height });
      }
    });
    observer.observe(container);
    return () => observer.disconnect();
  }, []);

  const drawScene = useCallback(() => {
    const gl = glRef.current;
    const canvas = canvasRef.current;
    const lineProgram = lineProgramRef.current;
    const pointProgram = pointProgramRef.current;
    const lineBuffer = lineBufferRef.current;
    const pointBuffer = pointBufferRef.current;

    if (
      !gl ||
      !canvas ||
      !lineProgram ||
      !pointProgram ||
      !lineBuffer ||
      !pointBuffer
    ) {
      return;
    }

    const dpr = Math.max(1, window.devicePixelRatio || 1);
    const displayWidth = Math.max(1, Math.floor(canvasSize.width * dpr));
    const displayHeight = Math.max(1, Math.floor(canvasSize.height * dpr));
    debugWebglLog("drawScene:start", {
      cssCanvasWidth: canvasSize.width,
      cssCanvasHeight: canvasSize.height,
      dpr,
      displayWidth,
      displayHeight,
      routeVerticesCount: rotatedRouteVertices.length / 2,
      stationVerticesCount: rotatedStationVertices.length / 2,
    });

    if (canvas.width !== displayWidth || canvas.height !== displayHeight) {
      canvas.width = displayWidth;
      canvas.height = displayHeight;
    }

    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clearColor(
      backgroundColor[0],
      backgroundColor[1],
      backgroundColor[2],
      1,
    );
    gl.clear(gl.COLOR_BUFFER_BIT);

    const fallbackVertices =
      rotatedRouteVertices.length > 0
        ? rotatedRouteVertices
        : rotatedStationVertices;

    const routeMinRaw = routeData?.scale_min ?? originalRouteData?.scale_min;
    const routeMaxRaw = routeData?.scale_max ?? originalRouteData?.scale_max;
    const hasRouteScaleLimits =
      typeof routeMinRaw === "number" &&
      typeof routeMaxRaw === "number" &&
      routeMaxRaw >= routeMinRaw &&
      routeMinRaw > 0;

    if (hasRouteScaleLimits) {
      scaleLimitsRef.current = {
        min: routeMinRaw / SCALE_FACTOR,
        max: routeMaxRaw / SCALE_FACTOR,
      };
    }

    let transform = transformRef.current;
    if (!transform || !Number.isFinite(transform.scale)) {
      if (canvasSize.width === 0 || canvasSize.height === 0) {
        return;
      }

      transform = computeViewTransform(
        fallbackVertices,
        canvas.width,
        canvas.height,
      );
      if (!hasRouteScaleLimits) {
        const baseScale = Math.max(0.1, transform.scale || 1);
        scaleLimitsRef.current = {
          min: baseScale * 0.25,
          max: baseScale * 16,
        };
      }
      const centerLat =
        routeData?.center_latitude ?? originalRouteData?.center_latitude;
      const centerLon =
        routeData?.center_longitude ?? originalRouteData?.center_longitude;
      if (
        Number.isFinite(centerLat) &&
        Number.isFinite(centerLon) &&
        canvas.width > 0 &&
        canvas.height > 0
      ) {
        const local = coordinatesToLocal(
          centerLat as number,
          centerLon as number,
        );
        const baseX = local.x * UP_SCALE;
        const baseY = local.y * UP_SCALE;
        const cos = Math.cos(rotationAngle);
        const sin = Math.sin(rotationAngle);
        const rotatedX = baseX * cos - baseY * sin;
        const rotatedY = baseX * sin + baseY * cos;
        const scale = transform.scale || 1;
        transform = {
          scale,
          translation: {
            x: canvas.width / 2 - rotatedX * scale,
            y: canvas.height / 2 - rotatedY * scale,
          },
        };
        lastAppliedCenterRef.current = {
          latitude: centerLat as number,
          longitude: centerLon as number,
        };
        const clamped = clampTransformScale(transform);
        if (clamped.scale !== transform.scale) {
          const clampedScale = clamped.scale;
          transform = {
            scale: clampedScale,
            translation: {
              x: canvas.width / 2 - rotatedX * clampedScale,
              y: canvas.height / 2 - rotatedY * clampedScale,
            },
          };

          updateTransform(transform, { skipClamp: true });
        } else {
          updateTransform(clamped, { skipClamp: true });
        }
      } else {
        transform = clampTransformScale(transform);
        updateTransform(transform);
      }
    } else {
      const clamped = clampTransformScale(transform);
      if (clamped !== transform) {
        updateTransform(clamped);
        transform = clamped;
      }
    }

    const { scale, translation } = transform;
    debugWebglLog("drawScene:transform", {
      scale,
      translation,
      scaleLimits: scaleLimitsRef.current,
    });

    const desiredRouteWidthCss = 7;
    const desiredStationDiameterCss = 12;
    const pointOuterSizePx = desiredStationDiameterCss * dpr;
    const pointInnerSizePx = pointOuterSizePx * 0.8;

    if (rotatedRouteVertices.length >= 4) {
      gl.useProgram(lineProgram.program);
      gl.bindBuffer(gl.ARRAY_BUFFER, lineBuffer);
      const lineWidth = (desiredRouteWidthCss * dpr) / scale;
      const thickVertices = generateThickLineGeometry(
        rotatedRouteVertices,
        lineWidth,
      );
      debugWebglLog("drawScene:route", {
        desiredRouteWidthCss,
        lineWidthWorldUnits: lineWidth,
        thickVerticesCount: thickVertices.length / 2,
      });
      if (thickVertices.length === 0) {
        gl.bufferData(gl.ARRAY_BUFFER, rotatedRouteVertices, gl.STATIC_DRAW);
      } else {
        gl.bufferData(gl.ARRAY_BUFFER, thickVertices, gl.STATIC_DRAW);
      }

      gl.enableVertexAttribArray(lineProgram.attribLocations.a_position);
      gl.vertexAttribPointer(
        lineProgram.attribLocations.a_position,
        2,
        gl.FLOAT,
        false,
        0,
        0,
      );

      if (lineProgram.uniformLocations.u_resolution) {
        gl.uniform2f(
          lineProgram.uniformLocations.u_resolution,
          canvas.width,
          canvas.height,
        );
      }
      if (lineProgram.uniformLocations.u_translation) {
        gl.uniform2f(
          lineProgram.uniformLocations.u_translation,
          translation.x,
          translation.y,
        );
      }
      if (lineProgram.uniformLocations.u_scale) {
        gl.uniform1f(lineProgram.uniformLocations.u_scale, scale);
      }
      if (lineProgram.uniformLocations.u_color) {
        gl.uniform4f(
          lineProgram.uniformLocations.u_color,
          pathColor[0],
          pathColor[1],
          pathColor[2],
          1,
        );
      }

      const vertexCount =
        thickVertices.length > 0
          ? thickVertices.length / 2
          : rotatedRouteVertices.length / 2;
      const mode = thickVertices.length > 0 ? gl.TRIANGLES : gl.LINE_STRIP;
      gl.drawArrays(mode, 0, vertexCount);
    }

    if (rotatedStationVertices.length >= 2) {
      gl.useProgram(pointProgram.program);
      gl.bindBuffer(gl.ARRAY_BUFFER, pointBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, rotatedStationVertices, gl.STATIC_DRAW);

      gl.enableVertexAttribArray(pointProgram.attribLocations.a_position);
      gl.vertexAttribPointer(
        pointProgram.attribLocations.a_position,
        2,
        gl.FLOAT,
        false,
        0,
        0,
      );

      if (pointProgram.uniformLocations.u_resolution) {
        gl.uniform2f(
          pointProgram.uniformLocations.u_resolution,
          canvas.width,
          canvas.height,
        );
      }
      if (pointProgram.uniformLocations.u_translation) {
        gl.uniform2f(
          pointProgram.uniformLocations.u_translation,
          translation.x,
          translation.y,
        );
      }
      if (pointProgram.uniformLocations.u_scale) {
        gl.uniform1f(pointProgram.uniformLocations.u_scale, scale);
      }

      if (pointProgram.uniformLocations.u_pointSize) {
        gl.uniform1f(
          pointProgram.uniformLocations.u_pointSize,
          pointOuterSizePx,
        );
      }
      debugWebglLog("drawScene:stations", {
        pointOuterSizePx,
        pointInnerSizePx,
      });
      if (pointProgram.uniformLocations.u_color) {
        gl.uniform4f(
          pointProgram.uniformLocations.u_color,
          backgroundColor[0],
          backgroundColor[1],
          backgroundColor[2],
          1,
        );
      }
      gl.drawArrays(gl.POINTS, 0, rotatedStationVertices.length / 2);
      if (pointProgram.uniformLocations.u_pointSize) {
        gl.uniform1f(
          pointProgram.uniformLocations.u_pointSize,
          pointInnerSizePx,
        );
      }
      if (pointProgram.uniformLocations.u_color) {
        gl.uniform4f(
          pointProgram.uniformLocations.u_color,
          pathColor[0],
          pathColor[1],
          pathColor[2],
          1,
        );
      }
      gl.drawArrays(gl.POINTS, 0, rotatedStationVertices.length / 2);
    }
  }, [
    canvasSize.height,
    canvasSize.width,
    rotatedRouteVertices,
    rotatedStationVertices,
    routeData?.scale_min,
    routeData?.scale_max,
    originalRouteData?.scale_min,
    originalRouteData?.scale_max,
    rotationAngle,
  ]);

  useEffect(() => {
    drawSceneRef.current = drawScene;
  }, [drawScene]);

  useEffect(() => {
    if (!transformState && transformRef.current) {
      setTransformState({ ...transformRef.current });
    }
  }, [transformState]);

  useEffect(() => {
    if (!transformRef.current) {
      return;
    }

    const current = transformRef.current;
    const { min, max } = scaleLimitsRef.current;
    const targetScale = clamp(sharedScale, min, max);

    if (Math.abs(current.scale - targetScale) < 1e-3) {
      if (targetScale !== sharedScale) {
        setSharedScale(targetScale);
      }
      return;
    }

    const canvas = canvasRef.current;
    if (!canvas) {
      return;
    }

    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;
    const worldCenterX = (centerX - current.translation.x) / current.scale;
    const worldCenterY = (centerY - current.translation.y) / current.scale;

    const updated: Transform = {
      scale: targetScale,
      translation: {
        x: centerX - worldCenterX * targetScale,
        y: centerY - worldCenterY * targetScale,
      },
    };

    transformRef.current = updated;
    lastTransformRef.current = updated;
    setTransformState(updated);
    if (targetScale !== sharedScale) {
      setSharedScale(targetScale);
    }
    drawSceneRef.current();
  }, [sharedScale, setSharedScale]);

  useEffect(() => {
    if (suppressAutoFitRef.current) {
      return;
    }
    if (skipNextAutoFitRef.current) {
      skipNextAutoFitRef.current = false;
      return;
    }

    const currentTransform = transformRef.current ?? lastTransformRef.current;
    if (!currentTransform) {
      resetTransform();
      return;
    }

    const canvas = canvasRef.current;
    if (!canvas || canvas.width === 0 || canvas.height === 0) {
      resetTransform();
      return;
    }

    const preservedScale = currentTransform.scale;

    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;

    const worldCenterX =
      (centerX - currentTransform.translation.x) / preservedScale;
    const worldCenterY =
      (centerY - currentTransform.translation.y) / preservedScale;

    const centerLat =
      routeData?.center_latitude ?? originalRouteData?.center_latitude;
    const centerLon =
      routeData?.center_longitude ?? originalRouteData?.center_longitude;

    if (Number.isFinite(centerLat) && Number.isFinite(centerLon)) {
      const local = coordinatesToLocal(
        centerLat as number,
        centerLon as number,
      );
      const baseX = local.x * UP_SCALE;
      const baseY = local.y * UP_SCALE;
      const cos = Math.cos(rotationAngle);
      const sin = Math.sin(rotationAngle);
      const rotatedX = baseX * cos - baseY * sin;
      const rotatedY = baseX * sin + baseY * cos;

      const updatedTransform: Transform = {
        scale: preservedScale,
        translation: {
          x: centerX - rotatedX * preservedScale,
          y: centerY - rotatedY * preservedScale,
        },
      };

      transformRef.current = updatedTransform;
      lastTransformRef.current = updatedTransform;
      setTransformState(updatedTransform);
      drawSceneRef.current();
    } else {
      const updatedTransform: Transform = {
        scale: preservedScale,
        translation: {
          x: centerX - worldCenterX * preservedScale,
          y: centerY - worldCenterY * preservedScale,
        },
      };

      transformRef.current = updatedTransform;
      lastTransformRef.current = updatedTransform;
      setTransformState(updatedTransform);
      drawSceneRef.current();
    }
  }, [
    rotationAngle,
    routeData?.center_latitude,
    routeData?.center_longitude,
    originalRouteData?.center_latitude,
    originalRouteData?.center_longitude,
    resetTransform,
  ]);

  useEffect(() => {
    drawScene();
  }, [drawScene]);

  useEffect(() => {
    const camera =
      transformState ?? transformRef.current ?? lastTransformRef.current;
    if (!camera || !sightData?.length) return;

    const routeIconSizePercent =
      routeData?.icon_size ?? originalRouteData?.icon_size ?? 100;

    for (const sight of sightData) {
      const shouldUseCustomSightIcon =
        sight.is_default_icon === false && !isMediaIdEmpty(sight.icon);
      if (!shouldUseCustomSightIcon) continue;

      const customSightIconScaleFactor =
        camera.scale / Math.max(customSightIconBaseScaleRef.current ?? 1, 1e-6);
      const sightIconSizePercent =
        liveSightIconSizes.get(sight.id) ??
        (typeof sight.icon_size === "number" && Number.isFinite(sight.icon_size)
          ? sight.icon_size
          : routeIconSizePercent);
      const iconSizePx =
        30 *
        clamp(sightIconSizePercent / 100, 0.1, 10) *
        customSightIconScaleFactor;

      debugWebglLog("custom sight icon size", {
        sightId: sight.id,
        cameraScale: camera.scale,
        baseScale: customSightIconBaseScaleRef.current,
        scaleFactor: customSightIconScaleFactor,
        iconPercent: sightIconSizePercent,
        iconSizePx,
      });
    }
  }, [
    sightData,
    liveSightIconSizes,
    transformState,
    routeData?.icon_size,
    originalRouteData?.icon_size,
  ]);

  useEffect(() => {
    if (!stationData?.ru?.length) return;

    const fontSizePercent =
      routeData?.font_size ?? originalRouteData?.font_size ?? 100;
    const fontScale = fontSizePercent / 100;
    const baseStationIconSizePx = 16 * fontScale * 1.2;

    for (const station of stationData.ru) {
      const hasCustomStationIcon = !isMediaIdEmpty(station.icon);
      if (!hasCustomStationIcon) continue;

      const stationIconSizePercent =
        liveStationIconSizes.get(station.id) ??
        (typeof station.icon_size === "number" &&
        Number.isFinite(station.icon_size)
          ? station.icon_size
          : 100);
      const iconSizePx = Math.max(
        1,
        Math.round(
          baseStationIconSizePx * clamp(stationIconSizePercent / 100, 0.1, 10),
        ),
      );

      debugWebglLog("custom station icon size", {
        stationId: station.id,
        fontSizePercent,
        baseStationIconSizePx,
        iconPercent: stationIconSizePercent,
        iconSizePx,
      });
    }
  }, [
    stationData?.ru,
    liveStationIconSizes,
    routeData?.font_size,
    originalRouteData?.font_size,
  ]);

  const applyCenterFromCoordinates = useCallback(
    (latitude: number, longitude: number) => {
      const roundedLat = Math.round(latitude * 1e6) / 1e6;
      const roundedLon = Math.round(longitude * 1e6) / 1e6;
      lastCenterRef.current = {
        latitude: roundedLat,
        longitude: roundedLon,
      };

      if (isUserInteractingRef.current) {
        pendingCenterRef.current = {
          latitude: roundedLat,
          longitude: roundedLon,
        };
        return;
      }

      const transform =
        transformRef.current ?? lastTransformRef.current ?? transformState;
      const canvas = canvasRef.current;

      if (!canvas || !transform) {
        pendingCenterRef.current = {
          latitude: roundedLat,
          longitude: roundedLon,
        };
        return;
      }

      const width = canvas.width || canvas.clientWidth;
      const height = canvas.height || canvas.clientHeight;
      if (!width || !height) {
        pendingCenterRef.current = {
          latitude: roundedLat,
          longitude: roundedLon,
        };
        return;
      }

      const local = coordinatesToLocal(roundedLat, roundedLon);
      const baseX = local.x * UP_SCALE;
      const baseY = local.y * UP_SCALE;

      const cos = Math.cos(rotationAngle);
      const sin = Math.sin(rotationAngle);
      const rotatedX = baseX * cos - baseY * sin;
      const rotatedY = baseX * sin + baseY * cos;

      const scale = transform.scale || 1;
      const targetTranslation = {
        x: width / 2 - rotatedX * scale,
        y: height / 2 - rotatedY * scale,
      };

      const currentTranslation = transform.translation;
      const distance = Math.hypot(
        targetTranslation.x - currentTranslation.x,
        targetTranslation.y - currentTranslation.y,
      );

      if (distance < 0.5) {
        pendingCenterRef.current = null;
        return;
      }

      const nextTransform: Transform = {
        scale,
        translation: targetTranslation,
      };

      transformRef.current = nextTransform;
      lastTransformRef.current = nextTransform;
      setTransformState(nextTransform);
      drawSceneRef.current();
      pendingCenterRef.current = null;
    },
    [rotationAngle, setTransformState, transformState],
  );

  useEffect(() => {
    const latitude = routeData?.center_latitude;
    const longitude = routeData?.center_longitude;
    if (latitude == null || longitude == null) {
      return;
    }

    const prevApplied = lastAppliedCenterRef.current;
    const epsilon = 1e-7;
    if (
      prevApplied.latitude != null &&
      prevApplied.longitude != null &&
      Math.abs(prevApplied.latitude - latitude) < epsilon &&
      Math.abs(prevApplied.longitude - longitude) < epsilon
    ) {
      return;
    }

    lastAppliedCenterRef.current = { latitude, longitude };
    applyCenterFromCoordinates(latitude, longitude);
  }, [
    routeData?.center_latitude,
    routeData?.center_longitude,
    applyCenterFromCoordinates,
  ]);

  useEffect(() => {
    if (!pendingCenterRef.current || !transformRef.current) {
      return;
    }

    const { latitude, longitude } = pendingCenterRef.current;
    pendingCenterRef.current = null;
    window.requestAnimationFrame(() => {
      applyCenterFromCoordinates(latitude, longitude);
    });
  }, [transformState, applyCenterFromCoordinates]);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) {
      return;
    }

    canvas.style.touchAction = "none";
    canvas.style.cursor = "grab";

    const getEventPosition = (event: PointerEvent | WheelEvent): Vec2 => {
      const currentCanvas = canvasRef.current;
      if (!currentCanvas) {
        return { x: 0, y: 0 };
      }
      const rect = currentCanvas.getBoundingClientRect();
      const scaleX = currentCanvas.width / Math.max(rect.width, 1);
      const scaleY = currentCanvas.height / Math.max(rect.height, 1);
      return {
        x: (event.clientX - rect.left) * scaleX,
        y: (event.clientY - rect.top) * scaleY,
      };
    };

    const applyTranslation = (dx: number, dy: number) => {
      const transform = transformRef.current;
      if (!transform) return;
      const next = {
        scale: transform.scale,
        translation: {
          x: transform.translation.x + dx,
          y: transform.translation.y + dy,
        },
      };
      updateTransform(next, { immediate: true });
      drawSceneRef.current();
    };

    const handlePointerDown = (event: PointerEvent) => {
      if (event.pointerType === "mouse" && event.button !== 0) {
        return;
      }

      event.preventDefault();
      isUserInteractingRef.current = true;
      cancelScheduledCenterCommit();
      const position = getEventPosition(event);
      activePointersRef.current.set(event.pointerId, position);
      canvas.setPointerCapture(event.pointerId);

      if (activePointersRef.current.size === 1) {
        dragStateRef.current = { lastPos: position };
        pinchStateRef.current = null;
        canvas.style.cursor = "grabbing";
      } else if (activePointersRef.current.size === 2) {
        dragStateRef.current = null;

        const pointers = Array.from(activePointersRef.current.values());
        const [p1, p2] = pointers;
        const distance = Math.hypot(p1.x - p2.x, p1.y - p2.y);
        const midpoint: Vec2 = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
        const transform = transformRef.current;
        if (!transform) return;

        const worldMidpoint: Vec2 = {
          x: (midpoint.x - transform.translation.x) / transform.scale,
          y: (midpoint.y - transform.translation.y) / transform.scale,
        };

        pinchStateRef.current = {
          initialDistance: Math.max(distance, 1),
          initialScale: transform.scale,
          worldMidpoint,
        };
      }
    };

    const handlePointerMove = (event: PointerEvent) => {
      if (!activePointersRef.current.has(event.pointerId)) {
        return;
      }

      const position = getEventPosition(event);
      activePointersRef.current.set(event.pointerId, position);

      if (activePointersRef.current.size === 1 && dragStateRef.current) {
        const { lastPos } = dragStateRef.current;
        applyTranslation(position.x - lastPos.x, position.y - lastPos.y);
        dragStateRef.current = { lastPos: position };
        return;
      }

      if (activePointersRef.current.size === 2 && pinchStateRef.current) {
        const pointers = Array.from(activePointersRef.current.values());
        const [p1, p2] = pointers;
        const distance = Math.max(Math.hypot(p1.x - p2.x, p1.y - p2.y), 1);
        const midpoint: Vec2 = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
        const transform = transformRef.current;
        if (!transform) return;

        const { initialDistance, initialScale, worldMidpoint } =
          pinchStateRef.current;
        const scaleRatio = distance / initialDistance;
        const unclampedScale = initialScale * scaleRatio;
        const clampedScale = clamp(
          unclampedScale,
          scaleLimitsRef.current.min,
          scaleLimitsRef.current.max,
        );

        updateTransform(
          {
            scale: clampedScale,
            translation: {
              x: midpoint.x - worldMidpoint.x * clampedScale,
              y: midpoint.y - worldMidpoint.y * clampedScale,
            },
          },
          { immediate: true },
        );
        drawSceneRef.current();
      }
    };

    const handlePointerUp = (event: PointerEvent) => {
      if (!activePointersRef.current.has(event.pointerId)) {
        return;
      }

      canvas.releasePointerCapture(event.pointerId);
      activePointersRef.current.delete(event.pointerId);

      if (activePointersRef.current.size === 0) {
        dragStateRef.current = null;
        pinchStateRef.current = null;
        canvas.style.cursor = "grab";
        scheduleCenterCommit();
      } else if (activePointersRef.current.size === 1) {
        const remaining = Array.from(activePointersRef.current.values())[0];
        dragStateRef.current = { lastPos: remaining };
        pinchStateRef.current = null;
        canvas.style.cursor = "grabbing";
      }
    };

    const handleWheel = (event: WheelEvent) => {
      event.preventDefault();
      const transform = transformRef.current;
      if (!transform) return;
      isUserInteractingRef.current = true;
      cancelScheduledCenterCommit();

      const position = getEventPosition(event);
      const delta = event.deltaY > 0 ? 0.9 : 1.1;
      const unclampedScale = transform.scale * delta;
      const clampedScale = clamp(
        unclampedScale,
        scaleLimitsRef.current.min,
        scaleLimitsRef.current.max,
      );

      if (clampedScale === transform.scale) {
        return;
      }

      const worldPoint = {
        x: (position.x - transform.translation.x) / transform.scale,
        y: (position.y - transform.translation.y) / transform.scale,
      };

      updateTransform(
        {
          scale: clampedScale,
          translation: {
            x: position.x - worldPoint.x * clampedScale,
            y: position.y - worldPoint.y * clampedScale,
          },
        },
        { immediate: true },
      );
      drawSceneRef.current();
      scheduleCenterCommit();
    };

    canvas.addEventListener("pointerdown", handlePointerDown);
    canvas.addEventListener("pointermove", handlePointerMove);
    canvas.addEventListener("pointerup", handlePointerUp);
    canvas.addEventListener("pointercancel", handlePointerUp);
    canvas.addEventListener("pointerleave", handlePointerUp);
    canvas.addEventListener("wheel", handleWheel, { passive: false });

    return () => {
      canvas.removeEventListener("pointerdown", handlePointerDown);
      canvas.removeEventListener("pointermove", handlePointerMove);
      canvas.removeEventListener("pointerup", handlePointerUp);
      canvas.removeEventListener("pointercancel", handlePointerUp);
      canvas.removeEventListener("pointerleave", handlePointerUp);
      canvas.removeEventListener("wheel", handleWheel as EventListener);
    };
  }, [cancelScheduledCenterCommit, scheduleCenterCommit, updateTransform]);

  return (
    <div
      ref={containerRef}
      style={{
        width: "100%",
        height: "100%",
        position: "relative",
        overflow: "hidden",
      }}
    >
      <canvas
        ref={canvasRef}
        style={{
          width: "100%",
          height: "100%",
          display: "block",
          position: "relative",
        }}
      />
      {stationData?.ru?.length ? (
        <div
          style={{
            position: "absolute",
            inset: 0,
            pointerEvents: "none",
            zIndex: 1,
          }}
        >
          {stationData.ru.map((station, index) => {
            const camera =
              transformState ??
              transformRef.current ??
              lastTransformRef.current;
            if (!camera) {
              return null;
            }
            const translatedStation = stationData?.[language]?.[index];
            const enStation = stationData?.["en"]?.[index];

            const local = coordinatesToLocal(
              station.latitude,
              station.longitude,
            );
            const baseX = local.x * UP_SCALE;
            const baseY = local.y * UP_SCALE;

            const cos = Math.cos(rotationAngle);
            const sin = Math.sin(rotationAngle);
            const rotatedX = baseX * cos - baseY * sin;
            const rotatedY = baseX * sin + baseY * cos;

            const liveStationOffset = liveStationOffsets.get(station.id);
            const hasCustomOffset =
              typeof station.offset_x === "number" &&
              typeof station.offset_y === "number" &&
              (station.offset_x !== 0 || station.offset_y !== 0);

            const baseOffsetX = hasCustomOffset ? station.offset_x : 25;
            const baseOffsetY = hasCustomOffset ? station.offset_y : 0;

            const offsetX =
              liveStationOffset?.x !== undefined
                ? liveStationOffset.x
                : baseOffsetX;
            const offsetY =
              liveStationOffset?.y !== undefined
                ? liveStationOffset.y
                : baseOffsetY;

            const stationScreenX =
              rotatedX * camera.scale + camera.translation.x;
            const stationScreenY =
              rotatedY * camera.scale + camera.translation.y;

            const labelX = stationScreenX + offsetX;
            const labelY = stationScreenY + offsetY;

            const backendAlign = station.align;

            const anchor = getAnchorFromOffset(backendAlign ?? 2);
            const transformCss = `translate(${-anchor.x * 100}%, ${
              -anchor.y * 100
            }%)`;

            const dpr = Math.max(1, window.devicePixelRatio || 1);

            const cssX = labelX / dpr;
            const cssY = labelY / dpr;
            const rotationCss = `${rotationAngle}rad`;
            const counterRotationCss = `${-rotationAngle}rad`;

            const secondaryStation =
              language === "ru" ? enStation : translatedStation;
            const showSecondary =
              secondaryStation &&
              secondaryStation.name &&
              secondaryStation.name !== station.name;

            const fontSizePercent =
              routeData?.font_size ?? originalRouteData?.font_size ?? 100;
            const fontScale = fontSizePercent / 100;

            const zoomClampedScale = Math.min(Math.max(camera.scale, 1), 3);
            const scaleFactor = 1 + (zoomClampedScale - 1) * 0.4;

            const primaryFontSize = 16 * fontScale * scaleFactor;
            const secondaryFontSize = 13 * fontScale * scaleFactor;

            const secondaryMarginTop = 5 * fontScale * scaleFactor;

            const alignmentFromData: StationAlignment =
              backendAlign === 1
                ? "left"
                : backendAlign === 3
                  ? "right"
                  : "center";
            const alignment: StationAlignment =
              stationAlignments.get(station.id) ?? alignmentFromData;

            const secondaryPositionStyle: CSSProperties =
              alignment === "left"
                ? { left: 0, transform: "none" }
                : alignment === "right"
                  ? { right: 0, transform: "none" }
                  : { left: "50%", transform: "translateX(-50%)" };

            const isMediaIdEmptyResult = isMediaIdEmpty(station.icon);
            const iconUrl = isMediaIdEmptyResult
              ? null
              : `${import.meta.env.VITE_KRBL_MEDIA}${
                  station.icon
                }/download?token=${localStorage.getItem("token") ?? ""}`;

            const stationIconSizePercent =
              liveStationIconSizes.get(station.id) ??
              (typeof station.icon_size === "number" &&
              Number.isFinite(station.icon_size)
                ? station.icon_size
                : 100);
            const baseStationIconSizePx = primaryFontSize * 1.2;
            const iconSizePx = Math.max(
              1,
              Math.round(
                baseStationIconSizePx *
                  clamp(stationIconSizePercent / 100, 0.1, 10),
              ),
            );
            const showStationResizeUi =
              hoveredStationIconId === station.id ||
              resizingStationIconId === station.id;

            const secondaryLineHeight = 1.2;

            return (
              <div key={station.id}>
                <div
                  onMouseEnter={() => setHoveredStationId(station.id)}
                  onMouseLeave={() =>
                    setHoveredStationId((prev) =>
                      prev === station.id ? null : prev,
                    )
                  }
                  onPointerDown={(event) =>
                    handleStationPointerDown(
                      event,
                      station.id,
                      {
                        x: rotatedX,
                        y: rotatedY,
                      },
                      { x: offsetX, y: offsetY },
                    )
                  }
                  style={{
                    position: "absolute",
                    left: cssX,
                    top: cssY,
                    transform: transformCss,
                    color: "#fff",
                    fontFamily: "Roboto, sans-serif",
                    textAlign: "left",
                    pointerEvents: "none",
                    cursor: "grab",
                    userSelect: "none",
                    touchAction: "none",
                    lineHeight: 1,
                  }}
                >
                  <div
                    style={{
                      display: "inline-block",
                      pointerEvents: "none",
                      transformOrigin: "left center",
                      transform: `rotate(${rotationCss})`,
                    }}
                  >
                    <div
                      style={{
                        display: "inline-block",
                        pointerEvents: "none",
                        transformOrigin: "left center",
                        transform: `rotate(${counterRotationCss})`,
                      }}
                    >
                      <div
                        style={{
                          position: "relative",
                          display: "inline-flex",
                          alignItems: "center",
                          gap: iconUrl ? 6 : 0,
                          pointerEvents: "auto",
                        }}
                      >
                        {iconUrl ? (
                          <div
                            style={{
                              position: "relative",
                              width: iconSizePx,
                              height: iconSizePx,
                              flexShrink: 0,
                            }}
                          >
                            <div
                              onPointerEnter={() =>
                                setHoveredStationIconId(station.id)
                              }
                              onPointerLeave={() =>
                                setHoveredStationIconId((prev) =>
                                  prev === station.id ? null : prev,
                                )
                              }
                              style={{
                                position: "absolute",
                                inset: -8,
                                pointerEvents: "auto",
                                zIndex: 0,
                              }}
                            />
                            <img
                              src={iconUrl}
                              alt=""
                              style={{
                                width: "100%",
                                height: "100%",
                                display: "block",
                                pointerEvents: "none",
                              }}
                            />
                            {showStationResizeUi ? (
                              <div
                                style={{
                                  position: "absolute",
                                  inset: -5,
                                  border: "1.5px solid #0D99FF",
                                  boxShadow: "0 0 0 1px rgba(13,153,255,0.25)",
                                  pointerEvents: "none",
                                  zIndex: -1,
                                }}
                              />
                            ) : null}
                            {showStationResizeUi ? (
                              <div
                                onPointerEnter={() =>
                                  setHoveredStationIconId(station.id)
                                }
                                onPointerDown={(event) =>
                                  handleStationIconResizeDown(
                                    event,
                                    station.id,
                                    stationIconSizePercent,
                                    baseStationIconSizePx,
                                  )
                                }
                                title="Потяните для изменения размера иконки"
                                style={{
                                  position: "absolute",
                                  right: -5,
                                  bottom: -5,
                                  width: 10,
                                  height: 10,
                                  border: "1.5px solid #0D99FF",
                                  borderRadius: 1,
                                  cursor: "nwse-resize",
                                  pointerEvents: "auto",
                                  zIndex: -1,
                                  boxSizing: "border-box",
                                }}
                              />
                            ) : null}
                          </div>
                        ) : null}
                        <div
                          style={{
                            position: "relative",
                            fontWeight: 700,
                            fontSize: primaryFontSize,
                            lineHeight: 1,
                            textShadow: "0 0 4px rgba(0,0,0,0.6)",
                            pointerEvents: "none",
                            whiteSpace: "nowrap",
                          }}
                        >
                          {station.name}
                          {showSecondary ? (
                            <div
                              style={{
                                position: "absolute",
                                top: "100%",
                                marginTop: -1 * secondaryMarginTop,
                                fontWeight: 400,
                                fontSize: secondaryFontSize,
                                lineHeight: secondaryLineHeight,
                                color: "#CBCBCB",
                                textShadow: "0 0 3px rgba(0,0,0,0.4)",
                                whiteSpace: "nowrap",
                                ...secondaryPositionStyle,
                                pointerEvents: "none",
                              }}
                            >
                              {secondaryStation?.name}
                            </div>
                          ) : null}
                        </div>
                      </div>
                    </div>
                  </div>
                  {hoveredStationId === station.id && (
                    <div
                      style={{
                        position: "absolute",
                        top: "100%",
                        left: "50%",
                        transform: "translateX(-50%)",
                        paddingTop: "8px",
                        pointerEvents: "auto",
                        zIndex: 1000000,
                        cursor: "default",
                      }}
                      onPointerDown={(e) => e.stopPropagation()}
                    >
                      <div
                        style={{
                          display: "flex",
                          gap: 4,
                          padding: 4,
                          borderRadius: 4,
                          backgroundColor: "white",
                          boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
                        }}
                      >
                        {buttons.map((btn) => (
                          <div
                            key={btn.value}
                            onClick={(e) => {
                              e.stopPropagation();
                              setStationAlignments((prev) => {
                                const next = new Map(prev);
                                next.set(
                                  station.id,
                                  btn.align as StationAlignment,
                                );
                                return next;
                              });
                              setStationAlign(station.id, btn.value);
                            }}
                            style={{
                              padding: "4px 8px",
                              fontSize: 12,
                              cursor: "pointer",
                              backgroundColor:
                                alignment === btn.align
                                  ? "#e0e0e0"
                                  : "transparent",
                              borderRadius: 4,
                              whiteSpace: "nowrap",
                              color: "black",
                              fontWeight: 500,
                              userSelect: "none",
                            }}
                          >
                            {btn.label}
                          </div>
                        ))}
                      </div>
                    </div>
                  )}
                </div>
              </div>
            );
          })}
        </div>
      ) : null}
      {sightData?.length ? (
        <div
          style={{
            position: "absolute",
            inset: 0,
            pointerEvents: "none",
          }}
        >
          {sightData.map((sight, index) => {
            const camera =
              transformState ??
              transformRef.current ??
              lastTransformRef.current;
            if (!camera) {
              return null;
            }
            const liveSightPosition = liveSightPositions.get(sight.id);
            const latitude = liveSightPosition?.latitude ?? sight.latitude;
            const longitude = liveSightPosition?.longitude ?? sight.longitude;
            const local = coordinatesToLocal(latitude, longitude);
            const baseX = local.x * UP_SCALE;
            const baseY = local.y * UP_SCALE;

            const cos = Math.cos(rotationAngle);
            const sin = Math.sin(rotationAngle);
            const rotatedX = baseX * cos - baseY * sin;
            const rotatedY = baseX * sin + baseY * cos;

            const rawOffsetX = sight.offset_x ?? 0;
            const rawOffsetY = sight.offset_y ?? 0;

            const DEFAULT_LABEL_OFFSET_X = 25;
            const DEFAULT_LABEL_OFFSET_Y = 0;

            const offsetX =
              liveSightPosition?.offsetX ??
              (rawOffsetX === 0 && rawOffsetY === 0
                ? DEFAULT_LABEL_OFFSET_X
                : rawOffsetX);
            const offsetY =
              liveSightPosition?.offsetY ??
              (rawOffsetX === 0 && rawOffsetY === 0
                ? DEFAULT_LABEL_OFFSET_Y
                : rawOffsetY);

            const labelX =
              (rotatedX + offsetX) * camera.scale + camera.translation.x;
            const labelY =
              (rotatedY + offsetY) * camera.scale + camera.translation.y;

            const dpr = Math.max(1, window.devicePixelRatio || 1);
            const cssX = labelX / dpr;
            const cssY = labelY / dpr;
            const shouldUseCustomSightIcon =
              sight.is_default_icon === false && !isMediaIdEmpty(sight.icon);
            const sightIconUrl = shouldUseCustomSightIcon
              ? `${mediaBaseUrl}${sight.icon}/download?token=${mediaToken}`
              : SIGHT_ICON_URL;
            const customSightIconScaleFactor = shouldUseCustomSightIcon
              ? camera.scale /
                Math.max(customSightIconBaseScaleRef.current ?? 1, 1e-6)
              : 1;
            const sightIconSizePercent =
              sight.is_default_icon === false
                ? (liveSightIconSizes.get(sight.id) ??
                  (typeof sight.icon_size === "number" &&
                  Number.isFinite(sight.icon_size)
                    ? sight.icon_size
                    : 100))
                : (routeData?.icon_size ?? originalRouteData?.icon_size ?? 100);
            const iconSize =
              30 *
              clamp(sightIconSizePercent / 100, 0.1, 10) *
              customSightIconScaleFactor;
            const showSightResizeUi =
              sight.is_default_icon !== true &&
              (hoveredSightIconId === sight.id ||
                resizingSightIconId === sight.id);
            const iconLeft = cssX - iconSize;
            const iconTop = cssY - iconSize;
            const sightZoomClampedScale = Math.min(
              Math.max(camera.scale, 1),
              3,
            );
            const sightScaleFactor = 1 + (sightZoomClampedScale - 1) * 0.4;
            const labelHeight = 24 * sightScaleFactor;
            const labelPadding = 6 * sightScaleFactor;
            const labelFontSize = 14 * sightScaleFactor;

            return (
              <div
                key={`sight-${sight.id}`}
                onPointerDown={(event) =>
                  handleSightPointerDown(
                    event,
                    sight,
                    {
                      x: offsetX,
                      y: offsetY,
                    },
                    { x: rotatedX, y: rotatedY },
                    { latitude, longitude },
                  )
                }
                style={{
                  position: "absolute",
                  left: iconLeft,
                  top: iconTop,
                  height: iconSize,
                  display: "flex",
                  alignItems: "center",
                  gap: 8,
                  pointerEvents: "auto",
                  cursor: "grab",
                  userSelect: "none",
                  touchAction: "none",
                }}
              >
                <div
                  style={{
                    position: "relative",
                    width: iconSize,
                    height: iconSize,
                    flexShrink: 0,
                  }}
                >
                  <div
                    onPointerEnter={() => {
                      setHoveredSightIconId(sight.id);
                      setSelectedSight(sight);
                    }}
                    onPointerLeave={() => {
                      setHoveredSightIconId((prev) =>
                        prev === sight.id ? null : prev,
                      );
                      if (
                        resizingSightIconId !== sight.id &&
                        sightDragStateRef.current?.sight.id !== sight.id &&
                        selectedSight?.id === sight.id
                      ) {
                        setSelectedSight(undefined);
                      }
                    }}
                    style={{
                      position: "absolute",
                      inset: -8,
                      pointerEvents: "auto",
                      zIndex: 0,
                    }}
                  />
                  <img
                    src={sightIconUrl}
                    alt=""
                    style={{
                      display: "block",
                      pointerEvents: "none",
                      width: "100%",
                      height: "100%",
                    }}
                  />
                  {showSightResizeUi ? (
                    <div
                      style={{
                        position: "absolute",
                        inset: -5,
                        border: "1.5px solid #0D99FF",
                        boxShadow: "0 0 0 1px rgba(13,153,255,0.25)",
                        pointerEvents: "none",
                        zIndex: 0,
                      }}
                    />
                  ) : null}
                  {showSightResizeUi ? (
                    <div
                      onPointerEnter={() => setHoveredSightIconId(sight.id)}
                      onPointerDown={(event) =>
                        handleSightIconResizeDown(
                          event,
                          sight.id,
                          sightIconSizePercent,
                          iconSize,
                        )
                      }
                      title="Потяните для изменения размера иконки"
                      style={{
                        position: "absolute",
                        right: -10,
                        bottom: -10,
                        width: 10,
                        height: 10,
                        border: "1.5px solid #0D99FF",
                        backgroundColor: "#FFFFFF",
                        borderRadius: 1,
                        cursor: "nwse-resize",
                        pointerEvents: "auto",
                        zIndex: 0,
                        boxSizing: "border-box",
                      }}
                    />
                  ) : null}
                </div>
                <div
                  style={{
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                    minWidth: labelHeight,
                    height: labelHeight,
                    padding: `0 ${labelPadding}px`,
                    borderRadius: labelHeight / 2,
                    backgroundColor: "rgba(0, 0, 0, 0.85)",
                    color: "#fff",
                    fontWeight: 700,
                    fontSize: labelFontSize,
                    lineHeight: 1,
                    boxShadow: "0 2px 6px rgba(0, 0, 0, 0.45)",
                    pointerEvents: "none",
                  }}
                >
                  {index + 1}
                </div>
              </div>
            );
          })}
        </div>
      ) : null}
      {routeVertices.length === 0 && (
        <div
          style={{
            position: "absolute",
            top: "50%",
            left: "50%",
            transform: "translate(-50%, -50%)",
            color: "#fff",
            fontFamily: "Roboto, sans-serif",
            fontSize: 16,
          }}
        >
          Нет данных для отображения маршрута
        </div>
      )}
    </div>
  );
});

export default WebGLRouteMapPrototype;
