import { useCallback, useEffect, useMemo, useState } from "react";
import {
  fetchComplexPlanRunModes,
  fetchComplexPlanRuns,
  runComplexPlan,
  MAX_COMPLEX_PLAN_RANGE_DAYS,
  MAX_COMPLEX_PLAN_RANGE_MS,
  type ComplexPlanMode,
  type ComplexPlanRunSummary
} from "../../api/complexPlanApi";
import { fetchRequestsForMap } from "../../api/requestApi";
import { fetchPriorityCells, type PriorityCell } from "../../api/cellsApi";
import type { RequestMapItem } from "../../model/requestTypes";
import type { TguPlatformUi, TimelineRange } from "../../model/timelineTypes";
import { DateTime24Field } from "../../components/timeFields";
import { parseWktPolygon } from "../tgu-map-2d/geometry/wktParser";
import { buildTgu2DMapScene } from "../tgu-map-2d/Tgu2DMapLayers";
import type { Tgu2DMapLayersState } from "../tgu-map-2d/model/mapLayerTypes";
import type { MapPolygon, Tgu2DMapScene } from "../tgu-map-2d/model/mapTypes";
import type { Tgu2DMapSelection } from "../tgu-map-2d/model/mapSelectionTypes";
import { Tgu2DMapView } from "../tgu-map-2d/Tgu2DMapView";
import { MapObjectInspector } from "../tgu-map-2d/MapObjectInspector";
import { formatLocalDateTime, parseLocal, toLocalInputValue, toUtcIso } from "../../utils/localTime";
import { formatVolume } from "../../utils/dataVolume";

type TguComplexPlanTabProps = {
  range: TimelineRange;
  /** Все эксплуатируемые КА вкладки «Планы» — по ним считаем комплексный план. */
  platforms: TguPlatformUi[];
};

export function TguComplexPlanTab({ range, platforms }: TguComplexPlanTabProps) {
  const [fromValue, setFromValue] = useState(() => toLocalInputValue(range.fromMs));
  const [toValue, setToValue] = useState(() => toLocalInputValue(range.toMs));
  const [running, setRunning] = useState(false);
  const [modes, setModes] = useState<ComplexPlanMode[]>([]);
  const [error, setError] = useState<string>();
  const [notice, setNotice] = useState<string>();

  // История прогонов и выбранный для просмотра прогон.
  const [runs, setRuns] = useState<ComplexPlanRunSummary[]>([]);
  const [runsError, setRunsError] = useState<string>();
  const [selectedRunId, setSelectedRunId] = useState<number>();
  const [loadingRun, setLoadingRun] = useState(false);

  // Наложение заявок поверх контуров комплексного плана.
  const [showRequests, setShowRequests] = useState(false);
  const [requestItems, setRequestItems] = useState<RequestMapItem[]>([]);
  const [requestsLoading, setRequestsLoading] = useState(false);
  const [requestsError, setRequestsError] = useState<string>();

  // Тепловая карта приоритетов ячеек сетки заданий (фоновый слой, тумблер-чекбокс).
  const [showHeatmap, setShowHeatmap] = useState(false);
  const [priorityCells, setPriorityCells] = useState<PriorityCell[]>([]);
  const [heatmapLoading, setHeatmapLoading] = useState(false);
  const [heatmapError, setHeatmapError] = useState<string>();
  const [heatmapLoaded, setHeatmapLoaded] = useState(false);

  // Инспектор объекта карты справа (по аналогии с редактором): выбранные под кликом объекты
  // (1 — карточка сразу, ≥2 — список с раскрытием) и id раскрытого в списке.
  const [mapHits, setMapHits] = useState<Tgu2DMapSelection[]>([]);
  const [activeHitId, setActiveHitId] = useState<string>();

  // NORAD-id всех эксплуатируемых КА «Планов» (spacecraftId == norad-id, см. tguTimelineMapper).
  const satelliteIds = useMemo(
    () =>
      Array.from(
        new Set(
          platforms
            .map((platform) => Number(platform.spacecraftId))
            .filter((id) => Number.isFinite(id))
        )
      ),
    [platforms]
  );

  const parsedRange = useMemo(
    () => ({ fromMs: parseLocal(fromValue), toMs: parseLocal(toValue) }),
    [fromValue, toValue]
  );
  const rangeInvalid =
    !Number.isFinite(parsedRange.fromMs) ||
    !Number.isFinite(parsedRange.toMs) ||
    parsedRange.fromMs >= parsedRange.toMs;
  const rangeTooLong =
    !rangeInvalid && parsedRange.toMs - parsedRange.fromMs > MAX_COMPLEX_PLAN_RANGE_MS;

  const selectedRun = useMemo(
    () => runs.find((run) => run.runId === selectedRunId),
    [runs, selectedRunId]
  );

  // Интервал, по которому грузим заявки: показанный прогон (если выбран) либо поля «С/По».
  const activeInterval = useMemo(() => {
    if (selectedRun) {
      return { fromMs: parseLocal(selectedRun.intervalStart), toMs: parseLocal(selectedRun.intervalEnd) };
    }
    return parsedRange;
  }, [selectedRun, parsedRange]);

  const modePolygons = useMemo(() => buildModePolygons(modes), [modes]);
  const requestPolygons = useMemo(
    () => (showRequests ? buildTgu2DMapScene({ range, platforms, requestItems }).polygons : []),
    [showRequests, requestItems, range, platforms]
  );

  const heatmapPolygons = useMemo<MapPolygon[]>(
    () => (showHeatmap ? buildHeatmapPolygons(priorityCells) : []),
    [showHeatmap, priorityCells]
  );

  const scene = useMemo<Tgu2DMapScene>(
    () => ({ polygons: [...heatmapPolygons, ...modePolygons, ...requestPolygons], lines: [], markers: [] }),
    [heatmapPolygons, modePolygons, requestPolygons]
  );

  const layers = useMemo<Tgu2DMapLayersState>(
    () => ({
      tracks: false,
      swath: false,
      planWorks: false,
      complexPlan: true,
      heatmap: showHeatmap,
      stations: false,
      spacecraftMarkers: false,
      requests: showRequests
    }),
    [showHeatmap, showRequests]
  );

  const loadRuns = useCallback(async (): Promise<ComplexPlanRunSummary[]> => {
    setRunsError(undefined);
    try {
      const items = await fetchComplexPlanRuns();
      setRuns(items);
      return items;
    } catch (caught) {
      setRunsError(caught instanceof Error ? caught.message : "Не удалось загрузить историю расчётов.");
      return [];
    }
  }, []);

  const loadRunModes = useCallback(async (runId: number) => {
    setSelectedRunId(runId);
    setMapHits([]);
    setActiveHitId(undefined);
    setLoadingRun(true);
    setError(undefined);
    try {
      const runModes = await fetchComplexPlanRunModes(runId);
      setModes(runModes);
    } catch (caught) {
      setError(caught instanceof Error ? caught.message : "Не удалось загрузить результат расчёта.");
    } finally {
      setLoadingRun(false);
    }
  }, []);

  // На входе во вкладку подтягиваем историю и сразу показываем последний построенный план.
  useEffect(() => {
    void (async () => {
      const items = await loadRuns();
      const latest = items.find((run) => run.status === "COMPLETED" && run.calculatedModesCount > 0) ?? items[0];
      if (latest) void loadRunModes(latest.runId);
    })();
  }, [loadRuns, loadRunModes]);

  // Заявки за активный интервал (по тумблеру) — для сопоставления контуров плана с заявками.
  useEffect(() => {
    if (!showRequests) return;
    const { fromMs, toMs } = activeInterval;
    if (!Number.isFinite(fromMs) || !Number.isFinite(toMs) || fromMs >= toMs) return;
    let cancelled = false;
    setRequestsLoading(true);
    setRequestsError(undefined);
    fetchRequestsForMap({ endFrom: toUtcIso(fromMs), beginTo: toUtcIso(toMs) })
      .then((result) => {
        if (!cancelled) setRequestItems(result.items);
      })
      .catch((caught) => {
        if (!cancelled) setRequestsError(caught instanceof Error ? caught.message : "Не удалось загрузить заявки.");
      })
      .finally(() => {
        if (!cancelled) setRequestsLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [showRequests, activeInterval]);

  // Карта приоритетов не зависит от интервала — грузим один раз при первом включении чекбокса.
  // heatmapLoading намеренно НЕ в зависимостях: иначе setHeatmapLoading(true) перезапустил бы
  // эффект, а его cleanup отменил бы собственный незавершённый fetch (cancelled=true).
  useEffect(() => {
    if (!showHeatmap || heatmapLoaded) return;
    let cancelled = false;
    setHeatmapLoading(true);
    setHeatmapError(undefined);
    fetchPriorityCells()
      .then((cells) => {
        if (!cancelled) {
          setPriorityCells(cells);
          setHeatmapLoaded(true);
        }
      })
      .catch((caught) => {
        if (!cancelled) setHeatmapError(caught instanceof Error ? caught.message : "Не удалось загрузить карту приоритетов.");
      })
      .finally(() => {
        if (!cancelled) setHeatmapLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [showHeatmap, heatmapLoaded]);

  // Клик по карте: 0/1 объект — карточка сразу; ≥2 (наложение) — список под точкой клика.
  const handleObjectSelect = useCallback((selection?: Tgu2DMapSelection) => {
    setMapHits(selection ? [selection] : []);
    setActiveHitId(selection?.id);
  }, []);

  const handleObjectsAtPoint = useCallback((selections: Tgu2DMapSelection[]) => {
    // При включённой тепловой карте ячейка приоритета всегда идёт первой в списке и раскрыта
    // по умолчанию — оператор сразу видит приоритет точки, а контуры/заявки ниже.
    const ordered = showHeatmap
      ? [...selections].sort((a, b) => Number(b.layer === "heatmap") - Number(a.layer === "heatmap"))
      : selections;
    setMapHits(ordered);
    setActiveHitId(ordered[0]?.id);
  }, [showHeatmap]);

  const toggleHit = useCallback((selection: Tgu2DMapSelection) => {
    setActiveHitId((current) => (current === selection.id ? undefined : selection.id));
  }, []);

  const closeInfoPanel = useCallback(() => {
    setMapHits([]);
    setActiveHitId(undefined);
  }, []);

  const runCalculation = async () => {
    if (running || rangeInvalid || rangeTooLong || satelliteIds.length === 0) return;
    setRunning(true);
    setError(undefined);
    setNotice(undefined);
    try {
      const runResult = await runComplexPlan({
        fromMs: parsedRange.fromMs,
        toMs: parsedRange.toMs,
        satelliteIds
      });
      await loadRuns();
      await loadRunModes(runResult.runId);
      setNotice(
        runResult.calculatedModesCount > 0
          ? `Расчёт завершён: режимов ${runResult.calculatedModesCount}, КА затронуто ${runResult.affectedSatellitesCount}, охват ${formatPercent(runResult.coveredAreaPercent)}.`
          : "Расчёт завершён, но включений не построено: на интервал нет покрытия заявками."
      );
    } catch (caught) {
      setError(caught instanceof Error ? caught.message : "Не удалось рассчитать комплексный план.");
    } finally {
      setRunning(false);
    }
  };

  const canRun = !running && !rangeInvalid && !rangeTooLong && satelliteIds.length > 0;
  const busy = running || loadingRun;
  const overlayMessage = busy
    ? running
      ? "Идёт расчёт…"
      : "Загрузка плана…"
    : rangeInvalid
      ? "Некорректный интервал: «С» должно быть раньше «По»."
      : modes.length === 0
        ? "Задайте интервал и нажмите «Рассчитать» либо выберите построенный план из истории."
        : undefined;

  const displayRun = selectedRun;
  const contoursCount = modePolygons.length;
  // Суммарный объём режимов run’а (МБ) — съёмка по скорости накопления, сброс по скорости канала.
  const totalVolumeMb = modes.reduce((sum, mode) => sum + (mode.volumeMb ?? 0), 0);

  return (
    <main className="tgu-map-workspace">
      <div className="tgu-map-toolbar">
        <div>
          <div className="tgu-map-toolbar__title">Комплексный план</div>
          <div className="tgu-map-toolbar__subtitle">
            Расчёт по {satelliteIds.length} КА вкладки «Планы»
          </div>
        </div>
        <label className="tgu-field">
          <span>С</span>
          <DateTime24Field value={fromValue} onChange={setFromValue} />
        </label>
        <label className="tgu-field">
          <span>По</span>
          <DateTime24Field value={toValue} onChange={setToValue} />
        </label>
        <button className="tgu-button tgu-button--primary" type="button" onClick={runCalculation} disabled={!canRun}>
          {running ? "Расчёт…" : "Рассчитать"}
        </button>
        <label className="tgu-field">
          <span>Построенные планы</span>
          <select
            className="tgu-select"
            value={selectedRunId ?? ""}
            disabled={runs.length === 0 || busy}
            onChange={(event) => {
              const value = Number(event.target.value);
              if (Number.isFinite(value)) void loadRunModes(value);
            }}
          >
            {runs.length === 0 && <option value="">нет расчётов</option>}
            {runs.map((run) => (
              <option key={run.runId} value={run.runId}>
                {formatRunOption(run)}
              </option>
            ))}
          </select>
        </label>
        <button
          className={`tgu-button${showRequests ? " is-active" : ""}`}
          type="button"
          onClick={() => setShowRequests((value) => !value)}
        >
          {showRequests ? "Скрыть заявки" : "Показать заявки"}
        </button>
        <label className="tgu-checkbox" title="Тепловая карта приоритетов ячеек сетки заданий">
          <input
            type="checkbox"
            checked={showHeatmap}
            onChange={(event) => setShowHeatmap(event.target.checked)}
          />
          <span>Тепловая карта</span>
        </label>
        <div className="tgu-map-toolbar__spacer" style={{ flex: 1 }} />
        {displayRun && (
          <div className="tgu-map-toolbar__subtitle">
            run #{displayRun.runId} · режимов {displayRun.calculatedModesCount} · охват {formatPercent(displayRun.coveredAreaPercent)} · объём {formatVolume(totalVolumeMb)}
            {displayRun.snapshotIds.length > 0 ? ` · snapshot ${displayRun.snapshotIds.join(", ")}` : ""} · на карте {contoursCount} контуров
            {showRequests ? ` · заявок ${requestItems.length}` : ""}
            {showHeatmap ? ` · ячеек ${heatmapPolygons.length}` : ""}
          </div>
        )}
      </div>

      <div
        className="tgu-complex-body"
        style={{ gridTemplateColumns: mapHits.length > 0 ? "minmax(0, 1fr) 22rem" : "minmax(0, 1fr) 0" }}
      >
        <section className="tgu-map-stage">
          {overlayMessage && <div className="tgu-map-overlay">{overlayMessage}</div>}
          <Tgu2DMapView
            scene={scene}
            layers={layers}
            redrawToken={modes.length + requestPolygons.length + heatmapPolygons.length}
            enableObjectChooser
            onObjectsAtPoint={handleObjectsAtPoint}
            onObjectSelect={handleObjectSelect}
            selectedObjectId={activeHitId}
          />
        </section>

        {mapHits.length > 0 && (
          <aside className="tgu-editor-side-panel">
            <MapObjectInspector
              hits={mapHits}
              activeId={activeHitId}
              onToggle={toggleHit}
              onClose={closeInfoPanel}
              modes={modes}
              requestItems={requestItems}
              priorityCells={priorityCells}
            />
          </aside>
        )}
      </div>

      <div className="tgu-map-errors">
        {satelliteIds.length === 0 && (
          <div className="tgu-alert tgu-alert--warning">
            Нет эксплуатируемых КА на вкладке «Планы» — расчёт недоступен.
          </div>
        )}
        {rangeTooLong && (
          <div className="tgu-alert tgu-alert--warning">
            Интервал расчёта не может превышать {MAX_COMPLEX_PLAN_RANGE_DAYS} суток — сократите диапазон «С/По».
          </div>
        )}
        {requestsLoading && <div className="tgu-alert">Загрузка заявок…</div>}
        {requestsError && <div className="tgu-alert tgu-alert--danger">{requestsError}</div>}
        {heatmapLoading && <div className="tgu-alert">Загрузка карты приоритетов…</div>}
        {heatmapError && <div className="tgu-alert tgu-alert--danger">{heatmapError}</div>}
        {showHeatmap && heatmapLoaded && !heatmapError && priorityCells.length === 0 && (
          <div className="tgu-alert tgu-alert--warning">
            Карта приоритетов пуста — на ячейки сетки заданий не спроецировано ни одной заявки
            (нужна пересборка сетки в сервисе заданий).
          </div>
        )}
        {runsError && <div className="tgu-alert tgu-alert--danger">{runsError}</div>}
        {notice && <div className="tgu-alert">{notice}</div>}
        {error && <div className="tgu-alert tgu-alert--danger">{error}</div>}
      </div>
    </main>
  );
}

/** Ячейки карты приоритетов → полигоны слоя тепловой карты (контур из WKT + важность + № ячейки). */
function buildHeatmapPolygons(cells: PriorityCell[]): MapPolygon[] {
  const result: MapPolygon[] = [];
  let zIndex = 0;
  for (const cell of cells) {
    if (!cell.contour) continue;
    const rings = parseWktPolygon(cell.contour);
    for (const ring of rings) {
      if (ring.length < 3) continue;
      result.push({
        id: `heatmap-cell:${cell.cellNum}`,
        name: `Ячейка ${cell.cellNum}`,
        kind: "heatmap",
        layer: "heatmap",
        points: ring,
        importance: cell.importance,
        cellNum: cell.cellNum,
        zIndex: zIndex++
      });
    }
  }
  return result;
}

/** Контуры режимов (survey) → полигоны слоя комплексного плана. Сбросы без контура пропускаем. */
function buildModePolygons(modes: ComplexPlanMode[]): MapPolygon[] {
  const polygons: MapPolygon[] = [];
  let zIndex = 0;
  for (const mode of modes) {
    if (!mode.contourWkt) continue;
    const rings = parseWktPolygon(mode.contourWkt);
    for (const ring of rings) {
      if (ring.length < 3) continue;
      polygons.push({
        id: `complex-mode:${mode.id}:${zIndex}`,
        name: `КА ${mode.satelliteId} · виток ${mode.revolution}`,
        kind: "complexPlan",
        layer: "complexPlan",
        points: ring,
        spacecraftId: String(mode.satelliteId),
        modeId: mode.id,
        zIndex: zIndex++
      });
    }
  }
  return polygons;
}

/** Подпись прогона в выпадающем списке истории: интервал, статус и счётчик режимов. */
function formatRunOption(run: ComplexPlanRunSummary): string {
  const interval = `${formatLocalDateTime(parseLocal(run.intervalStart))} — ${formatLocalDateTime(parseLocal(run.intervalEnd))}`;
  const status = run.status === "COMPLETED" ? "" : ` · ${run.status}`;
  return `#${run.runId} · ${interval} · режимов ${run.calculatedModesCount}${status}`;
}

function formatPercent(value: number | null | undefined): string {
  if (value == null || !Number.isFinite(value)) return "—";
  return `${value.toFixed(1)}%`;
}
