import { useCallback, useEffect, useMemo, useState } from "react";
import { fetchRequestsForMap } from "../../api/requestApi";
import { fetchStations, type StationDto } from "../../api/stationsApi";
import type { RequestMapItem } from "../../model/requestTypes";
import type { TguPlanUi, TguPlatformUi, TimelineRange } from "../../model/timelineTypes";
import { platformLabel } from "../tgu-planning/tguTimelineMapper";
import { DEFAULT_TGU_2D_MAP_LAYERS, type Tgu2DMapLayerKey, type Tgu2DMapLayersState } from "./model/mapLayerTypes";
import type { Tgu2DMapSelection } from "./model/mapSelectionTypes";
import type { MapPolygon } from "./model/mapTypes";
import { buildTgu2DMapScene, getLayerAvailability } from "./Tgu2DMapLayers";
import { Tgu2DMapDetails } from "./Tgu2DMapDetails";
import { Tgu2DMapRequestsPanel, type RequestAtPoint } from "./Tgu2DMapRequestsPanel";
import { Tgu2DMapSidebar } from "./Tgu2DMapSidebar";
import { Tgu2DMapStationPanel } from "./Tgu2DMapStationPanel";
import { Tgu2DMapToolbar } from "./Tgu2DMapToolbar";
import { Tgu2DMapView } from "./Tgu2DMapView";
import { getTgu2DMapIntervalState, TGU_2D_MAP_INTERVAL_WARNING } from "./tgu2DMapInterval";
import { parseUtc, toUtcInputValue } from "../../utils/utcTime";

type Tgu2DMapTabProps = {
  range: TimelineRange;
  invalidRange: boolean;
  selectedPlan?: TguPlanUi;
  platforms: TguPlatformUi[];
};

export type RequestsState = {
  fromValue: string;
  toValue: string;
  items: RequestMapItem[];
  loading: boolean;
  loadedCount: number;
  error?: string;
  totalItems: number;
  truncated: boolean;
  loaded: boolean;
};

function buildInitialRequestsState(range: TimelineRange): RequestsState {
  return {
    fromValue: toUtcInputValue(range.fromMs),
    toValue: toUtcInputValue(range.toMs),
    items: [],
    loading: false,
    loadedCount: 0,
    totalItems: 0,
    truncated: false,
    loaded: false
  };
}

export function Tgu2DMapTab({
  range,
  invalidRange,
  selectedPlan,
  platforms
}: Tgu2DMapTabProps) {
  const [layers, setLayers] = useState<Tgu2DMapLayersState>(DEFAULT_TGU_2D_MAP_LAYERS);
  const [search, setSearch] = useState("");
  const [redrawToken, setRedrawToken] = useState(0);
  const [selectedObject, setSelectedObject] = useState<Tgu2DMapSelection>();
  const [hiddenIds, setHiddenIds] = useState<ReadonlySet<string>>(new Set());
  const [requestsState, setRequestsState] = useState<RequestsState>(() => buildInitialRequestsState(range));
  const [stations, setStations] = useState<StationDto[]>([]);
  const [stationsFetchFailed, setStationsFetchFailed] = useState(false);
  const [requestsAtPoint, setRequestsAtPoint] = useState<RequestAtPoint[]>([]);
  const [selectedRequestPolygonId, setSelectedRequestPolygonId] = useState<string | undefined>();
  const [hoveredRequestPolygonId, setHoveredRequestPolygonId] = useState<string | undefined>();
  const [stationPanel, setStationPanel] = useState<(Tgu2DMapSelection & { type: "marker"; layer: "stations" }) | undefined>();

  useEffect(() => {
    fetchStations()
      .then((items) => { setStations(items); setStationsFetchFailed(false); })
      .catch(() => setStationsFetchFailed(true));
  }, []);

  const mapInterval = getTgu2DMapIntervalState(range, selectedPlan);
  const mapInvalidRange = invalidRange || mapInterval.tooLarge;

  const loadRequests = useCallback(async (fromValue: string, toValue: string) => {
    const fromMs = parseUtc(fromValue);
    const toMs = parseUtc(toValue);
    if (!Number.isFinite(fromMs) || !Number.isFinite(toMs) || fromMs >= toMs) return;

    setRequestsState((current) => ({ ...current, loading: true, error: undefined, loadedCount: 0 }));
    try {
      const result = await fetchRequestsForMap(
        {
          endFrom: new Date(fromMs).toISOString().slice(0, 19),
          beginTo: new Date(toMs).toISOString().slice(0, 19)
        },
        (loaded, total) => {
          setRequestsState((current) => ({ ...current, loadedCount: loaded, totalItems: total }));
        }
      );
      setRequestsState((current) => ({
        ...current,
        items: result.items,
        loadedCount: result.items.length,
        totalItems: result.totalItems,
        truncated: result.truncated,
        loading: false,
        loaded: true
      }));
    } catch (error) {
      setRequestsState((current) => ({
        ...current,
        loading: false,
        error: error instanceof Error ? error.message : "Не удалось загрузить заявки."
      }));
    }
  }, []);

  const applyRequestsRange = useCallback(() => {
    void loadRequests(requestsState.fromValue, requestsState.toValue);
  }, [loadRequests, requestsState.fromValue, requestsState.toValue]);

  const setRequestsFrom = useCallback((fromValue: string) => {
    setRequestsState((current) => ({ ...current, fromValue }));
  }, []);

  const setRequestsTo = useCallback((toValue: string) => {
    setRequestsState((current) => ({ ...current, toValue }));
  }, []);

  const setRequestsQuickRange = useCallback((fromMs: number, toMs: number) => {
    const fromValue = toUtcInputValue(fromMs);
    const toValue = toUtcInputValue(toMs);
    setRequestsState((current) => ({ ...current, fromValue, toValue }));
    void loadRequests(fromValue, toValue);
  }, [loadRequests]);

  useEffect(() => {
    setRequestsState(buildInitialRequestsState(range));
  }, [range]);

  const toggleSpacecraft = useCallback((id: string) => {
    setHiddenIds((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }, []);

  const showAll = useCallback(() => setHiddenIds(new Set()), []);

  const planPlatform = useMemo(
    () => selectedPlan ? platforms.find((p) => p.spacecraftId === selectedPlan.spacecraftId) : undefined,
    [platforms, selectedPlan]
  );
  const satelliteLabel = selectedPlan
    ? platformLabel(planPlatform, selectedPlan.spacecraftId)
    : hiddenIds.size > 0
      ? `Скрыто: ${hiddenIds.size} КА`
      : "Все КА";

  const intervalLabel = `${formatDate(mapInterval.range.fromMs)} - ${formatDate(mapInterval.range.toMs)}`;

  const scene = useMemo(
    () => buildTgu2DMapScene({
      range: mapInterval.range,
      selectedPlan,
      platforms,
      hiddenSpacecraftIds: hiddenIds,
      requestItems: layers.requests ? requestsState.items : [],
      stationItems: layers.stations ? stations : []
    }),
    [mapInterval.range, platforms, selectedPlan, hiddenIds, requestsState.items, layers.requests, stations, layers.stations]
  );
  const layerAvailability = useMemo(
    () => getLayerAvailability(layers, scene, stationsFetchFailed),
    [layers, scene, stationsFetchFailed]
  );

  const overlayMessage = invalidRange
    ? "Некорректный интервал: from должен быть меньше to."
    : mapInterval.tooLarge
      ? TGU_2D_MAP_INTERVAL_WARNING
      : undefined;

  const toggleLayer = (key: Tgu2DMapLayerKey) => {
    setLayers((current) => ({ ...current, [key]: !current[key] }));
  };

  const handleObjectSelect = useCallback((selection?: Tgu2DMapSelection) => {
    if (selection?.type === "marker" && selection.layer === "stations") {
      setStationPanel(selection as Tgu2DMapSelection & { type: "marker"; layer: "stations" });
      setRequestsAtPoint([]);
      setSelectedRequestPolygonId(undefined);
      setHoveredRequestPolygonId(undefined);
      setSelectedObject(undefined);
    } else {
      setStationPanel(undefined);
      setSelectedObject(selection);
    }
  }, []);

  const handleRequestsAtPoint = useCallback(
    (polygons: MapPolygon[]) => {
      if (polygons.length === 0) {
        setRequestsAtPoint([]);
        setSelectedRequestPolygonId(undefined);
        setHoveredRequestPolygonId(undefined);
        return;
      }
      const joined = polygons.flatMap((polygon) => {
        const item = requestsState.items.find((r) => r.id === polygon.requestId);
        return item ? [{ polygon, item }] : [];
      });
      setRequestsAtPoint(joined);
      setSelectedRequestPolygonId(undefined);
      setHoveredRequestPolygonId(undefined);
      setStationPanel(undefined);
    },
    [requestsState.items]
  );

  const closeRequestsPanel = useCallback(() => {
    setRequestsAtPoint([]);
    setSelectedRequestPolygonId(undefined);
    setHoveredRequestPolygonId(undefined);
  }, []);

  const closeStationPanel = useCallback(() => {
    setStationPanel(undefined);
  }, []);

  return (
    <main className="tgu-map-workspace">
      <Tgu2DMapToolbar
        satelliteLabel={satelliteLabel}
        intervalLabel={intervalLabel}
        layers={layers}
        onRefresh={() => setRedrawToken((value) => value + 1)}
        onToggleLayer={toggleLayer}
      />

      <div className={`tgu-map-content${(requestsAtPoint.length > 0 || stationPanel != null) ? " has-requests-panel" : ""}`}>
        <Tgu2DMapSidebar
          platforms={platforms}
          selectedPlanId={selectedPlan?.planId}
          hiddenIds={hiddenIds}
          layers={layers}
          search={search}
          layerAvailability={layerAvailability}
          requestsState={requestsState}
          onSearchChange={setSearch}
          onToggleSpacecraft={toggleSpacecraft}
          onShowAll={showAll}
          onToggleLayer={toggleLayer}
          onRequestsFromChange={setRequestsFrom}
          onRequestsToChange={setRequestsTo}
          onRequestsApply={applyRequestsRange}
          onRequestsQuickRange={setRequestsQuickRange}
        />
        <section className="tgu-map-stage">
          {overlayMessage && <div className="tgu-map-overlay">{overlayMessage}</div>}
          <Tgu2DMapView
            scene={mapInvalidRange ? { polygons: [], lines: [], markers: [] } : scene}
            layers={layers}
            redrawToken={redrawToken}
            onObjectSelect={handleObjectSelect}
            onRequestsAtPoint={handleRequestsAtPoint}
            hoveredRequestPolygonId={hoveredRequestPolygonId}
            selectedRequestPolygonId={selectedRequestPolygonId}
          />
        </section>
        {requestsAtPoint.length > 0 && (
          <Tgu2DMapRequestsPanel
            requests={requestsAtPoint}
            selectedPolygonId={selectedRequestPolygonId}
            onHover={setHoveredRequestPolygonId}
            onSelect={setSelectedRequestPolygonId}
            onClose={closeRequestsPanel}
          />
        )}
        {stationPanel != null && (
          <Tgu2DMapStationPanel selection={stationPanel} onClose={closeStationPanel} />
        )}
      </div>

      <div className="tgu-map-errors">
        {Object.entries(layerAvailability).map(([layer, message]) =>
          message ? (
            <div className="tgu-alert tgu-alert--warning" key={layer}>
              {layerLabel(layer as Tgu2DMapLayerKey)}: {message}
            </div>
          ) : null
        )}
      </div>

      <Tgu2DMapDetails selectedPlan={selectedPlan} selectedObject={selectedObject} />
    </main>
  );
}

function formatDate(ms: number): string {
  return new Date(ms).toLocaleString("ru-RU", {
    timeZone: "UTC",
    day: "2-digit",
    month: "2-digit",
    hour: "2-digit",
    minute: "2-digit"
  });
}

function layerLabel(layer: Tgu2DMapLayerKey): string {
  switch (layer) {
    case "tracks":
      return "Трасса КА";
    case "swath":
      return "Полоса обзора";
    case "planWorks":
      return "Работы плана";
    case "stations":
      return "Станции";
    case "spacecraftMarkers":
      return "Маркеры КА";
    case "requests":
      return "Заявки на съёмку";
  }
}
