import { useParams } from "react-router";
import { authInstance, languageInstance } from "@shared";
import {
  createContext,
  ReactNode,
  useContext,
  useEffect,
  useMemo,
  useState,
} from "react";
import {
  RouteData,
  SightData,
  SightPatchData,
  StationData,
  StationPatchData,
} from "./types";

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

const MapDataContext = createContext<{
  originalRouteData?: RouteData;
  originalStationData?: StationData[];
  originalSightData?: SightData[];
  routeData?: RouteData;
  stationData?: StationDataWithLanguage;
  sightData?: SightData[];

  isRouteLoading: boolean;
  isStationLoading: boolean;
  isSightLoading: boolean;
  selectedSight?: SightData;
  setSelectedSight: (sight?: SightData) => void;
  setScaleRange: (min: number, max: number) => void;
  setMapRotation: (rotation: number) => void;
  setMapCenter: (x: number, y: number) => void;
  setStationOffset: (stationId: number, x: number, y: number) => void;
  setStationAlign: (stationId: number, align: number) => void;
  setStationIconSize: (stationId: number, size: number) => void;
  setSightCoordinates: (
    sightId: number,
    latitude: number,
    longitude: number
  ) => void;
  setSightIconSize: (sightId: number, size: number) => void;
  setFontSize: (size: number) => void;
  setRouteIconSize: (size: number) => void;
  saveChanges: () => void;
}>({
  originalRouteData: undefined,
  originalStationData: undefined,
  originalSightData: undefined,
  routeData: undefined,
  stationData: undefined,
  sightData: undefined,

  isRouteLoading: true,
  isStationLoading: true,
  isSightLoading: true,
  selectedSight: undefined,
  setSelectedSight: () => {},
  setScaleRange: () => {},
  setMapRotation: () => {},
  setMapCenter: () => {},
  setStationOffset: () => {},
  setStationAlign: () => {},
  setStationIconSize: () => {},
  setSightCoordinates: () => {},
  setSightIconSize: () => {},
  setFontSize: () => {},
  setRouteIconSize: () => {},
  saveChanges: () => {},
});

type StationDataWithLanguage = {
  [key: string]: StationData[];
};
export const MapDataProvider = observer(
  ({ children }: Readonly<{ children: ReactNode }>) => {
    const { id: routeId } = useParams<{ id: string }>();

    const [originalRouteData, setOriginalRouteData] = useState<RouteData>();
    const [originalStationData, setOriginalStationData] =
      useState<StationData[]>();
    const [originalSightData, setOriginalSightData] = useState<SightData[]>();

    const [routeData, setRouteData] = useState<RouteData>();
    const [stationData, setStationData] = useState<StationDataWithLanguage>({
      RU: [],
      EN: [],
      ZH: [],
    });
    const [sightData, setSightData] = useState<SightData[]>();

    const [routeChanges, setRouteChanges] = useState<Partial<RouteData>>({});
    const [stationChanges, setStationChanges] = useState<StationPatchData[]>(
      []
    );
    const [sightChanges, setSightChanges] = useState<SightPatchData[]>([]);

    const [isRouteLoading, setIsRouteLoading] = useState(true);
    const [isStationLoading, setIsStationLoading] = useState(true);
    const [isSightLoading, setIsSightLoading] = useState(true);
    const [selectedSight, setSelectedSight] = useState<SightData>();

    useEffect(() => {
      const fetchData = async () => {
        try {
          setIsRouteLoading(true);
          setIsStationLoading(true);
          setIsSightLoading(true);

          const [
            routeResponse,
            ruStationResponse,
            enStationResponse,
            zhStationResponse,
            sightResponse,
          ] = await Promise.all([
            authInstance.get(`/route/${routeId}`),
            languageInstance("ru").get(`/route/${routeId}/station`),
            languageInstance("en").get(`/route/${routeId}/station`),
            languageInstance("zh").get(`/route/${routeId}/station`),
            languageInstance("ru").get(`/route/${routeId}/sight`),
          ]);

          const routeData = routeResponse.data as RouteData;
          setOriginalRouteData(routeData);
          setOriginalStationData(ruStationResponse.data as StationData[]);
          setStationData({
            ru: ruStationResponse.data as StationData[],
            en: enStationResponse.data as StationData[],
            zh: zhStationResponse.data as StationData[],
          });
          setOriginalSightData(sightResponse.data as SightData[]);

          setIsRouteLoading(false);
          setIsStationLoading(false);
          setIsSightLoading(false);
        } catch (error) {
          console.error("Error fetching data:", error);
          setIsRouteLoading(false);
          setIsStationLoading(false);
          setIsSightLoading(false);
        }
      };

      fetchData();
    }, [routeId]);

    useEffect(() => {
      if (originalRouteData) {
        setRouteData({ ...originalRouteData, ...routeChanges });
      }
    }, [originalRouteData, routeChanges]);

    useEffect(() => {
      if (originalSightData) {
        setSightData(originalSightData);
      }
    }, [originalSightData]);

    function setScaleRange(min: number, max: number) {
      setRouteChanges((prev) => {
        return { ...prev, scale_min: min, scale_max: max };
      });
    }

    function setMapRotation(rotation: number) {
      setRouteChanges((prev) => {
        return { ...prev, rotate: rotation };
      });
    }

    function setFontSize(size: number) {
      const clamped = Math.max(1, Math.min(300, size));
      setRouteChanges((prev) => {
        if (prev.font_size === clamped) {
          return prev;
        }
        return { ...prev, font_size: clamped };
      });
    }

    function setRouteIconSize(size: number) {
      const clamped = Math.max(1, Math.min(300, size));
      setRouteChanges((prev) => {
        if (prev.icon_size === clamped) {
          return prev;
        }
        return { ...prev, icon_size: clamped };
      });
    }

    function setMapCenter(latitude: number, longitude: number) {
      const epsilon = 1e-6;

      setRouteChanges((prev) => {
        const prevLat = prev.center_latitude;
        const prevLon = prev.center_longitude;

        if (
          prevLat !== undefined &&
          prevLon !== undefined &&
          Math.abs(prevLat - latitude) < epsilon &&
          Math.abs(prevLon - longitude) < epsilon
        ) {
          return prev;
        }

        return {
          ...prev,
          center_latitude: latitude,
          center_longitude: longitude,
        };
      });

      setRouteData((routePrev) => {
        if (!routePrev) return routePrev;

        return {
          ...routePrev,
          center_latitude: latitude,
          center_longitude: longitude,
        };
      });
    }

    async function saveChanges() {
      await authInstance.patch(`/route/${routeId}`, routeData);
      await saveStationChanges();
      await saveSightChanges();
    }

    async function saveStationChanges() {
      for (const station of stationChanges) {
        await authInstance.patch(`/route/${routeId}/station`, station);

        setStationData((prev) => {
          const updated = { ...prev };
          Object.keys(updated).forEach((lang) => {
            updated[lang] = updated[lang].map((s) =>
              s.id === station.station_id
                ? {
                    ...s,
                    offset_x: station.offset_x,
                    offset_y: station.offset_y,
                    align: station.align,
                    icon_size:
                      typeof station.icon_size === "number"
                        ? station.icon_size
                        : s.icon_size,
                  }
                : s
            );
          });
          return updated;
        });
      }
    }

    async function saveSightChanges() {
      for (const sight of sightChanges) {
        await authInstance.patch(`/route/${routeId}/sight`, sight);

        setSightData((prev) =>
          prev
            ? prev.map((s) =>
                s.id === sight.sight_id
                  ? {
                      ...s,
                      latitude: sight.latitude,
                      longitude: sight.longitude,
                      icon_size:
                        typeof sight.icon_size === "number"
                          ? sight.icon_size
                          : s.icon_size,
                    }
                  : s
              )
            : prev
        );
      }
    }

    function setStationOffset(stationId: number, x: number, y: number) {
      const currentStation = stationData.ru?.find(
        (station) => station.id === stationId
      );
      if (
        currentStation &&
        Math.abs(currentStation.offset_x - x) < 0.01 &&
        Math.abs(currentStation.offset_y - y) < 0.01
      ) {
        return;
      }

      setStationChanges((prev) => {
        const existingIndex = prev.findIndex(
          (station) => station.station_id === stationId
        );

        if (existingIndex !== -1) {
          const newChanges = [...prev];
          newChanges[existingIndex] = {
            ...newChanges[existingIndex],
            offset_x: x,
            offset_y: y,
          };
          return newChanges;
        } else {
          const originalStation = originalStationData?.find(
            (s) => s.id === stationId
          );
          return [
            ...prev,
            {
              station_id: stationId,
              offset_x: x,
              offset_y: y,
              align: originalStation?.align ?? 1,
              icon_size: originalStation?.icon_size,
              transfers: originalStation?.transfers ?? {
                bus: "",
                metro_blue: "",
                metro_green: "",
                metro_orange: "",
                metro_purple: "",
                metro_red: "",
                train: "",
                tram: "",
                trolleybus: "",
              },
            },
          ];
        }
      });

      setStationData((prev) => {
        const updated = { ...prev };
        Object.keys(updated).forEach((lang) => {
          updated[lang] = updated[lang].map((station) => {
            if (station.id === stationId) {
              return { ...station, offset_x: x, offset_y: y };
            }
            return station;
          });
        });
        return updated;
      });
    }

    function setStationAlign(stationId: number, align: number) {
      const currentStation = stationData.ru?.find(
        (station) => station.id === stationId
      );
      if (currentStation && currentStation.align === align) {
        return;
      }

      setStationChanges((prev) => {
        const existingIndex = prev.findIndex(
          (station) => station.station_id === stationId
        );

        if (existingIndex !== -1) {
          const newChanges = [...prev];
          newChanges[existingIndex] = {
            ...newChanges[existingIndex],
            align: align,
          };
          return newChanges;
        } else {
          const originalStation = originalStationData?.find(
            (s) => s.id === stationId
          );
          return [
            ...prev,
            {
              station_id: stationId,
              align: align,
              offset_x: originalStation?.offset_x ?? 0,
              offset_y: originalStation?.offset_y ?? 0,
              icon_size: originalStation?.icon_size,
              transfers: originalStation?.transfers ?? {
                bus: "",
                metro_blue: "",
                metro_green: "",
                metro_orange: "",
                metro_purple: "",
                metro_red: "",
                train: "",
                tram: "",
                trolleybus: "",
              },
            },
          ];
        }
      });

      setStationData((prev) => {
        const updated = { ...prev };
        Object.keys(updated).forEach((lang) => {
          updated[lang] = updated[lang].map((station) => {
            if (station.id === stationId) {
              return { ...station, align: align };
            }
            return station;
          });
        });
        return updated;
      });
    }

    function setStationIconSize(stationId: number, size: number) {
      const clamped = Math.max(1, Math.min(300, Math.round(size)));
      const currentStation = stationData.ru?.find(
        (station) => station.id === stationId
      );
      if (currentStation?.icon_size === clamped) {
        return;
      }

      setStationChanges((prev) => {
        const existingIndex = prev.findIndex(
          (station) => station.station_id === stationId
        );

        if (existingIndex !== -1) {
          const next = [...prev];
          next[existingIndex] = {
            ...next[existingIndex],
            icon_size: clamped,
          };
          return next;
        }

        const originalStation = originalStationData?.find(
          (s) => s.id === stationId
        );

        return [
          ...prev,
          {
            station_id: stationId,
            offset_x: currentStation?.offset_x ?? originalStation?.offset_x ?? 0,
            offset_y: currentStation?.offset_y ?? originalStation?.offset_y ?? 0,
            align: currentStation?.align ?? originalStation?.align ?? 1,
            icon_size: clamped,
            transfers: currentStation?.transfers ??
              originalStation?.transfers ?? {
                bus: "",
                metro_blue: "",
                metro_green: "",
                metro_orange: "",
                metro_purple: "",
                metro_red: "",
                train: "",
                tram: "",
                trolleybus: "",
              },
          },
        ];
      });

      setStationData((prev) => {
        const updated = { ...prev };
        Object.keys(updated).forEach((lang) => {
          updated[lang] = updated[lang].map((station) =>
            station.id === stationId
              ? { ...station, icon_size: clamped }
              : station
          );
        });
        return updated;
      });
    }

    function setSightCoordinates(
      sightId: number,
      latitude: number,
      longitude: number
    ) {
      setSightData((prev) =>
        prev
          ? prev.map((sight) =>
              sight.id === sightId ? { ...sight, latitude, longitude } : sight
            )
          : prev
      );

      setSightChanges((prev) => {
        const existingIndex = prev.findIndex(
          (sight) => sight.sight_id === sightId
        );

        if (existingIndex !== -1) {
          return prev.map((sight, index) => {
            if (index === existingIndex) {
              return {
                ...sight,
                latitude,
                longitude,
              };
            }
            return sight;
          });
        } else {
          const foundSight = sightData?.find((sight) => sight.id === sightId);
          if (foundSight) {
            return [
              ...prev,
              {
                sight_id: sightId,
                latitude,
                longitude,
                icon_size: foundSight.icon_size,
              },
            ];
          }
          return prev;
        }
      });
    }

    function setSightIconSize(sightId: number, size: number) {
      const clamped = Math.max(1, Math.min(300, Math.round(size)));

      setSightData((prev) =>
        prev
          ? prev.map((sight) =>
              sight.id === sightId ? { ...sight, icon_size: clamped } : sight
            )
          : prev
      );

      setSightChanges((prev) => {
        const existingIndex = prev.findIndex(
          (sight) => sight.sight_id === sightId
        );
        if (existingIndex !== -1) {
          const next = [...prev];
          next[existingIndex] = {
            ...next[existingIndex],
            icon_size: clamped,
          };
          return next;
        }

        const foundSight =
          sightData?.find((sight) => sight.id === sightId) ??
          originalSightData?.find((sight) => sight.id === sightId);
        if (!foundSight) {
          return prev;
        }

        return [
          ...prev,
          {
            sight_id: sightId,
            latitude: foundSight.latitude,
            longitude: foundSight.longitude,
            icon_size: clamped,
          },
        ];
      });
    }

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

    const value = useMemo(
      () => ({
        originalRouteData,
        originalStationData,
        originalSightData,
        routeData,
        stationData,
        sightData,
        isRouteLoading,
        isStationLoading,
        isSightLoading,
        selectedSight,
        setSelectedSight,
        setScaleRange,
        setMapRotation,
        setMapCenter,
        saveChanges,
        setStationOffset,
        setStationAlign,
        setStationIconSize,
        setSightCoordinates,
        setSightIconSize,
        setFontSize,
        setRouteIconSize,
      }),
      [
        originalRouteData,
        originalStationData,
        originalSightData,
        routeData,
        stationData,
        sightData,
        isRouteLoading,
        isStationLoading,
        isSightLoading,
        selectedSight,
        setStationIconSize,
        setSightIconSize,
        setFontSize,
        setRouteIconSize,
      ]
    );

    return (
      <MapDataContext.Provider value={value}>
        {children}
      </MapDataContext.Provider>
    );
  }
);

export const useMapData = () => {
  const context = useContext(MapDataContext);
  if (!context) {
    throw new Error("useMapData must be used within a MapDataProvider");
  }
  return context;
};
