import React, {
  useEffect,
  useRef,
  useState,
  useCallback,
  useMemo,
} from "react";
import { useNavigate } from "react-router-dom";
import { Map, 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,
} 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";

// --- CUSTOM SCROLLBAR STYLES ---
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);
}

// --- MAP STORE ---
// @ts-ignore
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 = [];

  // Сброс кешей MapService если он доступен
  if (typeof window !== "undefined" && (window as any).mapServiceInstance) {
    (window as any).mapServiceInstance.clearCaches();
  }
};

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

interface ApiStation {
  id: number;
  name: 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";

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

  routes: ApiRoute[] = [];
  stations: ApiStation[] = [];
  sights: ApiSight[] = [];

  // НОВЫЕ ПОЛЯ ДЛЯ СОРТИРОВКИ
  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.localeCompare(b.name));
      case "name_desc":
        return sorted.sort((a, b) => b.name.localeCompare(a.name));
      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()
            );
          }
          // Фоллбэк: сортировка по ID, если дата недоступна
          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()
            );
          }
          // Фоллбэк: сортировка по ID, если дата недоступна
          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;
    }

    // Получаем carriers для текущего языка
    const carriers = carrierStore.carriers.ru.data;

    // Фильтруем маршруты по городу через carriers
    return this.routes.filter((route: ApiRoute) => {
      // Находим carrier для маршрута
      const carrier = carriers.find((c: any) => c.id === route.carrier_id);
      return carrier && carrier.city_id === selectedCityId;
    });
  }

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

  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,
      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.localeCompare(b.route_number)
    );
  };

  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;

    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]]);

      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"
      );

      // Автоматически назначаем перевозчика из выбранного города
      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,
        center_longitude,
        carrier,
        carrier_id,
        governor_appeal: 0,
        rotate: 0,
        route_direction: false,
        route_sys_number: route_number,
        scale_max: 0,
        scale_min: 0,
      };

      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}`);
    }

    // @ts-ignore
    if (featureType === "route") this.routes.push(createdItem);
    // @ts-ignore
    else if (featureType === "station") this.stations.push(createdItem);
    // @ts-ignore
    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();

// Делаем mapStore доступным глобально для сброса кешей
if (typeof window !== "undefined") {
  (window as any).mapStore = mapStore;
}

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

// --- MAP POSITION STORAGE ---
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);
  }
};

// --- ACTIVE SECTION STORAGE ---
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);
  }
};

// --- TYPE DEFINITIONS ---
interface MapServiceConfig {
  target: HTMLElement;
  center: [number, number];
  zoom: number;
}

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

class MapService {
  private map: Map | null;
  public pointSource: VectorSource<Feature<Point>>;
  public lineSource: VectorSource<Feature<LineString>>;
  public clusterLayer: VectorLayer<Cluster>; // Public for the deselect handler
  public routeLayer: VectorLayer<VectorSource<Feature<LineString>>>; // Public for deselect
  private clusterSource: Cluster;
  private clusterStyleCache: { [key: number]: Style };

  private tooltipElement: HTMLElement;
  private tooltipOverlay: Overlay | null;
  private mode: string | null;
  // @ts-ignore
  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;

  // Styles
  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;

  // Callbacks
  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,
      // @ts-ignore
      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", () => {
      // Поскольку маршруты больше не добавляются как точки,
      // нам не нужно отслеживать unclusteredRouteIds
      // Все маршруты всегда отображаются как линии
      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 Map({
        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(),
          // Отключаем DoubleClickZoom как было изначально
          // new DoubleClickZoom(),
          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; // 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; // Ignore proxy points
        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; // Ignore proxy in lasso
        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; // Завершаем обработку
          }
        }

        // Стандартная логика выделения для одиночных объектов (или с Ctrl)
        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) {
            newSelectedIds.add(targetId);
          }
        });

        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) {
        // Устанавливаем курсор pointer по умолчанию для всей карты
        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";
          }
        });

        // Также добавляем обработчик для mousedown/mouseup для совместимости
        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>[] = [];

    // Используем фильтрованные данные из mapStore
    const filteredStations = mapStore.filteredStations;
    const filteredSights = mapStore.filteredSights;
    const filteredRoutes = mapStore.filteredRoutes;

    filteredStations.forEach((station) => {
      if (station.longitude == null || station.latitude == null) 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;
      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":
          // Используем полный список из mapStore, а не отфильтрованный
          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":
          // Используем полный список из mapStore, а не отфильтрованный
          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":
          // Используем полный список из mapStore, а не отфильтрованный
          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 {
        // @ts-ignore
        this.currentInteraction.abortDrawing();
      } catch (e) {
        /* ignore */
      }
      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,
    });
    // Устанавливаем курсор pointer для всей карты, чтобы показать возможность перетаскивания колёсиком
    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; // Ignore hover on proxy
        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(): Map | 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}`;
      // @ts-ignore
      const displayName =
        featureType === "route"
          ? // @ts-ignore
            createdFeatureData.route_number
          : // @ts-ignore
            createdFeatureData.name;

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

        // Update existing line feature
        feature.setId(newFeatureId);
        feature.set("name", displayName);

        // Optionally update geometry if server modified it
        const lineGeom = new LineString(
          routeData.path.map((c) =>
            transform([c[1], c[0]], "EPSG:4326", projection)
          )
        );
        feature.setGeometry(lineGeom);

        // Не создаем прокси-точку для маршрута - только линия
      } else {
        // For points: update existing
        feature.setId(newFeatureId);
        feature.set("name", displayName);
        // No need to remove and re-add since it's already in the source
      }

      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;
    }
  }
}

// --- MAP CONTROLS COMPONENT ---
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";

// --- MAP SIGHTBAR COMPONENT ---
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,
        });
        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");
        return feature;
      });

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

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

    const handleFeatureClick = useCallback(
      (id: string | number) => {
        if (!mapService) return;
        mapService.selectFeature(id);
      },
      [mapService]
    );

    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 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) || "").localeCompare(
              (b.get("name") as string) || ""
            )
          );
        case "name_desc":
          return sorted.sort((a, b) =>
            ((b.get("name") as string) || "").localeCompare(
              (a.get("name") as string) || ""
            )
          );
        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; // TypeScript-safe
            const fName = (feature.get("name") as string) || "Без названия";
            const isSelected = selectedFeature?.getId() === fId;
            const isChecked = selectedIds.has(fId);

            return (
              <div
                key={String(fId)}
                data-feature-id={fId}
                className={`flex items-start p-2 rounded-md group transition-colors duration-150 ${
                  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={`Выбрать ${fName}`}
                  />
                </div>
                <div
                  className="flex flex-col text-gray-800 text-sm flex-grow mr-2 min-w-0 cursor-pointer"
                  onClick={() => handleFeatureClick(fId)}
                >
                  <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={fName}
                    >
                      {fName}
                    </span>
                  </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>
                  <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 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 space-x-2 p-3 bg-white border-b border-gray-200">
            <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>
        ),
        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>
    );
  }
);
// --- MAP PAGE COMPONENT ---
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) => {
      // Logic to sync sidebar selection with map
      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);

        // Делаем mapServiceInstance доступным глобально для сброса кешей
        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;
      }
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // --- ИСПРАВЛЕНИЕ: Этот хук отвечает ТОЛЬКО за клики по ПУСТОМУ месту
  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();
      }
      // Если клик был ПО объекту, НИЧЕГО не делаем. За это отвечает selectInteraction.
    };

    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]);

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

  return (
    <div className="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}
          />
        )}

        <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>

        {showHelp && (
          <div className="absolute bottom-16 right-4 z-20 p-4 bg-white rounded-lg shadow-xl max-w-xs">
            <h4 className="font-bold mb-2">Горячие клавиши:</h4>
            <ul className="text-sm space-y-2">
              <li>
                <span className="font-mono bg-gray-100 px-1 rounded">
                  Shift
                </span>{" "}
                - Режим выделения (лассо)
              </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">Esc</span>{" "}
                - Отменить выделение
              </li>
            </ul>
            <button
              onClick={() => setShowHelp(false)}
              className="mt-3 text-xs text-blue-600 hover:text-blue-800"
            >
              Закрыть
            </button>
          </div>
        )}
      </div>
      {showContent && (
        <MapSightbar
          mapService={mapServiceInstance}
          mapFeatures={mapFeatures}
          selectedFeature={selectedFeatureForSidebar}
          selectedIds={selectedIds}
          setSelectedIds={setSelectedIds}
          activeSection={activeSectionFromParent}
          setActiveSection={setActiveSectionFromParent}
        />
      )}
    </div>
  );
});
