import React, {
  useEffect,
  useRef,
  useState,
  useCallback,
  useMemo,
} from "react";
import { useNavigate } from "react-router-dom";
import { Map as OLMap, View, Overlay, MapBrowserEvent } from "ol";
import TileLayer from "ol/layer/Tile";
import OSM from "ol/source/OSM";
import VectorLayer from "ol/layer/Vector";
import VectorSource, { VectorSourceEvent } from "ol/source/Vector";
import Cluster from "ol/source/Cluster";

import {
  Draw,
  Modify,
  Select,
  DragPan,
  MouseWheelZoom,
  KeyboardPan,
  KeyboardZoom,
  PinchZoom,
  PinchRotate,
} from "ol/interaction";
import { DrawEvent } from "ol/interaction/Draw";
import { SelectEvent } from "ol/interaction/Select";
import {
  Style,
  Fill,
  Stroke,
  Circle as CircleStyle,
  RegularShape,
  Text,
} from "ol/style";
import { Point, LineString, Geometry, Polygon } from "ol/geom";
import { transform, toLonLat } from "ol/proj";
import { GeoJSON } from "ol/format";
import {
  Bus,
  RouteIcon,
  MapPin,
  Trash2,
  ArrowRightLeft,
  Landmark,
  Pencil,
  InfoIcon,
  X,
  Loader2,
  EyeOff,
  Eye,
  Map as MapIcon,
} from "lucide-react";
import { toast } from "react-toastify";
import { singleClick, doubleClick } from "ol/events/condition";
import { Feature } from "ol";
import Layer from "ol/layer/Layer";
import Source from "ol/source/Source";
import { FeatureLike } from "ol/Feature";
import { createEmpty, extend, getCenter } from "ol/extent";

const scrollbarStyles = `
  .scrollbar-hide {
    -ms-overflow-style: none;
    scrollbar-width: none;
  }
  .scrollbar-hide::-webkit-scrollbar {
    display: none;
  }
  
  .scrollbar-visible {
    scrollbar-width: thin;
    scrollbar-color: #cbd5e1 #f1f5f9;
  }
  
  .scrollbar-visible::-webkit-scrollbar {
    width: 8px;
  }
  
  .scrollbar-visible::-webkit-scrollbar-track {
    background: #f1f5f9;
    border-radius: 4px;
  }
  
  .scrollbar-visible::-webkit-scrollbar-thumb {
    background: #cbd5e1;
    border-radius: 4px;
  }
  
  .scrollbar-visible::-webkit-scrollbar-thumb:hover {
    background: #94a3b8;
  }
`;

if (typeof document !== "undefined") {
  const styleElement = document.createElement("style");
  styleElement.textContent = scrollbarStyles;
  document.head.appendChild(styleElement);
}

import { languageInstance } from "@shared";
import { makeAutoObservable } from "mobx";

import {
  stationsStore,
  routeStore,
  sightsStore,
  menuStore,
  selectedCityStore,
  carrierStore,
} from "@shared";

export const clearMapCaches = () => {
  mapStore.routes = [];
  mapStore.stations = [];
  mapStore.sights = [];

  if (typeof window !== "undefined" && (window as any).mapServiceInstance) {
    (window as any).mapServiceInstance.clearCaches();
  }
};

interface ApiRoute {
  id: number;
  route_number: string;
  route_name: string;
  path: [number, number][];
  center_latitude: number;
  center_longitude: number;
  carrier_id: number;
}

interface ApiStation {
  id: number;
  name: string;
  description?: string;
  latitude: number;
  longitude: number;
  city_id: number;
  created_at?: string;
  updated_at?: string;
}

interface ApiSight {
  id: number;
  name: string;
  description: string;
  latitude: number;
  longitude: number;
  city_id: number;
  created_at?: string;
  updated_at?: string;
}

export type SortType =
  | "name_asc"
  | "name_desc"
  | "created_asc"
  | "created_desc"
  | "updated_asc"
  | "updated_desc";

const HIDDEN_ROUTES_KEY = "mapHiddenRoutes";
const HIDE_SIGHTS_BY_HIDDEN_ROUTES_KEY = "mapHideSightsByHiddenRoutes";

const getStoredHiddenRoutes = (): Set<number> => {
  try {
    const stored = localStorage.getItem(HIDDEN_ROUTES_KEY);
    if (stored) {
      const routes = JSON.parse(stored);
      if (
        Array.isArray(routes) &&
        routes.every((id) => typeof id === "number")
      ) {
        return new Set(routes);
      }
    }
  } catch (error) {
    console.warn("Failed to parse stored hidden routes:", error);
  }
  return new Set();
};

const saveHiddenRoutes = (hiddenRoutes: Set<number>): void => {
  try {
    localStorage.setItem(
      HIDDEN_ROUTES_KEY,
      JSON.stringify(Array.from(hiddenRoutes)),
    );
  } catch (error) {
    console.warn("Failed to save hidden routes:", error);
  }
};

class MapStore {
  constructor() {
    makeAutoObservable(this);

    this.hiddenRoutes = getStoredHiddenRoutes();

    try {
      const stored = localStorage.getItem(HIDE_SIGHTS_BY_HIDDEN_ROUTES_KEY);
      this.hideSightsByHiddenRoutes = stored
        ? JSON.parse(stored) === true
        : false;
    } catch (e) {
      this.hideSightsByHiddenRoutes = false;
    }
  }

  routes: ApiRoute[] = [];
  stations: ApiStation[] = [];
  sights: ApiSight[] = [];
  hiddenRoutes: Set<number>;
  hideSightsByHiddenRoutes: boolean = false;
  routeStationsCache: Map<number, number[]> = new Map();
  routeSightsCache: Map<number, number[]> = new Map();
  setHideSightsByHiddenRoutes(val: boolean) {
    this.hideSightsByHiddenRoutes = val;
    try {
      localStorage.setItem(
        HIDE_SIGHTS_BY_HIDDEN_ROUTES_KEY,
        JSON.stringify(!!val),
      );
    } catch (e) {}
  }

  stationSort: SortType = "name_asc";
  sightSort: SortType = "name_asc";

  setStationSort = (sortType: SortType) => {
    this.stationSort = sortType;
  };

  setSightSort = (sortType: SortType) => {
    this.sightSort = sortType;
  };

  private sortFeatures<T extends ApiStation | ApiSight>(
    features: T[],
    sortType: SortType,
  ): T[] {
    const sorted = [...features];
    switch (sortType) {
      case "name_asc":
        return sorted.sort((a, b) => a.name.trim().localeCompare(b.name.trim()));
      case "name_desc":
        return sorted.sort((a, b) => b.name.trim().localeCompare(a.name.trim()));
      case "created_asc":
        return sorted.sort((a, b) => {
          if (
            "created_at" in a &&
            "created_at" in b &&
            a.created_at &&
            b.created_at
          ) {
            return (
              new Date(a.created_at).getTime() -
              new Date(b.created_at).getTime()
            );
          }

          return a.id - b.id;
        });
      case "created_desc":
        return sorted.sort((a, b) => {
          if (
            "created_at" in a &&
            "created_at" in b &&
            a.created_at &&
            b.created_at
          ) {
            return (
              new Date(b.created_at).getTime() -
              new Date(a.created_at).getTime()
            );
          }

          return b.id - a.id;
        });
      case "updated_asc":
        return sorted.sort((a, b) => {
          const aUpdated =
            ("updated_at" in a && a.updated_at) ||
            ("created_at" in a && a.created_at);
          const bUpdated =
            ("updated_at" in b && b.updated_at) ||
            ("created_at" in b && b.created_at);
          if (typeof aUpdated === "string" && typeof bUpdated === "string") {
            return new Date(aUpdated).getTime() - new Date(bUpdated).getTime();
          }
          return a.id - b.id;
        });
      case "updated_desc":
        return sorted.sort((a, b) => {
          const aUpdated =
            ("updated_at" in a && a.updated_at) ||
            ("created_at" in a && a.created_at);
          const bUpdated =
            ("updated_at" in b && b.updated_at) ||
            ("created_at" in b && b.created_at);
          if (typeof aUpdated === "string" && typeof bUpdated === "string") {
            return new Date(bUpdated).getTime() - new Date(aUpdated).getTime();
          }
          return b.id - a.id;
        });
      default:
        return sorted;
    }
  }

  get sortedStations(): ApiStation[] {
    return this.sortFeatures(this.stations, this.stationSort);
  }

  get sortedSights(): ApiSight[] {
    return this.sortFeatures(this.sights, this.sightSort);
  }

  get filteredStations(): ApiStation[] {
    const selectedCityId = selectedCityStore.selectedCityId;
    if (!selectedCityId) {
      return this.sortedStations;
    }
    return this.sortedStations.filter(
      (station) => station.city_id === selectedCityId,
    );
  }

  get filteredRoutes(): ApiRoute[] {
    const selectedCityId = selectedCityStore.selectedCityId;
    if (!selectedCityId) {
      return this.routes;
    }

    const carriers = carrierStore.carriers.ru.data;

    return this.routes.filter((route: ApiRoute) => {
      const carrier = carriers.find((c: any) => c.id === route.carrier_id);
      return carrier && carrier.city_id === selectedCityId;
    });
  }

  get filteredSights(): ApiSight[] {
    const selectedCityId = selectedCityStore.selectedCityId;
    const cityFiltered = !selectedCityId
      ? this.sortedSights
      : this.sortedSights.filter((sight) => sight.city_id === selectedCityId);

    if (!this.hideSightsByHiddenRoutes || this.hiddenRoutes.size === 0) {
      return cityFiltered;
    }

    const hiddenSightIds = new Set<number>();
    this.hiddenRoutes.forEach((routeId) => {
      const sightIds = this.routeSightsCache.get(routeId) || [];
      sightIds.forEach((id) => hiddenSightIds.add(id));
    });

    return cityFiltered.filter((s) => !hiddenSightIds.has(s.id));
  }

  getRoutes = async () => {
    const response = await languageInstance("ru").get("/route");
    const routesIds = response.data.map((route: any) => route.id);
    const routePromises = routesIds.map((id: number) =>
      languageInstance("ru").get(`/route/${id}`),
    );
    const routeResponses = await Promise.all(routePromises);
    this.routes = routeResponses.map((res) => ({
      id: res.data.id,
      route_number: res.data.route_number,
      route_name: res.data.route_name || "",
      path: res.data.path,
      center_latitude: res.data.center_latitude,
      center_longitude: res.data.center_longitude,
      carrier_id: res.data.carrier_id,
    }));

    this.routes = this.routes.sort((a, b) =>
      a.route_number.trim().localeCompare(b.route_number.trim()),
    );

    await this.preloadRouteStations(routesIds);

    await this.preloadRouteSights(routesIds);
  };

  preloadRouteStations = async (routesIds: number[]) => {
    const stationPromises = routesIds.map(async (routeId) => {
      try {
        const stationsResponse = await languageInstance("ru").get(
          `/route/${routeId}/station`,
        );
        const stationIds = stationsResponse.data.map((s: any) => s.id);
        this.routeStationsCache.set(routeId, stationIds);
      } catch (error) {
        console.error(
          `Failed to preload stations for route ${routeId}:`,
          error,
        );
      }
    });
    await Promise.all(stationPromises);
  };

  preloadRouteSights = async (routesIds: number[]) => {
    const sightPromises = routesIds.map(async (routeId) => {
      try {
        const sightsResponse = await languageInstance("ru").get(
          `/route/${routeId}/sight`,
        );
        const sightIds = sightsResponse.data.map((s: any) => s.id);
        this.routeSightsCache.set(routeId, sightIds);
      } catch (error) {
        console.error(`Failed to preload sights for route ${routeId}:`, error);
      }
    });
    await Promise.all(sightPromises);
  };

  getStations = async () => {
    const stations = await languageInstance("ru").get("/station");
    this.stations = stations.data.map((station: any) => ({
      ...station,
    }));
  };

  getSights = async () => {
    const sights = await languageInstance("ru").get("/sight");
    this.sights = sights.data.map((sight: any) => ({
      ...sight,
    }));
  };

  deleteFeature = async (featureType: string, id: number) => {
    await languageInstance("ru").delete(`/${featureType}/${id}`);
    if (featureType === "route") {
      this.routes = this.routes.filter((route) => route.id !== id);
    } else if (featureType === "station") {
      this.stations = this.stations.filter((station) => station.id !== id);
    } else if (featureType === "sight") {
      this.sights = this.sights.filter((sight) => sight.id !== id);
    }
  };

  createFeature = async (featureType: string, geoJsonFeature: any) => {
    const { geometry, properties } = geoJsonFeature;
    let createdItem: any;

    if (featureType === "station") {
      const name = properties.name || "Остановка 1";
      const latitude = geometry.coordinates[1];
      const longitude = geometry.coordinates[0];

      stationsStore.setLanguageCreateStationData("ru", {
        name,
        address: "",
        system_name: name,
      });
      stationsStore.setLanguageCreateStationData("en", {
        name,
        address: "",
        system_name: name,
      });
      stationsStore.setLanguageCreateStationData("zh", {
        name,
        address: "",
        system_name: name,
      });
      const selectedCityId = selectedCityStore.selectedCityId || 1;
      const selectedCityName =
        selectedCityStore.selectedCityName || "Неизвестный город";
      stationsStore.setCreateCommonData({
        latitude,
        longitude,
        city_id: selectedCityId,
        city: selectedCityName,
      });

      await stationsStore.createStation();
      createdItem =
        stationsStore.stationLists["ru"].data[
          stationsStore.stationLists["ru"].data.length - 1
        ];
    } else if (featureType === "route") {
      const route_number = properties.name || "Маршрут 1";
      const path = geometry.coordinates.map((c: any) => [c[1], c[0]]);

      let carrier_id = 0;
      let carrier = "";

      if (selectedCityStore.selectedCityId) {
        const carriersInCity = carrierStore.carriers.ru.data.filter(
          (c: any) => c.city_id === selectedCityStore.selectedCityId,
        );

        if (carriersInCity.length > 0) {
          carrier_id = carriersInCity[0].id;
          carrier = carriersInCity[0].full_name;
        }
      }

      const routeData = {
        route_number,
        path,
        center_latitude: path[0][0],
        center_longitude: path[0][1],
        carrier,
        carrier_id,
        governor_appeal: 0,
        rotate: 0,
        route_direction: false,
        route_sys_number: route_number,
        scale_max: 100,
        scale_min: 10,
      };

      await routeStore.createRoute(routeData);

      if (!carrier_id && selectedCityStore.selectedCityId) {
        toast.error(
          "В выбранном городе нет доступных перевозчиков, маршрут отображается в общем списке",
        );
      }
      createdItem = routeStore.routes.data[routeStore.routes.data.length - 1];
    } else if (featureType === "sight") {
      const name = properties.name || "Достопримечательность 1";
      const latitude = geometry.coordinates[1];
      const longitude = geometry.coordinates[0];

      sightsStore.updateCreateSight("ru", { name, address: "" });
      sightsStore.updateCreateSight("en", { name, address: "" });
      sightsStore.updateCreateSight("zh", { name, address: "" });

      const selectedCityId = selectedCityStore.selectedCityId || 1;
      await sightsStore.createSightAction(selectedCityId, {
        latitude,
        longitude,
      });
      createdItem = sightsStore.sights[sightsStore.sights.length - 1];
    } else {
      throw new Error(`Unknown feature type for creation: ${featureType}`);
    }

    if (featureType === "route") this.routes.push(createdItem);
    else if (featureType === "station") this.stations.push(createdItem);
    else if (featureType === "sight") this.sights.push(createdItem);

    return createdItem;
  };

  updateFeature = async (featureType: string, geoJsonFeature: any) => {
    const { geometry, properties, id } = geoJsonFeature;
    const numericId = parseInt(String(id).split("-")[1], 10);
    if (isNaN(numericId)) {
      throw new Error(`Invalid feature ID for update: ${id}`);
    }

    let data;
    if (featureType === "station") {
      data = {
        name: properties.name,
        latitude: geometry.coordinates[1],
        longitude: geometry.coordinates[0],
      };
    } else if (featureType === "route") {
      const lineGeom = new GeoJSON().readGeometry(geometry, {
        dataProjection: "EPSG:4326",
        featureProjection: "EPSG:3857",
      });
      const centerCoords = getCenter(lineGeom.getExtent());
      const [center_longitude, center_latitude] = toLonLat(
        centerCoords,
        "EPSG:3857",
      );
      data = {
        route_number: properties.name,
        path: geometry.coordinates.map((coord: any) => [coord[1], coord[0]]),
        center_latitude,
        center_longitude,
      };
    } else if (featureType === "sight") {
      data = {
        name: properties.name,
        description: properties.description,
        latitude: geometry.coordinates[1],
        longitude: geometry.coordinates[0],
      };
    } else {
      throw new Error(`Unknown feature type for update: ${featureType}`);
    }

    const findOldData = (store: any[], id: number) =>
      store.find((f: any) => f.id === id);
    let oldData;
    if (featureType === "route") oldData = findOldData(this.routes, numericId);
    else if (featureType === "station")
      oldData = findOldData(this.stations, numericId);
    else if (featureType === "sight")
      oldData = findOldData(this.sights, numericId);

    if (!oldData) {
      if (properties.isProxy) {
        return;
      }
      throw new Error(
        `Could not find old data for ${featureType} with id ${numericId}`,
      );
    }

    let requestBody: any;
    if (featureType === "route") {
      requestBody = {
        ...oldData,
        ...data,
      };
    } else {
      requestBody = { ...oldData, ...data };
    }

    if (properties.isProxy) {
      return requestBody;
    }

    const response = await languageInstance("ru").patch(
      `/${featureType}/${numericId}`,
      requestBody,
    );

    const updateStore = (store: any[], updatedItem: any) => {
      const index = store.findIndex((f) => f.id === updatedItem.id);
      if (index !== -1) store[index] = updatedItem;
      else store.push(updatedItem);
    };

    if (featureType === "route") updateStore(this.routes, response.data);
    else if (featureType === "station")
      updateStore(this.stations, response.data);
    else if (featureType === "sight") updateStore(this.sights, response.data);

    return response.data;
  };
}

const mapStore = new MapStore();

if (typeof window !== "undefined") {
  (window as any).mapStore = mapStore;
}

export const mapConfig = {
  center: [30.311, 59.94] as [number, number],
  zoom: 13,
};

const MAP_POSITION_KEY = "mapPosition";
const ACTIVE_SECTION_KEY = "mapActiveSection";

interface MapPosition {
  center: [number, number];
  zoom: number;
}

const getStoredMapPosition = (): MapPosition | null => {
  try {
    const stored = localStorage.getItem(MAP_POSITION_KEY);
    if (stored) {
      const position = JSON.parse(stored);
      if (
        position &&
        Array.isArray(position.center) &&
        position.center.length === 2 &&
        typeof position.zoom === "number" &&
        position.zoom >= 0 &&
        position.zoom <= 20
      ) {
        return position;
      }
    }
  } catch (error) {
    console.warn("Failed to parse stored map position:", error);
  }
  return null;
};

const saveMapPosition = (position: MapPosition): void => {
  try {
    localStorage.setItem(MAP_POSITION_KEY, JSON.stringify(position));
  } catch (error) {
    console.warn("Failed to save map position:", error);
  }
};

const getStoredActiveSection = (): string | null => {
  try {
    const stored = localStorage.getItem(ACTIVE_SECTION_KEY);
    if (stored) {
      return stored;
    }
  } catch (error) {
    console.warn("Failed to get stored active section:", error);
  }
  return null;
};

const saveActiveSection = (section: string | null): void => {
  try {
    if (section) {
      localStorage.setItem(ACTIVE_SECTION_KEY, section);
    } else {
      localStorage.removeItem(ACTIVE_SECTION_KEY);
    }
  } catch (error) {
    console.warn("Failed to save active section:", error);
  }
};

interface MapServiceConfig {
  target: HTMLElement;
  center: [number, number];
  zoom: number;
}

type FeatureType = "station" | "route" | "sight";

class MapService {
  private map: OLMap | null;
  public pointSource: VectorSource<Feature<Point>>;
  public lineSource: VectorSource<Feature<LineString>>;
  public clusterLayer: VectorLayer<Cluster>;
  public routeLayer: VectorLayer<VectorSource<Feature<LineString>>>;
  private clusterSource: Cluster;
  private clusterStyleCache: { [key: number]: Style };

  private tooltipElement: HTMLElement;
  private tooltipOverlay: Overlay | null;
  private mode: string | null;

  private currentDrawingType: "Point" | "LineString" | null;
  private currentDrawingFeatureType: FeatureType | null;
  private currentInteraction: Draw | null;
  private modifyInteraction: Modify;
  private selectInteraction: Select;
  private hoveredFeatureId: string | number | null;
  private boundHandlePointerMove: (
    event: MapBrowserEvent<PointerEvent>,
  ) => void;
  private boundHandlePointerLeave: () => void;
  private boundHandleContextMenu: (event: MouseEvent) => void;
  private boundHandleKeyDown: (event: KeyboardEvent) => void;
  private lassoInteraction: Draw | null = null;
  private selectedIds: Set<string | number> = new Set();
  private onSelectionChange: ((ids: Set<string | number>) => void) | null =
    null;
  private isCreating: boolean = false;

  private defaultStyle: Style;
  private selectedStyle: Style;
  private drawStyle: Style;
  private busIconStyle: Style;
  private selectedBusIconStyle: Style;
  private drawBusIconStyle: Style;
  private sightIconStyle: Style;
  private selectedSightIconStyle: Style;
  private drawSightIconStyle: Style;

  private universalHoverStylePoint: Style;
  private hoverSightIconStyle: Style;
  private universalHoverStyleLine: Style;

  private setLoading: (loading: boolean) => void;
  private setError: (error: string | null) => void;
  private onModeChangeCallback: (mode: string) => void;
  private onFeaturesChange: (features: Feature<Geometry>[]) => void;
  private onFeatureSelect: (feature: Feature<Geometry> | null) => void;

  constructor(
    config: MapServiceConfig,
    setLoading: (loading: boolean) => void,
    setError: (error: string | null) => void,
    onModeChangeCallback: (mode: string) => void,
    onFeaturesChange: (features: Feature<Geometry>[]) => void,
    onFeatureSelect: (feature: Feature<Geometry> | null) => void,
    tooltipElement: HTMLElement,
    onSelectionChange?: (ids: Set<string | number>) => void,
  ) {
    this.map = null;
    this.tooltipElement = tooltipElement;
    this.tooltipOverlay = null;
    this.mode = null;
    this.currentDrawingType = null;
    this.currentDrawingFeatureType = null;
    this.currentInteraction = null;
    this.hoveredFeatureId = null;
    this.clusterStyleCache = {};

    this.setLoading = setLoading;
    this.setError = setError;
    this.onModeChangeCallback = onModeChangeCallback;
    this.onFeaturesChange = onFeaturesChange;
    this.onFeatureSelect = onFeatureSelect;

    this.defaultStyle = new Style({
      fill: new Fill({ color: "rgba(66, 153, 225, 0.2)" }),
      stroke: new Stroke({ color: "#3182ce", width: 8 }),
    });

    this.selectedStyle = new Style({
      fill: new Fill({ color: "rgba(221, 107, 32, 0.3)" }),
      stroke: new Stroke({ color: "#dd6b20", width: 8 }),
    });

    this.drawStyle = new Style({
      fill: new Fill({ color: "rgba(74, 222, 128, 0.3)" }),
      stroke: new Stroke({
        color: "rgba(34, 197, 94, 0.7)",
        width: 8,
        lineDash: [5, 5],
      }),
      image: new CircleStyle({
        radius: 5,
        fill: new Fill({ color: "rgba(34, 197, 94, 0.7)" }),
      }),
    });

    this.busIconStyle = new Style({
      image: new CircleStyle({
        radius: 8,
        fill: new Fill({ color: "rgba(0, 60, 255, 0.8)" }),
        stroke: new Stroke({ color: "#ffffff", width: 1.5 }),
      }),
    });
    this.selectedBusIconStyle = new Style({
      image: new CircleStyle({
        radius: 10,
        fill: new Fill({ color: "rgba(221, 107, 32, 0.9)" }),
        stroke: new Stroke({ color: "#ffffff", width: 2 }),
      }),
    });
    this.drawBusIconStyle = new Style({
      image: new CircleStyle({
        radius: 8,
        fill: new Fill({ color: "rgba(100, 180, 100, 0.8)" }),
        stroke: new Stroke({ color: "#ffffff", width: 1.5 }),
      }),
    });

    this.sightIconStyle = new Style({
      image: new RegularShape({
        fill: new Fill({ color: "rgba(139, 92, 246, 0.8)" }),
        stroke: new Stroke({ color: "#ffffff", width: 2 }),
        points: 5,
        radius: 12,
        radius2: 6,
        angle: 0,
      }),
    });
    this.selectedSightIconStyle = new Style({
      image: new RegularShape({
        fill: new Fill({ color: "rgba(221, 107, 32, 0.9)" }),
        stroke: new Stroke({ color: "#ffffff", width: 2 }),
        points: 5,
        radius: 12,
        radius2: 6,
        angle: 0,
      }),
    });
    this.drawSightIconStyle = new Style({
      image: new RegularShape({
        fill: new Fill({ color: "rgba(100, 180, 100, 0.8)" }),
        stroke: new Stroke({ color: "#ffffff", width: 1.5 }),
        points: 5,
        radius: 12,
        radius2: 6,
        angle: 0,
      }),
    });

    this.universalHoverStylePoint = new Style({
      image: new CircleStyle({
        radius: 11,
        fill: new Fill({ color: "rgba(255, 165, 0, 0.7)" }),
        stroke: new Stroke({ color: "rgba(255,255,255,0.8)", width: 2 }),
      }),
      zIndex: Infinity,
    });
    this.hoverSightIconStyle = new Style({
      image: new RegularShape({
        fill: new Fill({ color: "rgba(255, 165, 0, 0.7)" }),
        stroke: new Stroke({ color: "rgba(255,255,255,0.8)", width: 2 }),
        points: 5,
        radius: 15,
        radius2: 7.5,
        angle: 0,
      }),
      zIndex: Infinity,
    });
    this.universalHoverStyleLine = new Style({
      stroke: new Stroke({ color: "rgba(255, 165, 0, 0.8)", width: 8 }),
      zIndex: Infinity,
    });

    this.pointSource = new VectorSource();
    this.lineSource = new VectorSource();
    this.clusterSource = new Cluster({
      distance: 45,
      source: this.pointSource,
    });

    this.routeLayer = new VectorLayer({
      source: this.lineSource,

      style: (featureLike: FeatureLike) => {
        const feature = featureLike as Feature<Geometry>;
        if (!feature) return this.defaultStyle;
        const fId = feature.getId();

        const isSelected =
          this.selectInteraction?.getFeatures().getArray().includes(feature) ||
          (fId !== undefined && this.selectedIds.has(fId));
        const isHovered = this.hoveredFeatureId === fId;

        if (isHovered) return this.universalHoverStyleLine;
        if (isSelected) return this.selectedStyle;
        return this.defaultStyle;
      },
    });

    this.clusterLayer = new VectorLayer({
      source: this.clusterSource,
      style: (featureLike: FeatureLike) => {
        const clusterFeature = featureLike as Feature<Point>;
        const featuresInCluster = clusterFeature.get(
          "features",
        ) as Feature<Point>[];
        const size = featuresInCluster.length;

        if (size > 1) {
          let style = this.clusterStyleCache[size];
          if (!style) {
            style = new Style({
              image: new CircleStyle({
                radius: 12 + Math.log(size) * 3,
                fill: new Fill({ color: "rgba(56, 189, 248, 0.9)" }),
                stroke: new Stroke({ color: "#fff", width: 2 }),
              }),
              text: new Text({
                text: size.toString(),
                fill: new Fill({ color: "#fff" }),
                font: "bold 12px sans-serif",
              }),
            });
            this.clusterStyleCache[size] = style;
          }
          return style;
        } else {
          const originalFeature = featuresInCluster[0];
          const fId = originalFeature.getId();
          const featureType = originalFeature.get("featureType");

          const isSelected = fId !== undefined && this.selectedIds.has(fId);
          const isHovered = this.hoveredFeatureId === fId;

          if (isHovered) {
            return featureType === "sight"
              ? this.hoverSightIconStyle
              : this.universalHoverStylePoint;
          }

          if (isSelected) {
            if (featureType === "sight") return this.selectedSightIconStyle;
            return this.selectedBusIconStyle;
          }

          if (featureType === "sight") return this.sightIconStyle;
          return this.busIconStyle;
        }
      },
    });

    this.clusterSource.on("change", () => {
      this.routeLayer.changed();
    });

    this.boundHandlePointerMove = this.handlePointerMove.bind(this);
    this.boundHandlePointerLeave = this.handlePointerLeave.bind(this);
    this.boundHandleContextMenu = this.handleContextMenu.bind(this);
    this.boundHandleKeyDown = this.handleKeyDown.bind(this);

    this.pointSource.on(
      "addfeature",
      this.handleFeatureEvent.bind(this) as any,
    );
    this.pointSource.on("removefeature", () => this.updateFeaturesInReact());
    this.pointSource.on(
      "changefeature",
      this.handleFeatureChange.bind(this) as any,
    );
    this.lineSource.on("addfeature", this.handleFeatureEvent.bind(this) as any);
    this.lineSource.on("removefeature", () => this.updateFeaturesInReact());
    this.lineSource.on(
      "changefeature",
      this.handleFeatureChange.bind(this) as any,
    );

    let renderCompleteHandled = false;
    const MAP_LOAD_TIMEOUT = 15000;
    try {
      const storedPosition = getStoredMapPosition();
      const initialCenter = storedPosition?.center || config.center;
      const initialZoom = storedPosition?.zoom || config.zoom;

      this.map = new OLMap({
        target: config.target,
        layers: [
          new TileLayer({ source: new OSM() }),
          this.routeLayer,
          this.clusterLayer,
        ],
        view: new View({
          center: transform(initialCenter, "EPSG:4326", "EPSG:3857"),
          zoom: initialZoom,
        }),
        interactions: [
          new MouseWheelZoom(),
          new KeyboardPan(),
          new KeyboardZoom(),
          new PinchZoom(),
          new PinchRotate(),

          new DragPan({
            condition: (event) => {
              const originalEvent = event.originalEvent;
              if (!originalEvent) return false;

              if (
                originalEvent.type === "pointerdown" ||
                originalEvent.type === "pointermove"
              ) {
                const pointerEvent = originalEvent as PointerEvent;
                return pointerEvent.buttons === 4;
              }

              return false;
            },
          }),
        ],
        controls: [],
      });

      this.map.getView().on("change:center", () => {
        const center = this.map?.getView().getCenter();
        const zoom = this.map?.getView().getZoom();
        if (center && zoom !== undefined && this.map) {
          const [lon, lat] = toLonLat(
            center,
            this.map.getView().getProjection(),
          );
          saveMapPosition({ center: [lon, lat], zoom });
        }
      });

      this.map.getView().on("change:resolution", () => {
        const center = this.map?.getView().getCenter();
        const zoom = this.map?.getView().getZoom();
        if (center && zoom !== undefined && this.map) {
          const [lon, lat] = toLonLat(
            center,
            this.map.getView().getProjection(),
          );
          saveMapPosition({ center: [lon, lat], zoom });
        }
      });

      if (this.tooltipElement && this.map) {
        this.tooltipOverlay = new Overlay({
          element: this.tooltipElement,
          offset: [15, 0],
          positioning: "center-left",
        });
        this.map.addOverlay(this.tooltipOverlay);
      }
      this.map.once("rendercomplete", () => {
        if (!renderCompleteHandled) {
          this.setLoading(false);
          renderCompleteHandled = true;
          setError(null);
        }
      });
      setTimeout(() => {
        if (!renderCompleteHandled && this.map) {
          this.setLoading(false);
          this.setError("Карта не загрузилась вовремя.");
          renderCompleteHandled = true;
        }
      }, MAP_LOAD_TIMEOUT);
    } catch (error) {
      this.setError("Критическая ошибка при инициализации карты.");
      this.setLoading(false);
      renderCompleteHandled = true;
    }

    this.selectInteraction = new Select({
      style: null,
      condition: singleClick,
      filter: (feature: FeatureLike, l: Layer<Source, any> | null) => {
        if (l !== this.clusterLayer && l !== this.routeLayer) return false;
        const originalFeatures = feature.get("features");
        if (
          originalFeatures &&
          originalFeatures.length === 1 &&
          originalFeatures[0].get("isProxy")
        )
          return false;
        return true;
      },
      multi: true,
    });

    this.modifyInteraction = new Modify({
      features: this.selectInteraction.getFeatures(),
      style: new Style({
        image: new CircleStyle({
          radius: 6,
          fill: new Fill({
            color: "rgba(255, 255, 255, 0.8)",
          }),
          stroke: new Stroke({
            color: "#0099ff",
            width: 2.5,
          }),
        }),
      }),
      deleteCondition: (e: MapBrowserEvent<any>) => {
        if (!doubleClick(e)) {
          return false;
        }
        const selectedFeatures = this.selectInteraction.getFeatures();
        if (selectedFeatures.getLength() !== 1) {
          return true;
        }
        const feature = selectedFeatures.item(0) as Feature<Geometry>;
        const geometry = feature.getGeometry();
        if (!geometry || geometry.getType() !== "LineString") {
          return true;
        }
        const lineString = geometry as LineString;
        const coordinates = lineString.getCoordinates();
        if (coordinates.length <= 2) {
          toast.info("В маршруте должно быть не менее 2 точек.");
          return false;
        }
        const clickCoordinate = e.coordinate;
        let closestVertexIndex = -1;
        let minDistanceSq = Infinity;
        coordinates.forEach((vertex, index) => {
          const dx = vertex[0] - clickCoordinate[0];
          const dy = vertex[1] - clickCoordinate[1];
          const distanceSq = dx * dx + dy * dy;
          if (distanceSq < minDistanceSq) {
            minDistanceSq = distanceSq;
            closestVertexIndex = index;
          }
        });
        if (
          closestVertexIndex === 0 ||
          closestVertexIndex === coordinates.length - 1
        ) {
          return false;
        }
        return true;
      },
    });

    this.modifyInteraction.on("modifyend", (event) => {
      event.features.getArray().forEach((feature) => {
        this.saveModifiedFeature(feature as Feature<Geometry>);
      });
    });

    if (this.map) {
      this.map.on("dblclick", (event: MapBrowserEvent<any>) => {
        if (this.mode !== "edit") return;

        const layerFilter = (l: Layer<Source, any>) => l === this.routeLayer;

        const feature = this.map?.forEachFeatureAtPixel(
          event.pixel,
          (f: FeatureLike) => f as Feature<Geometry>,
          { layerFilter, hitTolerance: 5 },
        );

        if (!feature) return;

        const featureType = feature.get("featureType");
        if (featureType !== "route") return;

        const geometry = feature.getGeometry();
        if (!geometry || geometry.getType() !== "LineString") return;

        const lineString = geometry as LineString;
        const coordinates = lineString.getCoordinates();

        if (coordinates.length <= 2) {
          toast.info("В маршруте должно быть не менее 2 точек.");
          return;
        }

        const clickCoordinate = event.coordinate;
        let closestIndex = -1;
        let minDistanceSq = Infinity;

        coordinates.forEach((vertex, index) => {
          const dx = vertex[0] - clickCoordinate[0];
          const dy = vertex[1] - clickCoordinate[1];
          const distanceSq = dx * dx + dy * dy;
          if (distanceSq < minDistanceSq) {
            minDistanceSq = distanceSq;
            closestIndex = index;
          }
        });

        if (closestIndex === 0 || closestIndex === coordinates.length - 1) {
          return;
        }

        const newCoordinates = coordinates.filter(
          (_, index) => index !== closestIndex,
        );
        lineString.setCoordinates(newCoordinates);
        this.saveModifiedFeature(feature);
      });
    }

    this.lassoInteraction = new Draw({
      type: "Polygon",
      style: new Style({
        stroke: new Stroke({ color: "#14b8a6", width: 2 }),
        fill: new Fill({ color: "rgba(20, 184, 166, 0.1)" }),
      }),
    });
    this.lassoInteraction.setActive(false);
    this.lassoInteraction.on("drawend", (event: DrawEvent) => {
      const geometry = event.feature.getGeometry() as Polygon;
      const extent = geometry.getExtent();
      const selected = new Set<string | number>();

      this.pointSource.forEachFeatureInExtent(extent, (f) => {
        if (f.get("isProxy")) return;
        const geom = f.getGeometry();
        if (geom && geom.getType() === "Point") {
          const pointCoords = (geom as Point).getCoordinates();
          if (geometry.intersectsCoordinate(pointCoords)) {
            if (f.getId() !== undefined) selected.add(f.getId()!);
          }
        }
      });

      this.lineSource.forEachFeatureInExtent(
        extent,
        (f: Feature<LineString>) => {
          const lineGeom = f.getGeometry();
          if (lineGeom) {
            const intersects = lineGeom
              .getCoordinates()
              .some((coord) => geometry.intersectsCoordinate(coord));
            if (intersects && f.getId() !== undefined) {
              selected.add(f.getId()!);
            }
          }
        },
      );

      this.setSelectedIds(selected);
      this.deactivateLasso();
    });

    if (this.map) {
      this.map.addInteraction(this.modifyInteraction);
      this.map.addInteraction(this.selectInteraction);
      this.map.addInteraction(this.lassoInteraction);
      this.modifyInteraction.setActive(false);
      this.selectInteraction.setActive(false);
      this.lassoInteraction.setActive(false);

      this.selectInteraction.on("select", (e: SelectEvent) => {
        if (this.mode !== "edit" || !this.map) return;

        const ctrlKey =
          e.mapBrowserEvent.originalEvent.ctrlKey ||
          e.mapBrowserEvent.originalEvent.metaKey;

        if (e.selected.length === 1 && !ctrlKey) {
          const clickedFeature = e.selected[0];
          const originalFeatures = clickedFeature.get("features");

          if (originalFeatures && originalFeatures.length > 1) {
            const extent = createEmpty();
            originalFeatures.forEach((feat: Feature<Point>) => {
              const geom = feat.getGeometry();
              if (geom) extend(extent, geom.getExtent());
            });
            this.map.getView().fit(extent, {
              duration: 500,
              padding: [60, 60, 60, 60],
              maxZoom: 18,
            });

            this.selectInteraction.getFeatures().clear();
            this.setSelectedIds(new Set());
            return;
          }
        }

        const newSelectedIds = ctrlKey
          ? new Set(this.selectedIds)
          : new Set<string | number>();

        e.selected.forEach((feature) => {
          const originalFeatures = feature.get("features");
          let targetId: string | number | undefined;

          if (originalFeatures && originalFeatures.length > 0) {
            targetId = originalFeatures[0].getId();
          } else {
            targetId = feature.getId();
          }

          if (targetId !== undefined) {
            if (ctrlKey && newSelectedIds.has(targetId)) {
              newSelectedIds.delete(targetId);
            } else {
              newSelectedIds.add(targetId);
            }
          }
        });

        if (!ctrlKey) {
          e.deselected.forEach((feature) => {
            const originalFeatures = feature.get("features");
            let targetId: string | number | undefined;

            if (originalFeatures && originalFeatures.length > 0) {
              targetId = originalFeatures[0].getId();
            } else {
              targetId = feature.getId();
            }

            if (targetId !== undefined) {
              newSelectedIds.delete(targetId);
            }
          });
        }

        this.setSelectedIds(newSelectedIds);
      });

      this.map.on("pointermove", this.boundHandlePointerMove as any);
      const targetEl = this.map.getTargetElement();
      if (targetEl instanceof HTMLElement) {
        targetEl.style.cursor = "pointer";
        targetEl.addEventListener("contextmenu", this.boundHandleContextMenu);
        targetEl.addEventListener("pointerleave", this.boundHandlePointerLeave);

        targetEl.addEventListener("pointerdown", (e) => {
          if (e.buttons === 4) {
            e.preventDefault();
            targetEl.style.cursor = "grabbing";
          }
        });

        targetEl.addEventListener("pointerup", (e) => {
          if (e.button === 1) {
            e.preventDefault();
            targetEl.style.cursor = "pointer";
          }
        });

        targetEl.addEventListener("mousedown", (e) => {
          if (e.button === 1) {
            e.preventDefault();
            targetEl.style.cursor = "grabbing";
          }
        });

        targetEl.addEventListener("mouseup", (e) => {
          if (e.button === 1) {
            e.preventDefault();
            targetEl.style.cursor = "pointer";
          }
        });

        targetEl.addEventListener("auxclick", (e) => {
          if (e.button === 1) {
            e.preventDefault();
          }
        });
      }
      document.addEventListener("keydown", this.boundHandleKeyDown);
      this.activateEditMode();
    }

    if (onSelectionChange) {
      this.onSelectionChange = onSelectionChange;
    }
  }

  public unselect(): void {
    this.selectInteraction.getFeatures().clear();
    this.modifyInteraction.setActive(false);
    this.onFeatureSelect(null);
    this.setSelectedIds(new Set());
  }

  public loadFeaturesFromApi(
    _apiStations: typeof mapStore.stations,
    _apiRoutes: typeof mapStore.routes,
    _apiSights: typeof mapStore.sights,
  ): void {
    if (!this.map) return;

    const projection = this.map.getView().getProjection();
    const pointFeatures: Feature<Point>[] = [];
    const lineFeatures: Feature<LineString>[] = [];

    const filteredStations = mapStore.filteredStations;
    const filteredSights = mapStore.filteredSights;
    const filteredRoutes = mapStore.filteredRoutes;

    const stationsInVisibleRoutes = new Set<number>();
    filteredRoutes
      .filter((route) => !mapStore.hiddenRoutes.has(route.id))
      .forEach((route) => {
        const stationIds = mapStore.routeStationsCache.get(route.id) || [];
        stationIds.forEach((id) => stationsInVisibleRoutes.add(id));
      });

    let skippedStations = 0;
    filteredStations.forEach((station) => {
      if (station.longitude == null || station.latitude == null) return;

      if (!stationsInVisibleRoutes.has(station.id)) {
        skippedStations++;
        return;
      }

      const point = new Point(
        transform(
          [station.longitude, station.latitude],
          "EPSG:4326",
          projection,
        ),
      );
      const feature = new Feature({ geometry: point, name: station.name });
      feature.setId(`station-${station.id}`);
      feature.set("featureType", "station");
      pointFeatures.push(feature);
    });

    filteredSights.forEach((sight) => {
      if (sight.longitude == null || sight.latitude == null) return;
      const point = new Point(
        transform([sight.longitude, sight.latitude], "EPSG:4326", projection),
      );
      const feature = new Feature({
        geometry: point,
        name: sight.name,
        description: sight.description,
      });
      feature.setId(`sight-${sight.id}`);
      feature.set("featureType", "sight");
      pointFeatures.push(feature);
    });

    filteredRoutes.forEach((route) => {
      if (!route.path || route.path.length === 0) return;

      if (mapStore.hiddenRoutes.has(route.id)) return;

      const coordinates = route.path
        .filter((c) => c && c[0] != null && c[1] != null)
        .map((c: [number, number]) =>
          transform([c[1], c[0]], "EPSG:4326", projection),
        );

      if (coordinates.length === 0) return;
      const routeId = `route-${route.id}`;
      const line = new LineString(coordinates);
      const lineFeature = new Feature({
        geometry: line,
        name: route.route_number,
      });
      lineFeature.setId(routeId);
      lineFeature.set("featureType", "route");
      lineFeatures.push(lineFeature);
    });

    this.pointSource.addFeatures(pointFeatures);
    this.lineSource.addFeatures(lineFeatures);

    this.updateFeaturesInReact();
  }

  private updateFeaturesInReact(): void {
    if (this.onFeaturesChange) {
      const allFeatures = [
        ...this.pointSource.getFeatures(),
        ...this.lineSource.getFeatures(),
      ];
      this.onFeaturesChange(allFeatures);
    }
  }

  private handlePointerLeave(): void {
    if (this.hoveredFeatureId) {
      this.hoveredFeatureId = null;
      this.clusterLayer.changed();
      this.routeLayer.changed();
    }
    if (this.tooltipOverlay) this.tooltipOverlay.setPosition(undefined);

    if (this.map) {
      const targetEl = this.map.getTargetElement();
      if (targetEl instanceof HTMLElement) {
        targetEl.style.cursor = "pointer";
      }
    }
  }

  private handleKeyDown(event: KeyboardEvent): void {
    if (event.key === "Escape") {
      this.unselect();
    }
  }

  private setMode(newMode: string): void {
    if (!this.map) return;
    const oldMode = this.mode;
    this.mode = newMode;
    if (this.onModeChangeCallback) this.onModeChangeCallback(newMode);

    if (this.hoveredFeatureId && oldMode !== newMode) {
      this.hoveredFeatureId = null;
      this.clusterLayer.changed();
      this.routeLayer.changed();
      if (this.tooltipOverlay) this.tooltipOverlay.setPosition(undefined);
    }

    if (this.currentInteraction instanceof Draw) {
      this.map.removeInteraction(this.currentInteraction);
      this.currentInteraction = null;
    }

    if (newMode === "edit") {
      this.selectInteraction.setActive(true);
    } else {
      this.unselect();
      this.selectInteraction.setActive(false);
    }
  }

  public activateEditMode(): void {
    this.currentDrawingType = null;
    this.setMode("edit");
  }

  public startDrawing(
    type: "Point" | "LineString",
    featureType: FeatureType,
  ): void {
    if (!this.map) return;

    this.currentDrawingType = type;
    this.currentDrawingFeatureType = featureType;

    const drawingMode = `drawing-${featureType}`;
    this.setMode(drawingMode);

    if (this.currentInteraction instanceof Draw) {
      this.map.removeInteraction(this.currentInteraction);
    }

    let styleForDrawing: Style;
    if (featureType === "station") styleForDrawing = this.drawBusIconStyle;
    else if (featureType === "sight") styleForDrawing = this.drawSightIconStyle;
    else styleForDrawing = this.drawStyle;

    const sourceForDrawing =
      type === "Point" ? this.pointSource : this.lineSource;

    this.currentInteraction = new Draw({
      source: sourceForDrawing,
      type,
      style: styleForDrawing,
    });

    this.currentInteraction.on("drawend", async (event: DrawEvent) => {
      const feature = event.feature as Feature<Geometry>;
      const fType = this.currentDrawingFeatureType;
      if (!fType) return;

      if (this.isCreating) {
        toast.warning("Дождитесь завершения создания предыдущего объекта.");

        const sourceForDrawing =
          type === "Point" ? this.pointSource : this.lineSource;
        setTimeout(() => {
          if (sourceForDrawing.hasFeature(feature as any)) {
            sourceForDrawing.removeFeature(feature as any);
          }
        }, 0);
        return;
      }

      feature.set("featureType", fType);

      let resourceName: string;

      switch (fType) {
        case "station":
          const stationNumbers = mapStore.stations
            .map((station) => {
              const match = station.name?.match(/^Остановка (\d+)$/);
              return match ? parseInt(match[1], 10) : 0;
            })
            .filter((num) => num > 0);
          const nextStationNumber =
            stationNumbers.length > 0 ? Math.max(...stationNumbers) + 1 : 1;
          resourceName = `Остановка ${nextStationNumber}`;
          break;
        case "sight":
          const sightNumbers = mapStore.sights
            .map((sight) => {
              const match = sight.name?.match(/^Достопримечательность (\d+)$/);
              return match ? parseInt(match[1], 10) : 0;
            })
            .filter((num) => num > 0);
          const nextSightNumber =
            sightNumbers.length > 0 ? Math.max(...sightNumbers) + 1 : 1;
          resourceName = `Достопримечательность ${nextSightNumber}`;
          break;
        case "route":
          const routeNumbers = mapStore.routes
            .map((route) => {
              const match = route.route_number?.match(/^Маршрут (\d+)$/);
              return match ? parseInt(match[1], 10) : 0;
            })
            .filter((num) => num > 0);
          const nextRouteNumber =
            routeNumbers.length > 0 ? Math.max(...routeNumbers) + 1 : 1;
          resourceName = `Маршрут ${nextRouteNumber}`;
          break;
        default:
          resourceName = "Объект";
      }
      feature.set("name", resourceName);

      if (fType === "route") {
        this.activateEditMode();
      }

      await this.saveNewFeature(feature);
    });

    this.map.addInteraction(this.currentInteraction);
  }

  private handleContextMenu(event: MouseEvent): void {
    event.preventDefault();
    if (
      this.mode?.startsWith("drawing-") &&
      this.currentInteraction instanceof Draw
    ) {
      this.finishDrawing();
      if (this.currentDrawingType === "LineString") {
        this.stopDrawing();
      }
    }
  }

  private stopDrawing() {
    if (this.map && this.currentInteraction) {
      try {
        this.currentInteraction.abortDrawing();
      } catch (e) {}
      this.map.removeInteraction(this.currentInteraction);
    }
    this.currentInteraction = null;
    this.currentDrawingType = null;
    this.currentDrawingFeatureType = null;
  }

  public finishDrawing(): void {
    if (!this.currentInteraction) return;

    if (this.isCreating) {
      toast.warning("Дождитесь завершения создания предыдущего объекта.");
      return;
    }

    try {
      this.currentInteraction.finishDrawing();
    } catch (e) {
      this.stopDrawing();
    }
  }

  private handlePointerMove(event: MapBrowserEvent<PointerEvent>): void {
    if (!this.map || event.dragging) {
      if (this.hoveredFeatureId) {
        this.hoveredFeatureId = null;
        this.clusterLayer.changed();
        this.routeLayer.changed();
      }
      if (this.tooltipOverlay) this.tooltipOverlay.setPosition(undefined);
      return;
    }

    const layerFilter = (l: Layer<Source, any>) =>
      l === this.clusterLayer || l === this.routeLayer;

    const hit = this.map.hasFeatureAtPixel(event.pixel, {
      layerFilter,
      hitTolerance: 5,
    });

    this.map.getTargetElement().style.cursor = hit ? "pointer" : "pointer";

    const featureAtPixel: Feature<Geometry> | undefined =
      this.map.forEachFeatureAtPixel(
        event.pixel,
        (f: FeatureLike) => f as Feature<Geometry>,
        { layerFilter, hitTolerance: 5 },
      );

    let finalFeature: Feature<Geometry> | null = null;
    if (featureAtPixel) {
      const originalFeatures = featureAtPixel.get("features");
      if (originalFeatures && originalFeatures.length > 0) {
        if (originalFeatures[0].get("isProxy")) return;
        finalFeature = originalFeatures[0];
      } else {
        finalFeature = featureAtPixel;
      }
    }

    const newHoveredFeatureId = finalFeature ? finalFeature.getId() : null;

    if (this.tooltipOverlay && this.tooltipElement) {
      if (this.mode === "edit" && finalFeature) {
        const name = finalFeature.get("name");
        if (name) {
          this.tooltipElement.innerHTML = name as string;
          this.tooltipOverlay.setPosition(event.coordinate);
        } else {
          this.tooltipOverlay.setPosition(undefined);
        }
      } else {
        this.tooltipOverlay.setPosition(undefined);
      }
    }

    if (this.hoveredFeatureId !== newHoveredFeatureId) {
      this.hoveredFeatureId = newHoveredFeatureId as string | number | null;
      this.clusterLayer.changed();
      this.routeLayer.changed();
    }
  }

  public selectFeature(featureId: string | number | undefined): void {
    if (!this.map || featureId === undefined) {
      this.unselect();
      return;
    }

    this.setSelectedIds(new Set([featureId]));

    const feature =
      this.lineSource.getFeatureById(featureId) ||
      this.pointSource.getFeatureById(featureId);

    if (!feature) {
      this.unselect();
      return;
    }

    const view = this.map.getView();
    const geometry = feature.getGeometry();
    if (geometry) {
      if (geometry instanceof Point) {
        view.animate({
          center: geometry.getCoordinates(),
          duration: 500,
          zoom: Math.max(view.getZoom() || 14, 16),
        });
      } else {
        view.fit(geometry.getExtent(), {
          duration: 500,
          padding: [50, 50, 50, 50],
          maxZoom: 16,
        });
      }
    }
  }

  public deleteFeature(
    featureId: string | number | undefined,
    recourse: string,
  ): void {
    if (featureId === undefined) return;

    const numericId = parseInt(String(featureId).split("-")[1], 10);
    if (!recourse || isNaN(numericId)) return;

    mapStore
      .deleteFeature(recourse, numericId)
      .then(() => {
        if (recourse === "route") {
          const lineFeature = this.lineSource.getFeatureById(featureId);
          if (lineFeature)
            this.lineSource.removeFeature(lineFeature as Feature<LineString>);

          const pointFeature = this.pointSource.getFeatureById(featureId);
          if (pointFeature)
            this.pointSource.removeFeature(pointFeature as Feature<Point>);
        } else {
          const feature = this.pointSource.getFeatureById(featureId);
          if (feature)
            this.pointSource.removeFeature(feature as Feature<Point>);
        }

        this.unselect();
      })
      .catch((err) => {
        console.error("Delete failed:", err);
      });
  }

  public deleteMultipleFeatures(featureIds: (string | number)[]): void {
    if (!featureIds || featureIds.length === 0) return;

    const deletePromises = Array.from(featureIds).map((id) => {
      const recourse = String(id).split("-")[0];
      const numericId = parseInt(String(id).split("-")[1], 10);
      if (recourse && !isNaN(numericId)) {
        return mapStore.deleteFeature(recourse, numericId).then(() => id);
      }
      return Promise.resolve(null);
    });

    Promise.all(deletePromises)
      .then((deletedIds) => {
        const successfulDeletes = deletedIds.filter((id) => id) as (
          | string
          | number
        )[];
        if (successfulDeletes.length > 0) {
          successfulDeletes.forEach((id) => {
            const recourse = String(id).split("-")[0];
            if (recourse === "route") {
              const lineFeature = this.lineSource.getFeatureById(id);
              if (lineFeature)
                this.lineSource.removeFeature(
                  lineFeature as Feature<LineString>,
                );
              const pointFeature = this.pointSource.getFeatureById(id);
              if (pointFeature)
                this.pointSource.removeFeature(pointFeature as Feature<Point>);
            } else {
              const feature = this.pointSource.getFeatureById(id);
              if (feature)
                this.pointSource.removeFeature(feature as Feature<Point>);
            }
          });
          toast.success(`Удалено ${successfulDeletes.length} объект(ов).`);
          this.unselect();
        }
      })
      .catch((err) => {
        console.error("Bulk delete failed:", err);
      });
  }

  public destroy(): void {
    if (this.map) {
      document.removeEventListener("keydown", this.boundHandleKeyDown);
      const targetEl = this.map.getTargetElement();
      if (targetEl instanceof HTMLElement) {
        targetEl.removeEventListener(
          "contextmenu",
          this.boundHandleContextMenu,
        );
        targetEl.removeEventListener(
          "pointerleave",
          this.boundHandlePointerLeave,
        );
      }
      this.map.un("pointermove", this.boundHandlePointerMove as any);
      if (this.tooltipOverlay) this.map.removeOverlay(this.tooltipOverlay);
      this.pointSource.clear();
      this.lineSource.clear();
      this.map.setTarget(undefined);
      this.map = null;
    }
  }

  private handleFeatureEvent(
    event: VectorSourceEvent<Feature<Geometry>>,
  ): void {
    if (!event.feature) return;
    const feature = event.feature;
    if (!feature.getId()) {
      feature.setId(Date.now() + Math.random().toString(36).substr(2, 9));
    }
    this.updateFeaturesInReact();
  }

  private handleFeatureChange(
    event: VectorSourceEvent<Feature<Geometry>>,
  ): void {
    if (!event.feature) return;
    this.updateFeaturesInReact();
  }

  public activateLasso() {
    if (this.lassoInteraction && this.map) {
      this.lassoInteraction.setActive(true);
      this.setMode("lasso");
    }
  }

  public deactivateLasso() {
    if (this.lassoInteraction && this.map) {
      this.lassoInteraction.setActive(false);
      this.setMode("edit");
    }
  }

  public setSelectedIds(ids: Set<string | number>) {
    this.selectedIds = new Set(ids);
    if (this.onSelectionChange) this.onSelectionChange(this.selectedIds);

    this.selectInteraction.getFeatures().clear();
    ids.forEach((id) => {
      const lineFeature = this.lineSource.getFeatureById(id);
      if (lineFeature) {
        this.selectInteraction.getFeatures().push(lineFeature);
      }

      const pointFeature = this.pointSource.getFeatureById(id);
      if (pointFeature) {
        this.selectInteraction.getFeatures().push(pointFeature);
      }
    });

    this.modifyInteraction.setActive(
      this.selectInteraction.getFeatures().getLength() > 0,
    );
    this.clusterLayer.changed();
    this.routeLayer.changed();

    if (ids.size === 1) {
      const featureId = Array.from(ids)[0];
      const feature =
        this.lineSource.getFeatureById(featureId) ||
        this.pointSource.getFeatureById(featureId);
      if (feature) {
        this.onFeatureSelect(feature);
      }
    } else {
      this.onFeatureSelect(null);
    }
  }

  public getSelectedIds() {
    return new Set(this.selectedIds);
  }
  public setOnSelectionChange(cb: (ids: Set<string | number>) => void) {
    this.onSelectionChange = cb;
  }
  public toggleLasso() {
    if (this.mode === "lasso") this.deactivateLasso();
    else this.activateLasso();
  }
  public getMap(): OLMap | null {
    return this.map;
  }

  public clearCaches() {
    this.clusterStyleCache = {};
    this.hoveredFeatureId = null;
    this.selectedIds.clear();

    if (this.pointSource) {
      this.pointSource.clear();
    }
    if (this.lineSource) {
      this.lineSource.clear();
    }

    if (this.clusterLayer) {
      this.clusterLayer.changed();
    }
    if (this.routeLayer) {
      this.routeLayer.changed();
    }
  }

  public saveCurrentPosition(): void {
    if (!this.map) return;
    const center = this.map.getView().getCenter();
    const zoom = this.map.getView().getZoom();
    if (center && zoom !== undefined) {
      const [lon, lat] = toLonLat(center, this.map.getView().getProjection());
      saveMapPosition({ center: [lon, lat], zoom });
    }
  }

  private async saveModifiedFeature(feature: Feature<Geometry>) {
    const featureType = feature.get("featureType") as FeatureType;
    const featureId = feature.getId();
    if (!featureType || featureId === undefined || !this.map) return;

    if (typeof featureId === "number" || !String(featureId).includes("-")) {
      console.warn(
        "Skipping save for feature with non-standard ID:",
        featureId,
      );
      return;
    }

    const geoJSONFormat = new GeoJSON({
      dataProjection: "EPSG:4326",
      featureProjection: this.map.getView().getProjection().getCode(),
    });
    const featureGeoJSON = geoJSONFormat.writeFeatureObject(feature);

    try {
      await mapStore.updateFeature(featureType, featureGeoJSON);
    } catch (error) {
      console.error("Failed to update feature:", error);
      toast.error(`Не удалось обновить: ${error}`);
    }
  }

  private async saveNewFeature(feature: Feature<Geometry>) {
    const featureType = feature.get("featureType") as FeatureType;
    if (!featureType || !this.map) return;

    if (this.isCreating) {
      toast.warning("Дождитесь завершения создания предыдущего объекта.");

      if (feature.getGeometry()?.getType() === "LineString") {
        if (this.lineSource.hasFeature(feature as Feature<LineString>))
          this.lineSource.removeFeature(feature as Feature<LineString>);
      } else {
        if (this.pointSource.hasFeature(feature as Feature<Point>))
          this.pointSource.removeFeature(feature as Feature<Point>);
      }
      return;
    }

    this.isCreating = true;

    const geoJSONFormat = new GeoJSON({
      dataProjection: "EPSG:4326",
      featureProjection: this.map.getView().getProjection().getCode(),
    });

    const featureGeoJSON = geoJSONFormat.writeFeatureObject(feature);

    try {
      const createdFeatureData = await mapStore.createFeature(
        featureType,
        featureGeoJSON,
      );

      const newFeatureId = `${featureType}-${createdFeatureData.id}`;

      const displayName =
        featureType === "route"
          ? createdFeatureData.route_number
          : createdFeatureData.name;

      if (featureType === "route") {
        const routeData = createdFeatureData as ApiRoute;
        const projection = this.map.getView().getProjection();

        feature.setId(newFeatureId);
        feature.set("name", displayName);

        const lineGeom = new LineString(
          routeData.path.map((c) =>
            transform([c[1], c[0]], "EPSG:4326", projection),
          ),
        );
        feature.setGeometry(lineGeom);
      } else {
        feature.setId(newFeatureId);
        feature.set("name", displayName);
      }

      this.updateFeaturesInReact();
      this.routeLayer.changed();
      this.clusterLayer.changed();
    } catch (error) {
      console.error("Failed to save new feature:", error);
      toast.error("Не удалось сохранить объект.");
      if (feature.getGeometry()?.getType() === "LineString") {
        if (this.lineSource.hasFeature(feature as Feature<LineString>))
          this.lineSource.removeFeature(feature as Feature<LineString>);
      } else {
        if (this.pointSource.hasFeature(feature as Feature<Point>))
          this.pointSource.removeFeature(feature as Feature<Point>);
      }
    } finally {
      this.isCreating = false;
    }
  }
}

interface MapControlsProps {
  mapService: MapService | null;
  activeMode: string;
  isLassoActive: boolean;
  isUnselectDisabled: boolean;
}

interface ControlItem {
  mode: string;
  title: string;
  longTitle: string;
  icon: React.ReactNode;
  action: () => void;
  isActive?: boolean;
  disabled?: boolean;
}

const MapControls: React.FC<MapControlsProps> = ({
  mapService,
  activeMode,
  // @ts-ignore
  isLassoActive,
  isUnselectDisabled,
}) => {
  if (!mapService) return null;

  const controls: ControlItem[] = [
    {
      mode: "edit",
      title: "Редактировать",
      longTitle: "Редактирование",
      icon: <Pencil size={16} className="mr-1 sm:mr-2" />,
      action: () => mapService.activateEditMode(),
    },
    {
      mode: "drawing-station",
      title: "Остановка",
      longTitle: "Добавить остановку",
      icon: <Bus size={16} className="mr-1 sm:mr-2" />,
      action: () => mapService.startDrawing("Point", "station"),
    },
    {
      mode: "drawing-sight",
      title: "Достопримечательность",
      longTitle: "Добавить достопримечательность",
      icon: <Landmark size={16} className="mr-1 sm:mr-2" />,
      action: () => mapService.startDrawing("Point", "sight"),
    },
    {
      mode: "drawing-route",
      title: "Маршрут",
      longTitle: "Добавить маршрут (Правый клик для завершения)",
      icon: <RouteIcon size={16} className="mr-1 sm:mr-2" />,
      action: () => mapService.startDrawing("LineString", "route"),
    },

    {
      mode: "unselect",
      title: "Сбросить",
      longTitle: "Сбросить выделение (Esc)",
      icon: <X size={16} className="mr-1 sm:mr-2" />,
      action: () => mapService.unselect(),
      disabled: isUnselectDisabled,
    },
  ];
  return (
    <div className="absolute top-4 left-1/2 -translate-x-1/2 z-20 flex flex-nowrap justify-center p-2 bg-white/90 backdrop-blur-sm rounded-lg shadow-xl space-x-1 sm:space-x-2">
      {controls.map((c) => {
        const isActive =
          c.isActive !== undefined ? c.isActive : activeMode === c.mode;
        const isDisabled = c.disabled;
        const buttonClasses = `flex items-center px-3 py-2 rounded-md transition-all duration-200 text-xs sm:text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-blue-500 focus:ring-opacity-75 ${
          isDisabled
            ? "bg-gray-200 text-gray-400 cursor-not-allowed"
            : isActive
              ? "bg-blue-600 text-white shadow-md hover:bg-blue-700"
              : "bg-gray-100 text-gray-700 hover:bg-blue-100 hover:text-blue-700"
        }`;
        return (
          <button
            key={c.mode}
            className={buttonClasses}
            onClick={c.action}
            title={c.longTitle}
            disabled={isDisabled}
          >
            {c.icon}
            <span className="hidden sm:inline ml-0 sm:ml-1">{c.title}</span>
          </button>
        );
      })}
    </div>
  );
};

import { observer } from "mobx-react-lite";

interface MapSightbarProps {
  mapService: MapService | null;
  mapFeatures: Feature<Geometry>[];
  selectedFeature: Feature<Geometry> | null;
  selectedIds: Set<string | number>;
  setSelectedIds: (ids: Set<string | number>) => void;
  activeSection: string | null;
  setActiveSection: (section: string | null) => void;
}
const MapSightbar: React.FC<MapSightbarProps> = observer(
  ({
    mapService,
    mapFeatures,
    selectedFeature,
    selectedIds,
    setSelectedIds,
    activeSection,
    setActiveSection,
  }) => {
    const navigate = useNavigate();
    const [searchQuery, setSearchQuery] = useState("");
    const [stationSort, setStationSort] = useState<SortType>("name_asc");
    const [sightSort, setSightSort] = useState<SortType>("name_asc");

    const { isOpen } = menuStore;
    const { selectedCityId } = selectedCityStore;

    const actualFeatures = useMemo(
      () => mapFeatures.filter((f) => !f.get("isProxy")),
      [mapFeatures],
    );

    const allFeatures = useMemo(() => {
      const stations = mapStore.filteredStations.map((station) => {
        const feature = new Feature({
          geometry: new Point(
            transform(
              [station.longitude, station.latitude],
              "EPSG:4326",
              "EPSG:3857",
            ),
          ),
          name: station.name,
          description: station.description || "",
        });
        feature.setId(`station-${station.id}`);
        feature.set("featureType", "station");
        feature.set("created_at", station.created_at);
        return feature;
      });

      const sights = mapStore.filteredSights.map((sight) => {
        const feature = new Feature({
          geometry: new Point(
            transform(
              [sight.longitude, sight.latitude],
              "EPSG:4326",
              "EPSG:3857",
            ),
          ),
          name: sight.name,
          description: sight.description,
        });
        feature.setId(`sight-${sight.id}`);
        feature.set("featureType", "sight");
        feature.set("created_at", sight.created_at);
        return feature;
      });

      const lines = mapStore.filteredRoutes.map((route) => {
        const feature = new Feature({
          geometry: new LineString(route.path),
          name: route.route_number,
        });
        feature.setId(`route-${route.id}`);
        feature.set("featureType", "route");
        feature.set("routeName", route.route_name);
        feature.set("routeNumber", route.route_number);
        return feature;
      });

      return [...stations, ...sights, ...lines];
    }, [
      mapStore.filteredStations,
      mapStore.filteredSights,
      mapStore.filteredRoutes,
      actualFeatures,
      selectedCityId,
      mapStore,
    ]);

    const filteredFeatures = useMemo(() => {
      if (!searchQuery.trim()) return allFeatures;
      const normalizedQuery = searchQuery.toLowerCase();
      return allFeatures.filter((f) => {
        const candidates = [
          (f.get("name") as string) || "",
          (f.get("description") as string) || "",
          (f.get("routeName") as string) || "",
          (f.get("routeNumber") as string) || "",
        ];
        return candidates.some((value) =>
          value.toLowerCase().includes(normalizedQuery),
        );
      });
    }, [allFeatures, searchQuery]);

    const handleFeatureClick = useCallback(
      (id: string | number, event?: React.MouseEvent) => {
        if (!mapService) return;
        const ctrlKey = event?.ctrlKey || event?.metaKey;

        if (ctrlKey) {
          const newSet = new Set(selectedIds);
          if (newSet.has(id)) {
            newSet.delete(id);
          } else {
            newSet.add(id);
          }
          setSelectedIds(newSet);
          mapService.setSelectedIds(newSet);
        } else {
          mapService.selectFeature(id);
        }
      },
      [mapService, selectedIds, setSelectedIds],
    );

    const handleDeleteFeature = useCallback(
      (id: string | number, resource: string) => {
        if (!mapService) return;
        if (window.confirm("Вы действительно хотите удалить этот объект?")) {
          mapService.deleteFeature(id, resource);
        }
      },
      [mapService],
    );

    const handleCheckboxChange = useCallback(
      (id: string | number) => {
        if (!mapService) return;
        const newSet = new Set(selectedIds);
        if (newSet.has(id)) newSet.delete(id);
        else newSet.add(id);
        setSelectedIds(newSet);
        mapService.setSelectedIds(newSet);
      },
      [mapService, selectedIds, setSelectedIds],
    );

    const handleBulkDelete = useCallback(() => {
      if (!mapService || selectedIds.size === 0) return;
      if (
        window.confirm(
          `Вы уверены, что хотите удалить ${selectedIds.size} объект(ов)?`,
        )
      ) {
        mapService.deleteMultipleFeatures(Array.from(selectedIds));
        setSelectedIds(new Set());
      }
    }, [mapService, selectedIds, setSelectedIds]);

    const handleEditFeature = useCallback(
      (featureType: string, fullId: string | number) => {
        const numericId = String(fullId).split("-")[1];
        if (!featureType || !numericId) return;
        navigate(`/${featureType}/${numericId}/edit`);
      },
      [navigate],
    );

    const handleHideRoute = useCallback(
      async (routeId: string | number) => {
        if (!mapService) return;

        const numericRouteId = parseInt(String(routeId).split("-")[1], 10);
        if (isNaN(numericRouteId)) return;

        const isHidden = mapStore.hiddenRoutes.has(numericRouteId);

        try {
          if (isHidden) {
            const route = mapStore.routes.find((r) => r.id === numericRouteId);
            if (!route) {
              return;
            }

            const projection = mapService.getMap()?.getView().getProjection();
            if (!projection) {
              return;
            }

            const coordinates = route.path
              .filter((c) => c && c[0] != null && c[1] != null)
              .map((c: [number, number]) =>
                transform([c[1], c[0]], "EPSG:4326", projection),
              );

            if (coordinates.length > 0) {
              const line = new LineString(coordinates);
              const lineFeature = new Feature({
                geometry: line,
                name: route.route_number,
              });
              lineFeature.setId(routeId);
              lineFeature.set("featureType", "route");
              mapService.lineSource.addFeature(lineFeature);
            } else {
            }

            const routeStationIds =
              mapStore.routeStationsCache.get(numericRouteId) || [];

            const allRouteIds = mapStore.routes.map((r) => r.id);

            const visibleRouteIds = allRouteIds.filter(
              (id: number) =>
                id !== numericRouteId && !mapStore.hiddenRoutes.has(id),
            );

            const stationsInVisibleRoutes = new Set<number>();
            visibleRouteIds.forEach((otherRouteId) => {
              const stationIds =
                mapStore.routeStationsCache.get(otherRouteId) || [];
              stationIds.forEach((id: number) =>
                stationsInVisibleRoutes.add(id),
              );
            });

            const stationsToShow = routeStationIds.filter(
              (id: number) => !stationsInVisibleRoutes.has(id),
            );

            for (const stationId of stationsToShow) {
              const station = mapStore.stations.find((s) => s.id === stationId);
              if (!station) continue;

              const point = new Point(
                transform(
                  [station.longitude, station.latitude],
                  "EPSG:4326",
                  projection,
                ),
              );
              const feature = new Feature({
                geometry: point,
                name: station.name,
              });
              feature.setId(`station-${station.id}`);
              feature.set("featureType", "station");

              const existingFeature = mapService.pointSource.getFeatureById(
                `station-${station.id}`,
              );
              if (!existingFeature) {
                mapService.pointSource.addFeature(feature);
              }
            }

            mapStore.hiddenRoutes.delete(numericRouteId);
            saveHiddenRoutes(mapStore.hiddenRoutes);
          } else {
            const routeStationIds =
              mapStore.routeStationsCache.get(numericRouteId) || [];

            const allRouteIds = mapStore.routes.map((r) => r.id);

            const visibleRouteIds = allRouteIds.filter(
              (id: number) =>
                id !== numericRouteId && !mapStore.hiddenRoutes.has(id),
            );

            const stationsInVisibleRoutes = new Set<number>();
            visibleRouteIds.forEach((otherRouteId) => {
              const stationIds =
                mapStore.routeStationsCache.get(otherRouteId) || [];
              stationIds.forEach((id: number) =>
                stationsInVisibleRoutes.add(id),
              );
            });

            const stationsToHide = routeStationIds.filter(
              (id: number) => !stationsInVisibleRoutes.has(id),
            );

            stationsToHide.forEach((stationId: number) => {
              const pointFeature = mapService.pointSource.getFeatureById(
                `station-${stationId}`,
              );
              if (pointFeature) {
                mapService.pointSource.removeFeature(
                  pointFeature as Feature<Point>,
                );
              }
            });

            const lineFeature = mapService.lineSource.getFeatureById(routeId);
            if (lineFeature) {
              mapService.lineSource.removeFeature(
                lineFeature as Feature<LineString>,
              );
            }

            mapStore.hiddenRoutes.add(numericRouteId);
            saveHiddenRoutes(mapStore.hiddenRoutes);
          }

          mapService.unselect();
        } catch (error) {
          console.error(
            "[handleHideRoute] Error toggling route visibility:",
            error,
          );
          toast.error("Ошибка при изменении видимости маршрута");
        }
      },
      [mapService],
    );

    const sortFeaturesByType = <T extends Feature<Geometry>>(
      features: T[],
      sortType: SortType,
    ): T[] => {
      const sorted = [...features];
      switch (sortType) {
        case "name_asc":
          return sorted.sort((a, b) =>
            ((a.get("name") as string) || "").trim().localeCompare(
              ((b.get("name") as string) || "").trim(),
            ),
          );
        case "name_desc":
          return sorted.sort((a, b) =>
            ((b.get("name") as string) || "").trim().localeCompare(
              ((a.get("name") as string) || "").trim(),
            ),
          );
        case "created_asc":
          return sorted.sort((a, b) => {
            const aDate = a.get("created_at")
              ? new Date(a.get("created_at"))
              : new Date(0);
            const bDate = b.get("created_at")
              ? new Date(b.get("created_at"))
              : new Date(0);
            return aDate.getTime() - bDate.getTime();
          });
        case "created_desc":
          return sorted.sort((a, b) => {
            const aDate = a.get("created_at")
              ? new Date(a.get("created_at"))
              : new Date(0);
            const bDate = b.get("created_at")
              ? new Date(b.get("created_at"))
              : new Date(0);
            return bDate.getTime() - aDate.getTime();
          });
        case "updated_asc":
          return sorted.sort((a, b) => {
            const aDate = a.get("updated_at")
              ? new Date(a.get("updated_at"))
              : a.get("created_at")
                ? new Date(a.get("created_at"))
                : new Date(0);
            const bDate = b.get("updated_at")
              ? new Date(b.get("updated_at"))
              : b.get("created_at")
                ? new Date(b.get("created_at"))
                : new Date(0);
            return aDate.getTime() - bDate.getTime();
          });
        case "updated_desc":
          return sorted.sort((a, b) => {
            const aDate = a.get("updated_at")
              ? new Date(a.get("updated_at"))
              : a.get("created_at")
                ? new Date(a.get("created_at"))
                : new Date(0);
            const bDate = b.get("updated_at")
              ? new Date(b.get("updated_at"))
              : b.get("created_at")
                ? new Date(b.get("created_at"))
                : new Date(0);
            return bDate.getTime() - aDate.getTime();
          });
        default:
          return sorted;
      }
    };

    const stations = filteredFeatures.filter(
      (f) => f.get("featureType") === "station",
    );
    const lines = filteredFeatures.filter(
      (f) => f.get("featureType") === "route",
    );
    const sights = filteredFeatures.filter(
      (f) => f.get("featureType") === "sight",
    );

    const sortedStations = sortFeaturesByType(stations, stationSort);
    const sortedSights = sortFeaturesByType(sights, sightSort);

    const renderFeatureList = (
      features: Feature<Geometry>[],
      featureType: "station" | "route" | "sight",
      IconComponent: React.ElementType,
    ) => (
      <div className="space-y-1 pr-1">
        {features.length > 0 ? (
          features.map((feature) => {
            const fId = feature.getId();
            if (fId === undefined) return null;
            const fName = (feature.get("name") as string) || "Без названия";
            const isSelected = selectedFeature?.getId() === fId;
            const isChecked = selectedIds.has(fId);

            const numericRouteId =
              featureType === "route"
                ? parseInt(String(fId).split("-")[1], 10)
                : null;
            const isRouteHidden =
              numericRouteId !== null &&
              mapStore.hiddenRoutes.has(numericRouteId);

            const description = feature.get("description") as
              | string
              | undefined;
            const showDescription =
              featureType === "station" &&
              description &&
              description.trim() !== "";
            const routeName =
              featureType === "route"
                ? (feature.get("routeName") as string) || ""
                : "";
            const routeNumber =
              featureType === "route"
                ? (feature.get("routeNumber") as string) || fName
                : "";
            const routeNumberTrimmed = routeNumber.trim();
            const routeNameTrimmed = routeName.trim();
            const displayName =
              featureType === "route" ? routeNumberTrimmed || fName : fName;
            const showRouteName =
              featureType === "route" &&
              routeNameTrimmed !== "" &&
              routeNameTrimmed !== displayName;
            const titleParts: string[] = [];
            if (featureType === "route") {
              if (routeNumberTrimmed) {
                titleParts.push(routeNumberTrimmed);
              }
              if (routeNameTrimmed) {
                titleParts.push(routeNameTrimmed);
              }
            }
            const titleText =
              featureType === "route"
                ? titleParts.join(" • ") ||
                  routeNumberTrimmed ||
                  routeNameTrimmed ||
                  fName
                : fName;

            return (
              <div
                key={String(fId)}
                data-feature-id={fId}
                className={`flex items-start p-2 rounded-md group transition-colors duration-150 relative ${
                  isSelected
                    ? "bg-orange-100 border border-orange-300"
                    : "hover:bg-blue-50"
                }`}
              >
                <div className="flex-shrink-0 pr-2 pt-1">
                  <input
                    type="checkbox"
                    className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 cursor-pointer"
                    checked={isChecked}
                    onChange={() => handleCheckboxChange(fId)}
                    onClick={(e) => e.stopPropagation()}
                    aria-label={`Выбрать ${titleText}`}
                  />
                </div>
                <div
                  className="flex flex-col text-gray-800 text-sm flex-grow mr-2 min-w-0 cursor-pointer"
                  onClick={(e) => handleFeatureClick(fId, e)}
                >
                  <div className="flex items-center">
                    <IconComponent
                      {...({
                        className: [
                          "mr-1.5",
                          "flex-shrink-0",
                          isSelected ? "text-orange-500" : "text-blue-500",
                          !isSelected && "group-hover:text-blue-600",
                        ]
                          .filter(Boolean)
                          .join(" "),
                      } as React.HTMLAttributes<HTMLElement>)}
                      // @ts-ignore
                      size={16}
                    />
                    <span
                      className={`font-medium whitespace-nowrap overflow-x-auto block
                      scrollbar-visible`}
                      title={titleText}
                    >
                      {displayName}
                    </span>
                  </div>
                  {showDescription && (
                    <div className="mt-1 text-xs text-gray-600 line-clamp-2">
                      {description}
                    </div>
                  )}
                  {showRouteName && (
                    <div className="mt-1 text-xs text-gray-600">
                      {routeNameTrimmed}
                    </div>
                  )}
                </div>
                <div className="flex-shrink-0 flex items-center space-x-1 opacity-60 group-hover:opacity-100">
                  <button
                    onClick={(e) => {
                      e.stopPropagation();
                      const featureTypeVal = feature.get("featureType");
                      if (featureTypeVal)
                        handleEditFeature(featureTypeVal, fId);
                    }}
                    className="p-1 rounded-full text-gray-400 hover:text-blue-600 hover:bg-blue-100 transition-colors"
                    title="Редактировать детали"
                  >
                    <Pencil size={16} />
                  </button>
                  {featureType === "route" && (
                    <button
                      onClick={(e) => {
                        e.stopPropagation();
                        const routeId = parseInt(String(fId).split("-")[1], 10);
                        navigate(`/route-preview/${routeId}`);
                      }}
                      className="p-1 rounded-full text-gray-400 hover:text-green-600 hover:bg-green-100 transition-colors"
                      title="Предпросмотр маршрута"
                    >
                      <MapIcon size={16} />
                    </button>
                  )}
                  {featureType === "route" && (
                    <button
                      onClick={(e) => {
                        e.stopPropagation();
                        handleHideRoute(fId);
                      }}
                      className={`p-1 rounded-full transition-colors ${
                        isRouteHidden
                          ? "text-yellow-600 hover:text-yellow-700 hover:bg-yellow-100"
                          : "text-gray-400 hover:text-yellow-600 hover:bg-yellow-100"
                      }`}
                      title={
                        isRouteHidden ? "Показать на карте" : "Скрыть с карты"
                      }
                    >
                      {isRouteHidden ? <Eye size={16} /> : <EyeOff size={16} />}
                    </button>
                  )}
                  <button
                    onClick={(e) => {
                      e.stopPropagation();
                      handleDeleteFeature(fId, featureType);
                    }}
                    className="p-1 rounded-full text-gray-400 hover:text-red-600 hover:bg-red-100 transition-colors"
                    title="Удалить"
                  >
                    <Trash2 size={16} />
                  </button>
                </div>
              </div>
            );
          })
        ) : (
          <p className="text-gray-500 text-sm p-2">Нет объектов этого типа.</p>
        )}
      </div>
    );

    const toggleSection = (id: string) =>
      setActiveSection(activeSection === id ? null : id);

    const [showSightsOptions, setShowSightsOptions] = useState(false);
    const sightsOptionsTimeoutRef = useRef<NodeJS.Timeout | null>(null);

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

    const sections = [
      {
        id: "layers",
        title: `Остановки (${sortedStations.length})`,
        icon: <Bus size={20} />,
        count: sortedStations.length,
        sortControl: (
          <div className="flex items-center space-x-2 p-3 bg-white border-b border-gray-200">
            <label className="text-sm text-gray-700">Сортировка:</label>
            <select
              value={stationSort}
              onChange={(e) => setStationSort(e.target.value as SortType)}
              className="border rounded px-2 py-1 text-sm"
            >
              <option value="name_asc">Имя ↑</option>
              <option value="name_desc">Имя ↓</option>
              <option value="created_asc">Дата создания ↑</option>
              <option value="created_desc">Дата создания ↓</option>
              <option value="updated_asc">Дата обновления ↑</option>
              <option value="updated_desc">Дата обновления ↓</option>
            </select>
          </div>
        ),
        content: renderFeatureList(sortedStations, "station", MapPin),
      },
      {
        id: "lines",
        title: `Маршруты (${lines.length})`,
        icon: <RouteIcon size={20} />,
        count: lines.length,
        sortControl: null,
        content: renderFeatureList(lines, "route", ArrowRightLeft),
      },
      {
        id: "sights",
        title: `Достопримечательности (${sortedSights.length})`,
        icon: <Landmark size={20} />,
        count: sortedSights.length,
        sortControl: (
          <div className="flex items-center justify-between gap-4 p-3 bg-white border-b border-gray-200">
            <div className="flex items-center space-x-2">
              <label className="text-sm text-gray-700">Сортировка:</label>
              <select
                value={sightSort}
                onChange={(e) => setSightSort(e.target.value as SortType)}
                className="border rounded px-2 py-1 text-sm"
              >
                <option value="name_asc">Имя ↑</option>
                <option value="name_desc">Имя ↓</option>
                <option value="created_asc">Дата создания ↑</option>
                <option value="created_desc">Дата создания ↓</option>
                <option value="updated_asc">Дата обновления ↑</option>
                <option value="updated_desc">Дата обновления ↓</option>
              </select>
            </div>
            <div
              className="relative"
              onMouseEnter={() => {
                sightsOptionsTimeoutRef.current = setTimeout(() => {
                  setShowSightsOptions(true);
                }, 1000);
              }}
              onMouseLeave={() => {
                if (sightsOptionsTimeoutRef.current) {
                  clearTimeout(sightsOptionsTimeoutRef.current);
                  sightsOptionsTimeoutRef.current = null;
                }
                setShowSightsOptions(false);
              }}
            >
              <button
                className={`px-2 py-1 rounded text-sm transition-colors ${
                  mapStore.hideSightsByHiddenRoutes
                    ? "bg-blue-100 text-blue-700 hover:bg-blue-200"
                    : "text-gray-600 hover:text-gray-800 hover:bg-gray-100"
                }`}
                onClick={() =>
                  mapStore.setHideSightsByHiddenRoutes(
                    !mapStore.hideSightsByHiddenRoutes,
                  )
                }
              >
                Скрыть
              </button>
              {showSightsOptions && (
                <div className="absolute right-0 mt-2 w-50 bg-white border border-gray-200 rounded-md shadow-md p-3 z-5000">
                  <div className="text-xs text-gray-600">
                    Будут скрыты все достопримечательности, привязанные к
                    скрытым маршрутам.
                  </div>
                </div>
              )}
            </div>
          </div>
        ),
        content: renderFeatureList(sortedSights, "sight", Landmark),
      },
    ];

    return (
      <div
        className={`${
          isOpen ? "w-[360px]" : "w-[590px]"
        } transition-all duration-300 ease-in-out relative bg-gray-50 shadow-lg flex flex-col border-l border-gray-200 h-[90vh]`}
      >
        <div className="p-4 bg-gray-700 text-white">
          <h2 className="text-lg font-semibold">Панель управления</h2>
        </div>
        <div className="p-3 border-b border-gray-200 bg-white">
          <input
            type="text"
            placeholder="Поиск по названию..."
            value={searchQuery}
            onChange={(e) => setSearchQuery(e.target.value)}
            className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
          />
        </div>
        <div className="flex-1 flex flex-col min-h-0 overflow-y-auto scrollbar-visible">
          {filteredFeatures.length === 0 && searchQuery ? (
            <div className="p-4 text-center text-gray-500">
              Ничего не найдено.
            </div>
          ) : (
            sections.map(
              (s) =>
                (s.count > 0 || !searchQuery) && (
                  <div
                    key={s.id}
                    className={`border-b border-gray-200 last:border-b-0 flex flex-col transition-all duration-300 ease-in-out ${
                      activeSection === s.id ? "flex-1 min-h-0" : "flex-none"
                    }`}
                  >
                    <button
                      onClick={() => toggleSection(s.id)}
                      className={`w-full p-3 flex items-center justify-between text-left hover:bg-gray-100 transition-colors duration-150 focus:outline-none focus:bg-gray-100 flex-shrink-0 ${
                        activeSection === s.id
                          ? "bg-gray-100 text-blue-600"
                          : "text-gray-700"
                      }`}
                    >
                      <div className="flex items-center space-x-3">
                        <span
                          className={
                            activeSection === s.id
                              ? "text-blue-600"
                              : "text-gray-600"
                          }
                        >
                          {s.icon}
                        </span>
                        <span className="font-medium text-sm">{s.title}</span>
                      </div>
                      <span
                        className={`transform transition-transform duration-200 text-gray-500 ${
                          activeSection === s.id ? "rotate-180" : ""
                        }`}
                      >
                        ▼
                      </span>
                    </button>
                    {activeSection === s.id && (
                      <>
                        {s.sortControl && (
                          <div className="flex-shrink-0">{s.sortControl}</div>
                        )}
                        <div className="overflow-y-auto scrollbar-visible bg-white flex-1 min-h-0">
                          <div className="p-3 text-sm text-gray-600">
                            {s.content}
                          </div>
                        </div>
                      </>
                    )}
                  </div>
                ),
            )
          )}
        </div>
        {selectedIds.size > 0 && (
          <div className="p-3 border-t border-gray-200 bg-white flex-shrink-0">
            <button
              onClick={handleBulkDelete}
              className="w-full flex items-center justify-center px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors"
            >
              <Trash2 size={16} className="mr-2" />
              Удалить выбранное ({selectedIds.size})
            </button>
          </div>
        )}
      </div>
    );
  },
);

export const MapPage: React.FC = observer(() => {
  const mapRef = useRef<HTMLDivElement | null>(null);
  const tooltipRef = useRef<HTMLDivElement | null>(null);
  const [mapServiceInstance, setMapServiceInstance] =
    useState<MapService | null>(null);
  const [isMapLoading, setIsMapLoading] = useState<boolean>(true);
  const [isDataLoading, setIsDataLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);
  const [currentMapMode, setCurrentMapMode] = useState<string>("edit");
  const [mapFeatures, setMapFeatures] = useState<Feature<Geometry>[]>([]);
  const [selectedFeatureForSidebar, setSelectedFeatureForSidebar] =
    useState<Feature<Geometry> | null>(null);
  const [selectedIds, setSelectedIds] = useState<Set<string | number>>(
    new Set(),
  );
  const [isLassoActive, setIsLassoActive] = useState<boolean>(false);
  const [showHelp, setShowHelp] = useState<boolean>(false);
  const [activeSectionFromParent, setActiveSectionFromParent] = useState<
    string | null
  >(() => getStoredActiveSection() || "layers");

  const { selectedCityId } = selectedCityStore;

  const handleFeaturesChange = useCallback(
    (feats: Feature<Geometry>[]) => setMapFeatures([...feats]),
    [],
  );

  const handleFeatureSelectForSidebar = useCallback(
    (feat: Feature<Geometry> | null) => {
      setSelectedFeatureForSidebar(feat);
      if (feat) {
        const featureType = feat.get("featureType");
        const sectionId =
          featureType === "sight"
            ? "sights"
            : featureType === "route"
              ? "lines"
              : "layers";
        setActiveSectionFromParent(sectionId);
        setTimeout(() => {
          document
            .querySelector(`[data-feature-id="${feat.getId()}"]`)
            ?.scrollIntoView({ behavior: "smooth", block: "center" });
        }, 100);
      }
    },
    [],
  );

  useEffect(() => {
    let service: MapService | null = null;
    if (mapRef.current && tooltipRef.current && !mapServiceInstance) {
      setIsMapLoading(true);
      setIsDataLoading(true);
      setError(null);

      const loadInitialData = async (mapService: MapService) => {
        try {
          await Promise.all([
            mapStore.getRoutes(),
            mapStore.getStations(),
            mapStore.getSights(),
            carrierStore.getCarriers("ru"),
          ]);
          mapService.loadFeaturesFromApi(
            mapStore.stations,
            mapStore.routes,
            mapStore.sights,
          );
        } catch (e) {
          console.error("Failed to load initial map data:", e);
          setError("Не удалось загрузить данные для карты.");
        } finally {
          setIsDataLoading(false);
        }
      };

      try {
        service = new MapService(
          { ...mapConfig, target: mapRef.current },
          setIsMapLoading,
          setError,
          setCurrentMapMode,
          handleFeaturesChange,
          handleFeatureSelectForSidebar,
          tooltipRef.current,
          setSelectedIds,
        );
        setMapServiceInstance(service);

        if (typeof window !== "undefined") {
          (window as any).mapServiceInstance = service;
        }

        loadInitialData(service);
      } catch (e: any) {
        setError(
          `Ошибка инициализации карты: ${e.message || "Неизвестная ошибка"}.`,
        );
        setIsMapLoading(false);
        setIsDataLoading(false);
      }
    }
    return () => {
      service?.destroy();
      setMapServiceInstance(null);

      if (typeof window !== "undefined") {
        delete (window as any).mapServiceInstance;
      }
    };
  }, []);

  useEffect(() => {
    const olMap = mapServiceInstance?.getMap();
    if (!olMap || !mapServiceInstance) return;

    const handleMapClickForDeselect = (event: any) => {
      if (!mapServiceInstance) return;

      const hit = olMap.hasFeatureAtPixel(event.pixel, {
        layerFilter: (layer) =>
          layer === mapServiceInstance.clusterLayer ||
          layer === mapServiceInstance.routeLayer,
        hitTolerance: 5,
      });

      if (!hit) {
        mapServiceInstance.unselect();
      }
    };

    olMap.on("click", handleMapClickForDeselect);

    return () => {
      olMap.un("click", handleMapClickForDeselect);
    };
  }, [mapServiceInstance]);

  useEffect(() => {
    mapServiceInstance?.setOnSelectionChange(setSelectedIds);
  }, [mapServiceInstance]);

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === "Shift" && mapServiceInstance && !isLassoActive) {
        mapServiceInstance.activateLasso();
        setIsLassoActive(true);
      }
    };
    const handleKeyUp = (e: KeyboardEvent) => {
      if (e.key === "Shift" && mapServiceInstance) {
        mapServiceInstance.deactivateLasso();
        setIsLassoActive(false);
      }
    };
    window.addEventListener("keydown", handleKeyDown);
    window.addEventListener("keyup", handleKeyUp);
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
      window.removeEventListener("keyup", handleKeyUp);
    };
  }, [mapServiceInstance, isLassoActive]);

  useEffect(() => {
    if (mapServiceInstance) {
      mapServiceInstance.toggleLasso = function () {
        if (currentMapMode === "lasso") {
          this.deactivateLasso();
          setIsLassoActive(false);
        } else {
          this.activateLasso();
          setIsLassoActive(true);
        }
      };
    }
  }, [mapServiceInstance, currentMapMode]);

  useEffect(() => {
    saveActiveSection(activeSectionFromParent);
  }, [activeSectionFromParent]);

  useEffect(() => {
    if (mapServiceInstance && !isDataLoading) {
      mapServiceInstance.pointSource.clear();
      mapServiceInstance.lineSource.clear();

      mapServiceInstance.loadFeaturesFromApi(
        mapStore.stations,
        mapStore.routes,
        mapStore.sights,
      );
    }
  }, [selectedCityId, mapServiceInstance, isDataLoading]);

  useEffect(() => {
    if (mapServiceInstance && !isDataLoading) {
      mapServiceInstance.pointSource.clear();
      mapServiceInstance.lineSource.clear();

      mapServiceInstance.loadFeaturesFromApi(
        mapStore.stations,
        mapStore.routes,
        mapStore.sights,
      );
    }
  }, [
    mapStore.hideSightsByHiddenRoutes,
    mapStore.hiddenRoutes.size,
    mapServiceInstance,
    isDataLoading,
  ]);

  const showLoader = isMapLoading || isDataLoading;
  const showContent = mapServiceInstance && !showLoader && !error;
  const isAnythingSelected =
    selectedFeatureForSidebar !== null || selectedIds.size > 0;

  return (
    <div className="-mb-4 flex flex-col md:flex-row font-sans bg-gray-100 h-[90vh] overflow-hidden">
      <div className="relative flex-grow flex">
        <div
          ref={mapRef}
          id="map"
          className="flex-1 h-full relative bg-gray-200"
        >
          <div
            ref={tooltipRef}
            className="tooltip ol-tooltip bg-white text-black p-2 rounded shadow-lg text-xs whitespace-nowrap"
            style={{ position: "absolute", pointerEvents: "none" }}
          ></div>
          {showLoader && (
            <div className="absolute inset-0 flex flex-col items-center justify-center bg-gray-500 bg-opacity-50 z-[1001]">
              <Loader2 className="w-12 h-12 text-blue-500 animate-spin mb-3" />
              <div className="text-md font-semibold text-white drop-shadow-md">
                {isMapLoading ? "Загрузка карты..." : "Загрузка данных..."}
              </div>
            </div>
          )}
          {error && !showLoader && (
            <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-red-100 border border-red-400 text-red-700 p-6 rounded-lg shadow-lg z-[1002] text-center max-w-md">
              <h3 className="font-semibold text-lg mb-2">Произошла ошибка</h3>
              <p className="text-sm">{error}</p>
              <button
                onClick={() => window.location.reload()}
                className="mt-4 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition-colors text-sm"
              >
                Перезагрузить
              </button>
            </div>
          )}
          {isLassoActive && (
            <div className="absolute bottom-4 left-1/2 -translate-x-1/2 bg-blue-600 text-white py-2 px-4 rounded-full shadow-lg text-sm font-medium z-20">
              Режим выделения области.
            </div>
          )}
        </div>
        {showContent && (
          <MapControls
            mapService={mapServiceInstance}
            activeMode={currentMapMode}
            isLassoActive={isLassoActive}
            isUnselectDisabled={!isAnythingSelected}
          />
        )}

        {showHelp && (
          <div className="absolute bottom-16 right-4 z-20 p-4 bg-white rounded-lg shadow-xl max-w-md max-h-[30vh] overflow-y-auto scrollbar-visible">
            <h4 className="font-bold mb-2">Управление картой</h4>
            <div className="text-sm space-y-3">
              <div>
                <p className="font-semibold mb-1">Перемещение и масштаб:</p>
                <ul className="space-y-1">
                  <li>Колесо мыши — приблизить / отдалить.</li>
                  <li>
                    Средняя кнопка мыши (колесо зажато) — перетаскивание карты.
                  </li>
                </ul>
              </div>

              <div>
                <p className="font-semibold mb-1">Выделение объектов:</p>
                <ul className="space-y-1">
                  <li>Одинарный клик по объекту — выделить и центрировать.</li>
                  <li>
                    <span className="font-mono bg-gray-100 px-1 rounded">
                      Ctrl
                    </span>{" "}
                    + клик — добавить / убрать объект из выделения.
                  </li>
                  <li>
                    <span className="font-mono bg-gray-100 px-1 rounded">
                      Shift
                    </span>{" "}
                    — временно включить режим лассо (выделение области).
                  </li>
                  <li>Клик по пустому месту карты — снять выделение.</li>
                  <li>
                    <span className="font-mono bg-gray-100 px-1 rounded">
                      Esc
                    </span>{" "}
                    — снять выделение всех объектов.
                  </li>
                </ul>
              </div>

              <div>
                <p className="font-semibold mb-1">
                  Рисование и редактирование:
                </p>
                <ul className="space-y-1">
                  <li>
                    Кнопки в верхней панели — выбор режима: редактирование,
                    добавление остановки, достопримечательности или маршрута.
                  </li>
                  <li>
                    При рисовании маршрута: правый клик — завершить линию.
                  </li>
                  <li>
                    В режиме редактирования: перетаскивайте точки маршрута для
                    изменения траектории.
                  </li>
                  <li>
                    Двойной клик по внутренней точке маршрута — удалить эту
                    точку (при наличии не менее 2 точек).
                  </li>
                </ul>
              </div>

              <div>
                <p className="font-semibold mb-1">Боковая панель:</p>
                <ul className="space-y-1">
                  <li>Клик по строке в списке — перейти к объекту на карте.</li>
                  <li>
                    Иконка карандаша — открыть объект в режиме редактирования.
                  </li>
                  <li>
                    Иконка карты у маршрутов — открыть предпросмотр маршрута.
                  </li>
                  <li>
                    Иконка глаза у маршрутов — скрыть / показать маршрут и
                    связанные остановки.
                  </li>
                  <li>Иконка корзины — удалить объект.</li>
                </ul>
              </div>
            </div>
            <button
              onClick={() => setShowHelp(false)}
              className="mt-3 text-xs text-blue-600 hover:text-blue-800"
            >
              Закрыть
            </button>
          </div>
        )}

        <button
          onClick={() => setShowHelp(!showHelp)}
          className="absolute bottom-4 right-4 z-20 p-2 bg-white rounded-full shadow-md hover:bg-gray-100"
          title="Помощь по управлению картой"
        >
          <InfoIcon size={20} />
        </button>
      </div>
      {showContent && (
        <MapSightbar
          mapService={mapServiceInstance}
          mapFeatures={mapFeatures}
          selectedFeature={selectedFeatureForSidebar}
          selectedIds={selectedIds}
          setSelectedIds={setSelectedIds}
          activeSection={activeSectionFromParent}
          setActiveSection={setActiveSectionFromParent}
        />
      )}
    </div>
  );
});
