import type { ReactNode } from "react";
import { formatDate, formatDateTime, formatDateTimeSeconds, formatDuration, formatTime, formatVolume, kindColor, kindLabel } from "./editorPresentation";
import { statusStyle } from "../tgu-planning/tguStatus";
import { formatLocalDateTime, parseLocal, toLocalIso } from "../../utils/localTime";
import { Time24Field } from "../../components/timeFields";
import type { RvaZone } from "../../api/ballisticsApi";
import type { SurveyRoute } from "../../api/missionPlanningApi";
import type { Tgu2DMapSelection } from "../tgu-map-2d/model/mapSelectionTypes";
import type { RequestMapItem } from "../../model/requestTypes";
import type {
  EditorGroundStation,
  EditorMode,
  EditorTarget,
  EditorVisibilityWindow,
  EditorWork,
  EditorWorkPatch
} from "./model/editorTypes";

export type DownlinkSelection = {
  station: EditorGroundStation;
  pass: EditorVisibilityWindow;
};

export type DownlinkStationOption = {
  station: EditorGroundStation;
  passes: EditorVisibilityWindow[];
};

type WorkInspectorProps = {
  work: EditorWork;
  modes: EditorMode[];
  stations: EditorGroundStation[];
  /** Название аппарата редактора — для блока параметров включения. */
  spacecraftLabel?: string;
  /** Метка съёмки по id режима — для перечня сбрасываемых маршрутов у сброса. */
  surveyModeLabel?: (surveyModeId: number) => string;
  conflicts: string[];
  /** Включение существующего плана — показываем только информацию, без правок. */
  readOnly?: boolean;
  /** Разрешить удаление даже у read-only включения плана (есть черновик — режим живёт в нём). */
  canDelete?: boolean;
  /** План редактируемый — показываем кнопку «Редактировать» (открывает окно построения). */
  canEdit?: boolean;
  /** Открыть включение в окне построения для правки параметров. */
  onEdit: (workId: string) => void;
  onDelete: (workId: string) => void;
  onClose: () => void;
};

type ShootPanelProps = {
  target?: EditorTarget;
  passes: EditorVisibilityWindow[];
  loading?: boolean;
  error?: string;
  selectedPass?: EditorVisibilityWindow;
  modeId?: string;
  modes: EditorMode[];
  /** Окно строящегося маршрута (локальное, мс). undefined пока не выбран проход. */
  routeWindow?: { startMs: number; endMs: number };
  /** Текущий крен (gamma) маршрута, градусы. */
  routeRoll?: number;
  /** Окно видимости выбранного прохода — для подписи длительности. */
  passStartMs?: number;
  passEndMs?: number;
  /** Допустимый диапазон времени окна маршрута (±30 мин от окна видимости), мс. */
  routeMinMs?: number;
  routeMaxMs?: number;
  routeLoading?: boolean;
  routeError?: string;
  route?: SurveyRoute;
  /** Режим правки существующей съёмки — меняет заголовок и подпись кнопки. */
  editing?: boolean;
  /** Активен режим прицеливания: карта в режиме клика по точке (перекрестие), иначе — «рука». */
  picking?: boolean;
  /** Включить режим прицеливания (указать/сменить опорную точку одним кликом по карте). */
  onPickTarget: () => void;
  /** Почему нельзя добавить включение в этот план (заложен/стартовал) — гасит «Добавить». */
  addBlockedReason?: string;
  onRouteStartChange: (startMs: number) => void;
  onRouteEndChange: (endMs: number) => void;
  /** Сдвиг окна целиком вперёд/назад (мс), длительность сохраняется. */
  onRouteShift: (deltaMs: number) => void;
  /** Изменение крена влево/вправо (градусы, дельта от кнопок). */
  onRouteRollChange: (deltaDeg: number) => void;
  /** Установка крена напрямую (ручной ввод), градусы. */
  onRouteRollSet: (deg: number) => void;
  onSelectPass: (pass?: EditorVisibilityWindow) => void;
  onModeChange: (modeId: string) => void;
  onAdd: () => void;
  onClose: () => void;
};

/** Минимальная длительность маршрута, мс (нижняя граница окна). */
const MIN_ROUTE_DURATION_MS = 5_000;

type DownlinkPanelProps = {
  stationOptions: DownlinkStationOption[];
  selected?: DownlinkSelection;
  onSelect: (selection?: DownlinkSelection) => void;
  onAdd: () => void;
  onClose: () => void;
};

/** Маршрут съёмки — кандидат на сброс в выбранной зоне (заканчивается до начала зоны). */
export type DropCandidateSurvey = {
  id: number;
  label: string;
  startMs: number;
  endMs: number;
};

type DropPanelProps = {
  zones: RvaZone[];
  loading?: boolean;
  error?: string;
  selectedZone?: RvaZone;
  onSelectZone: (zone?: RvaZone) => void;
  /** Название станции по её номеру (из каталога станций). */
  stationName: (stationId: number) => string;
  /** Время начала зоны в мс (локальное) — для подписи. */
  zoneStartMs: (zone: RvaZone) => number;
  zoneStopMs: (zone: RvaZone) => number;
  /** Съёмки-кандидаты для выбранной зоны (end < начало зоны). */
  candidates: DropCandidateSurvey[];
  selectedSurveyIds: Set<number>;
  onToggleSurvey: (id: number) => void;
  saving?: boolean;
  saveError?: string;
  /** Режим правки существующего сброса — меняет заголовок и подпись кнопки. */
  editing?: boolean;
  /** Почему нельзя добавить включение в этот план (заложен/стартовал) — гасит «Добавить». */
  addBlockedReason?: string;
  onAdd: () => void;
  onClose: () => void;
};

export function WorkInspector({
  work,
  modes,
  stations,
  spacecraftLabel,
  surveyModeLabel,
  conflicts,
  readOnly,
  canDelete,
  canEdit,
  onEdit,
  onDelete,
  onClose
}: WorkInspectorProps) {
  const color = kindColor(work.kind);

  return (
    <div className="tgu-editor-panel">
      <PanelHead title={kindLabel(work.kind)} onClose={onClose} />
      <div className="tgu-editor-panel__id">{work.id}</div>

      {conflicts.length > 0 && (
        <div className="tgu-alert tgu-alert--warning">
          {conflicts.map((conflict) => (
            <div key={conflict}>{conflict}</div>
          ))}
        </div>
      )}

      <section className="tgu-editor-panel__section">
        <div className="tgu-editor-eyebrow">Время</div>
        <dl className="tgu-map-request-details__grid">
          <dt>Начало</dt>
          <dd className="mono">{formatDateTimeSeconds(work.startMs)}</dd>
          <dt>Конец</dt>
          <dd className="mono">{formatDateTimeSeconds(work.endMs)}</dd>
          <dt>Длительность</dt>
          <dd className="mono">{formatDuration(work.endMs - work.startMs)}</dd>
          <dt>Объём</dt>
          <dd className="mono">{formatVolume(work.volumeMb)}</dd>
        </dl>
      </section>

      <section className="tgu-editor-panel__section">
        <div className="tgu-editor-eyebrow">Параметры включения</div>
        <dl className="tgu-map-request-details__grid">
          <dt>Аппарат</dt>
          <dd>{spacecraftLabel ?? "—"}</dd>
          <dt>Виток</dt>
          <dd className="mono">{work.revolution ?? "—"}</dd>
          {work.kind === "shoot" && (
            <>
              <dt>Крен</dt>
              <dd className="mono">{work.rollDeg != null ? `${work.rollDeg.toFixed(1)}°` : "—"}</dd>
              <dt>Угол захвата</dt>
              <dd className="mono">{work.captureAngleDeg != null ? `${work.captureAngleDeg.toFixed(1)}°` : "—"}</dd>
              <dt>Центр контура</dt>
              <dd className="mono">
                {work.centerLat != null && work.centerLon != null
                  ? `${work.centerLat.toFixed(2)}, ${work.centerLon.toFixed(2)}`
                  : "—"}
              </dd>
            </>
          )}
          {work.kind === "downlink" && (
            <>
              <dt>Станция</dt>
              <dd>{stations.find((station) => station.id === work.stationId)?.name ?? work.stationId ?? "—"}</dd>
            </>
          )}
        </dl>
      </section>

      {work.kind === "shoot" && (
        <section className="tgu-editor-panel__section">
          <div className="tgu-editor-eyebrow">Задания под маршрутом</div>
          <div className="tgu-editor-panel__hint">Привязка заданий к маршруту появится позже.</div>
        </section>
      )}

      {work.kind === "downlink" && (
        <section className="tgu-editor-panel__section">
          <div className="tgu-editor-eyebrow">Сбрасываемые съёмки</div>
          {work.surveyIds && work.surveyIds.length > 0 ? (
            <ul className="tgu-editor-panel__list">
              {work.surveyIds.map((surveyId) => (
                <li key={surveyId}>{surveyModeLabel ? surveyModeLabel(surveyId) : `Маршрут #${surveyId}`}</li>
              ))}
            </ul>
          ) : (
            <div className="tgu-editor-panel__hint">Маршруты съёмки к сбросу не привязаны.</div>
          )}
        </section>
      )}

      {work.kind === "shoot" && (
        <section className="tgu-editor-panel__section">
          <div className="tgu-editor-eyebrow">Режим аппаратуры</div>
          <div className="tgu-editor-panel__hint">{modes.find((mode) => mode.id === work.modeId)?.label ?? "—"}</div>
          {work.targetName && <div className="tgu-editor-panel__hint">цель: {work.targetName}</div>}
        </section>
      )}

      {(canEdit || !readOnly || canDelete) && (
        <div className="tgu-editor-panel__actions">
          {canEdit && (
            <button className="tgu-button" type="button" onClick={() => onEdit(work.id)}>
              Редактировать
            </button>
          )}
          {(!readOnly || canDelete) && (
            <button className="tgu-button tgu-button--reject" type="button" onClick={() => onDelete(work.id)}>
              Удалить
            </button>
          )}
        </div>
      )}

      <span className="tgu-editor-panel__color" style={{ background: color }} aria-hidden="true" />
    </div>
  );
}

export function ShootPanel({
  target,
  passes,
  loading,
  error,
  selectedPass,
  modeId,
  modes,
  routeWindow,
  routeRoll,
  passStartMs,
  passEndMs,
  routeMinMs,
  routeMaxMs,
  routeLoading,
  routeError,
  route,
  editing,
  picking,
  onPickTarget,
  addBlockedReason,
  onRouteStartChange,
  onRouteEndChange,
  onRouteShift,
  onRouteRollChange,
  onRouteRollSet,
  onSelectPass,
  onModeChange,
  onAdd,
  onClose
}: ShootPanelProps) {
  return (
    <div className="tgu-editor-panel">
      <PanelHead title={editing ? "Редактирование съёмки" : "Новая съёмка"} onClose={onClose} />

      <section className="tgu-editor-panel__section">
        <div className="tgu-editor-panel__section-head">
          <div className="tgu-editor-eyebrow">1 · опорная точка прицеливания</div>
          {target && !picking && (
            <button className="tgu-link-button" type="button" onClick={onPickTarget}>
              сменить
            </button>
          )}
        </div>
        {target ? (
          <>
            <div className="tgu-editor-target">
              <i />
              <span>{target.name}</span>
              <small>
                {target.lat.toFixed(2)}, {target.lon.toFixed(2)}
              </small>
            </div>
            {picking && (
              <div className="tgu-editor-panel__hint">
                Кликайте по карте, чтобы переставить точку. Выберите проход ниже, когда точка верна.
              </div>
            )}
          </>
        ) : picking ? (
          <div className="tgu-editor-panel__placeholder">Кликните точку на карте слева, чтобы задать опорную точку прицеливания.</div>
        ) : (
          <button className="tgu-button" type="button" onClick={onPickTarget}>
            Указать точку на карте
          </button>
        )}
      </section>

      {target && (
        <section className="tgu-editor-panel__section">
          <div className="tgu-editor-eyebrow">2 · окно видимости</div>
          {loading ? (
            <div className="tgu-editor-panel__hint">Запрос параметров видимости…</div>
          ) : error ? (
            <div className="tgu-alert tgu-alert--danger tgu-alert--compact">{error}</div>
          ) : passes.length === 0 ? (
            <div className="tgu-editor-panel__hint">В окне планирования проходов над целью нет.</div>
          ) : (
            <div className="tgu-editor-pass-list">
              {passes.map((pass) => {
                const selected = selectedPass?.startMs === pass.startMs && selectedPass.endMs === pass.endMs;
                return (
                  <button
                    className={selected ? "is-selected" : ""}
                    key={`${pass.startMs}-${pass.endMs}`}
                    type="button"
                    onClick={() => onSelectPass(pass)}
                  >
                    <span>
                      {formatDate(pass.startMs)} {formatTime(pass.startMs)}
                      {pass.revolution != null && ` · виток ${pass.revolution}`}
                    </span>
                    <small>{formatVisibilityParams(pass)}</small>
                  </button>
                );
              })}
            </div>
          )}
        </section>
      )}

      {target && selectedPass && (
        <section className="tgu-editor-panel__section">
          <div className="tgu-editor-eyebrow">3 · режим съёмки</div>
          <div className="tgu-editor-chip-row">
            {modes.map((mode) => (
              <button className={modeId === mode.id ? "is-selected" : ""} key={mode.id} type="button" onClick={() => onModeChange(mode.id)}>
                {mode.label}
              </button>
            ))}
          </div>
        </section>
      )}

      {target && selectedPass && routeWindow && (
        <section className="tgu-editor-panel__section">
          <div className="tgu-editor-eyebrow">4 · окно маршрута</div>
          <RouteTimeStepper
            label="начало"
            value={routeWindow.startMs}
            min={routeMinMs}
            max={routeWindow.endMs - MIN_ROUTE_DURATION_MS}
            onChange={onRouteStartChange}
          />
          <RouteTimeStepper
            label="конец"
            value={routeWindow.endMs}
            min={routeWindow.startMs + MIN_ROUTE_DURATION_MS}
            max={routeMaxMs}
            onChange={onRouteEndChange}
          />
          <div className="tgu-editor-panel__hint">
            длительность: {formatDuration(routeWindow.endMs - routeWindow.startMs)}
            {passStartMs != null && passEndMs != null && ` · окно видимости: ${formatDuration(passEndMs - passStartMs)}`}
          </div>

          <div className="tgu-editor-time-stepper">
            <span>сдвиг — ◀ назад · вперёд ▶</span>
            <div className="tgu-editor-time-stepper__steps">
              <button type="button" onClick={() => onRouteShift(-30_000)}>−30с</button>
              <button type="button" onClick={() => onRouteShift(-5_000)}>−5с</button>
              <button type="button" onClick={() => onRouteShift(5_000)}>+5с</button>
              <button type="button" onClick={() => onRouteShift(30_000)}>+30с</button>
            </div>
          </div>

          <div className="tgu-editor-time-stepper">
            <span>крен — ◀ влево · вправо ▶</span>
            <div>
              <button type="button" onClick={() => onRouteRollChange(-5)}>−5°</button>
              <button type="button" onClick={() => onRouteRollChange(-1)}>−1°</button>
              <input
                type="number"
                step={0.5}
                value={routeRoll ?? 0}
                onChange={(event) => {
                  const deg = Number.parseFloat(event.target.value);
                  // Крен — не точнее одного знака после запятой.
                  if (Number.isFinite(deg)) onRouteRollSet(Math.round(deg * 10) / 10);
                }}
              />
              <button type="button" onClick={() => onRouteRollChange(1)}>+1°</button>
              <button type="button" onClick={() => onRouteRollChange(5)}>+5°</button>
            </div>
          </div>

          {routeLoading ? (
            <div className="tgu-editor-panel__hint">Построение маршрута…</div>
          ) : routeError ? (
            <div className="tgu-alert tgu-alert--danger tgu-alert--compact">{routeError}</div>
          ) : route ? (
            <div className="tgu-editor-panel__hint">
              Маршрут построен · виток {route.revolution} · крен {route.roll.toFixed(1)}° · захват {route.captureAngle.toFixed(1)}° · объём {formatVolume(route.volumeMb)}
              <br />
              центр {route.lat.toFixed(2)}, {route.longitude.toFixed(2)} · контур показан на карте
            </div>
          ) : null}
        </section>
      )}

      {addBlockedReason && <div className="tgu-alert tgu-alert--warning tgu-alert--compact">{addBlockedReason}</div>}

      <button
        className="tgu-button tgu-button--primary"
        type="button"
        onClick={onAdd}
        disabled={!target || !selectedPass || !modeId || !!addBlockedReason}
      >
        {editing ? "Сохранить съёмку" : "+ Добавить съёмку в план"}
      </button>
    </div>
  );
}

export function DownlinkPanel({ stationOptions, selected, onSelect, onAdd, onClose }: DownlinkPanelProps) {
  return (
    <div className="tgu-editor-panel">
      <PanelHead title="Новый сброс на НКПОР" onClose={onClose} />
      <div className="tgu-editor-panel__hint">Выберите станцию и сеанс связи для добавления сброса в черновик.</div>

      <div className="tgu-editor-station-list">
        {stationOptions.map((option) => (
          <div className="tgu-editor-station" key={option.station.id}>
            <div className="tgu-editor-station__head">
              <span>{option.station.name}</span>
              <small>{option.passes.length} сеанс.</small>
            </div>
            {option.passes.length === 0 ? (
              <div className="tgu-editor-panel__hint">нет сеансов в текущем окне</div>
            ) : (
              option.passes.slice(0, 5).map((pass) => {
                const isSelected = selected?.station.id === option.station.id && selected.pass.startMs === pass.startMs;
                return (
                  <button
                    className={isSelected ? "is-selected" : ""}
                    key={`${option.station.id}-${pass.startMs}`}
                    type="button"
                    onClick={() => onSelect({ station: option.station, pass })}
                  >
                    <span>{formatDateTime(pass.startMs)}-{formatTime(pass.endMs)}</span>
                    <small>{formatDuration(pass.endMs - pass.startMs)}</small>
                  </button>
                );
              })
            )}
          </div>
        ))}
      </div>

      <button className="tgu-button tgu-button--primary" type="button" onClick={onAdd} disabled={!selected}>
        + Добавить сброс в план
      </button>
    </div>
  );
}

export function DropPanel({
  zones,
  loading,
  error,
  selectedZone,
  onSelectZone,
  stationName,
  zoneStartMs,
  zoneStopMs,
  candidates,
  selectedSurveyIds,
  onToggleSurvey,
  saving,
  saveError,
  editing,
  addBlockedReason,
  onAdd,
  onClose
}: DropPanelProps) {
  const canAdd = !!selectedZone && selectedSurveyIds.size > 0 && !saving && !addBlockedReason;

  return (
    <div className="tgu-editor-panel">
      <PanelHead title={editing ? "Редактирование сброса" : "Новый сброс по ЗРВ"} onClose={onClose} />
      <div className="tgu-editor-panel__hint">
        Выберите зону радиовидимости станции, затем маршруты съёмки, которые успевают завершиться до начала зоны.
      </div>

      {loading && <div className="tgu-editor-panel__hint">Загрузка зон радиовидимости…</div>}
      {error && <div className="tgu-alert tgu-alert--danger">{error}</div>}
      {!loading && !error && zones.length === 0 && (
        <div className="tgu-editor-panel__hint">Нет зон радиовидимости в текущем окне.</div>
      )}

      {zones.length > 0 && (
        <div className="tgu-editor-station-list">
          {zones.map((zone) => {
            const startMs = zoneStartMs(zone);
            const stopMs = zoneStopMs(zone);
            const isSelected =
              !!selectedZone &&
              selectedZone.stationId === zone.stationId &&
              selectedZone.revolution === zone.revolution &&
              selectedZone.onStart.time === zone.onStart.time;
            return (
              <button
                className={isSelected ? "is-selected" : ""}
                key={`${zone.stationId}-${zone.revolution}-${zone.onStart.time}`}
                type="button"
                onClick={() => onSelectZone(isSelected ? undefined : zone)}
              >
                <span>{stationName(zone.stationId)} · виток {zone.revolution}</span>
                <small>
                  {formatDateTime(startMs)}–{formatTime(stopMs)} · {formatDuration(stopMs - startMs)}
                </small>
              </button>
            );
          })}
        </div>
      )}

      {selectedZone && (
        <section className="tgu-editor-panel__section">
          <div className="tgu-editor-eyebrow">Съёмки для сброса</div>
          {candidates.length === 0 ? (
            <div className="tgu-editor-panel__hint">Нет съёмок, завершающихся до начала выбранной зоны.</div>
          ) : (
            <div className="tgu-editor-station-list">
              {candidates.map((survey) => {
                const checked = selectedSurveyIds.has(survey.id);
                return (
                  <button
                    className={checked ? "is-selected" : ""}
                    key={survey.id}
                    type="button"
                    onClick={() => onToggleSurvey(survey.id)}
                  >
                    <span>{checked ? "☑" : "☐"} {survey.label}</span>
                    <small>
                      {formatDateTime(survey.startMs)}–{formatTime(survey.endMs)}
                    </small>
                  </button>
                );
              })}
            </div>
          )}
        </section>
      )}

      {addBlockedReason && <div className="tgu-alert tgu-alert--warning tgu-alert--compact">{addBlockedReason}</div>}
      {saveError && <div className="tgu-alert tgu-alert--danger">{saveError}</div>}

      <button className="tgu-button tgu-button--primary" type="button" onClick={onAdd} disabled={!canAdd}>
        {saving ? "Сохранение…" : editing ? "Сохранить сброс" : "+ Добавить сброс в план"}
      </button>
    </div>
  );
}

type MapInfoPanelProps = {
  selection: Tgu2DMapSelection;
  requestItems: RequestMapItem[];
  onClose: () => void;
};

export function MapInfoPanel({ selection, requestItems, onClose }: MapInfoPanelProps) {
  const title = mapInfoTitle(selection);

  const requestItem =
    selection.type === "polygon" && selection.layer === "requests" && selection.requestId
      ? requestItems.find((r) => r.id === selection.requestId)
      : undefined;

  return (
    <div className="tgu-editor-panel">
      <PanelHead title={title} onClose={onClose} />

      {selection.type === "marker" && selection.layer === "stations" && (
        <section className="tgu-editor-panel__section">
          <div className="tgu-editor-eyebrow">{selection.name}</div>
          <dl className="tgu-map-request-details__grid">
            <dt>Номер</dt>
            <dd className="mono">{selection.stationNumber ?? "—"}</dd>
            <dt>Широта</dt>
            <dd className="mono">{selection.lat != null ? `${selection.lat.toFixed(4)}°` : "—"}</dd>
            <dt>Долгота</dt>
            <dd className="mono">{selection.lon != null ? `${selection.lon.toFixed(4)}°` : "—"}</dd>
            <dt>Высота, м</dt>
            <dd className="mono">{selection.altitude != null ? selection.altitude.toFixed(1) : "—"}</dd>
            <dt>Угол места мин, °</dt>
            <dd className="mono">{selection.elevationMin != null ? selection.elevationMin.toFixed(1) : "—"}</dd>
            <dt>Угол места макс, °</dt>
            <dd className="mono">{selection.elevationMax != null ? selection.elevationMax.toFixed(1) : "—"}</dd>
          </dl>
        </section>
      )}

      {selection.type === "polygon" && selection.layer === "requests" && (
        requestItem ? (
          <section className="tgu-editor-panel__section">
            <dl className="tgu-map-request-details__grid">
              <dt>ID</dt>
              <dd className="mono">{requestItem.id}</dd>
              <dt>Название</dt>
              <dd>{requestItem.name || "—"}</dd>
              <dt>Статус</dt>
              <dd>{requestItem.status}</dd>
              <dt>Тип съёмки</dt>
              <dd>{surveyTypeLabel(requestItem.surveyType)}</dd>
              <dt>Начало</dt>
              <dd className="mono">{formatIso(requestItem.beginDateTime)}</dd>
              <dt>Конец</dt>
              <dd className="mono">{formatIso(requestItem.endDateTime)}</dd>
              <dt>Важность</dt>
              <dd className="mono">{requestItem.importance}</dd>
              <dt>Покрытие</dt>
              <dd className="mono">{requestItem.coveragePercent != null ? `${requestItem.coveragePercent.toFixed(1)} %` : "—"}</dd>
              <dt>КПП</dt>
              <dd className="mono">{requestItem.kpp?.join(", ") || "—"}</dd>
              <dt>Приоритет передачи</dt>
              <dd>{requestItem.highPriorityTransmit ? "Да" : "Нет"}</dd>
            </dl>
          </section>
        ) : (
          <div className="tgu-editor-panel__hint">{selection.name || selection.id}</div>
        )
      )}

      {selection.type === "polygon" && selection.layer === "planWorks" && (
        <section className="tgu-editor-panel__section">
          <div className="tgu-editor-eyebrow">{selection.name}</div>
          {selection.status && (
            <span className="tgu-map-status-pill">
              <i style={{ backgroundColor: statusStyle(selection.status).color }} />
              {statusStyle(selection.status).label}
            </span>
          )}
        </section>
      )}

      {selection.type === "polygon" && selection.layer === "swath" && (
        <section className="tgu-editor-panel__section">
          <div className="tgu-editor-eyebrow">{selection.name}</div>
          <dl className="tgu-map-request-details__grid">
            {selection.spacecraftId && (<><dt>КА</dt><dd className="mono">{selection.spacecraftId}</dd></>)}
            <dt>Виток</dt>
            <dd className="mono">{selection.revolution ?? "—"}</dd>
          </dl>
        </section>
      )}
    </div>
  );
}

type MapPickPanelProps = {
  /** Объекты под точкой клика на карте. */
  hits: Tgu2DMapSelection[];
  /** id раскрытого объекта (показывается его детальная карточка). */
  activeId?: string;
  onToggle: (selection: Tgu2DMapSelection) => void;
  onClose: () => void;
  /** Детальная карточка объекта (инспектор включения или карточка объекта карты). */
  renderDetail: (selection: Tgu2DMapSelection) => ReactNode;
};

/**
 * Список объектов под точкой клика на карте: оператор раскрывает нужный, и под ним показывается
 * его детальная карточка (renderDetail). Заменяет всплывающее меню выбора у карты.
 */
export function MapPickPanel({ hits, activeId, onToggle, onClose, renderDetail }: MapPickPanelProps) {
  return (
    <div className="tgu-editor-panel">
      <PanelHead title={`Объекты под курсором · ${hits.length}`} onClose={onClose} />
      <div className="tgu-editor-panel__hint">Кликните объект в списке, чтобы раскрыть его данные.</div>
      <div className="tgu-editor-pick">
        {hits.map((hit, index) => {
          const open = hit.id === activeId;
          return (
            <div className={`tgu-editor-pick__item${open ? " is-open" : ""}`} key={`${hit.id}:${index}`}>
              <button type="button" className="tgu-editor-pick__row" onClick={() => onToggle(hit)}>
                <i className={`tgu-editor-pick__swatch tgu-editor-pick__swatch--${selectionSwatch(hit)}`} />
                <span className="tgu-editor-pick__title">{hit.name || selectionSubtitle(hit)}</span>
                <small className="tgu-editor-pick__sub">{selectionSubtitle(hit)}</small>
                <span className="tgu-editor-pick__chevron" aria-hidden="true">{open ? "▾" : "▸"}</span>
              </button>
              {open && <div className="tgu-editor-pick__detail">{renderDetail(hit)}</div>}
            </div>
          );
        })}
      </div>
    </div>
  );
}

/** Класс цветовой метки объекта в списке выбора. */
function selectionSwatch(selection: Tgu2DMapSelection): string {
  if (selection.type === "marker") return "station";
  if (selection.type === "line") return "drop";
  return selection.layer;
}

/** Подпись типа объекта в списке выбора. */
function selectionSubtitle(selection: Tgu2DMapSelection): string {
  if (selection.type === "marker") {
    return selection.layer === "stations" ? "Станция" : "Маркер КА";
  }
  if (selection.type === "line") {
    return selection.kind === "drop" ? "Сброс на НКПОР" : "Трасса КА";
  }
  switch (selection.layer) {
    case "requests": return "Заявка на съёмку";
    case "planWorks": return "Маршрут съёмки";
    case "swath": return "Полоса обзора";
    case "tracks": return "Трасса КА";
    default: return "Объект карты";
  }
}

function mapInfoTitle(selection: Tgu2DMapSelection): string {
  if (selection.type === "marker") {
    return selection.layer === "stations" ? "Станция" : "Маркер КА";
  }
  switch (selection.layer) {
    case "requests": return "Заявка на съёмку";
    case "planWorks": return "Работа плана";
    case "swath": return "Полоса обзора";
    case "tracks": return "Трасса КА";
    default: return "Объект карты";
  }
}

function formatVisibilityParams(pass: EditorVisibilityWindow): string {
  const parts: string[] = [formatDuration(pass.endMs - pass.startMs)];
  if (pass.rollDeg != null) parts.push(`крен ${pass.rollDeg.toFixed(1)}°`);
  if (pass.sunAngleDeg != null) parts.push(`☉ ${pass.sunAngleDeg.toFixed(1)}°`);
  if (pass.range != null) {
    const km = pass.range > 100000 ? pass.range / 1000 : pass.range;
    parts.push(`${km.toFixed(0)} км`);
  }
  return parts.join(" · ");
}

function surveyTypeLabel(surveyType: string): string {
  switch (surveyType) {
    case "OPTICS": return "Оптическая";
    case "RSA": return "Радиолокационная (РСА)";
    case "COMBINED": return "Комбинированная";
    default: return surveyType;
  }
}

function formatIso(iso: string): string {
  return formatLocalDateTime(parseLocal(iso));
}

function PanelHead({ title, onClose }: { title: string; onClose: () => void }) {
  return (
    <div className="tgu-editor-panel__head">
      <h2>{title}</h2>
      <button className="tgu-icon-button" type="button" onClick={onClose} title="Закрыть">
        ×
      </button>
    </div>
  );
}

/**
 * Степпер времени окна маршрута с секундной точностью: кнопки ±5 c / ±30 c и ручной
 * ввод времени `чч:мм:сс` (локальное, без даты — дата берётся из текущего значения).
 * Значение зажимается в [min, max].
 */
function RouteTimeStepper({
  label,
  value,
  min,
  max,
  onChange
}: {
  label: string;
  value: number;
  min?: number;
  max?: number;
  onChange: (value: number) => void;
}) {
  const clamp = (next: number) => Math.max(min ?? Number.NEGATIVE_INFINITY, Math.min(max ?? Number.POSITIVE_INFINITY, next));
  const change = (deltaMs: number) => onChange(clamp(value + deltaMs));
  const onTimeChange = (raw: string) => {
    // Ввод — только время суток (ЧЧ:ММ:СС, локальное); день оставляем прежним.
    const datePart = toLocalIso(value).slice(0, 10);
    const ms = parseLocal(`${datePart}T${raw}`);
    if (Number.isFinite(ms)) onChange(clamp(ms));
  };

  return (
    <label className="tgu-editor-time-stepper tgu-editor-time-stepper--seconds">
      <span>{label}</span>
      <Time24Field withSeconds value={toLocalIso(value).slice(11, 19)} onChange={onTimeChange} />
      <div className="tgu-editor-time-stepper__steps">
        <button type="button" onClick={() => change(-30_000)}>−30с</button>
        <button type="button" onClick={() => change(-5_000)}>−5с</button>
        <button type="button" onClick={() => change(5_000)}>+5с</button>
        <button type="button" onClick={() => change(30_000)}>+30с</button>
      </div>
    </label>
  );
}
