(() => {
    const state = {
        satellites: [],
        filteredSatellites: [],
        missions: [],
        currentModes: [],
        bookedSlotsById: {},
        selectedSatelliteId: null,
        selectedMissionId: null,
        selectedMission: null,
        missionsPage: 0,
        missionsSize: 4,
        missionsTotalPages: 0,
        missionsTotalElements: 0
    };

    const el = (id) => document.getElementById(id);

    document.addEventListener('DOMContentLoaded', () => {
        bindEvents();
        loadSatellites();
    });

    function bindEvents() {
        el('current-plans-refresh')?.addEventListener('click', () => reloadAll());
        el('current-plans-satellite-filter')?.addEventListener('input', () => applySatelliteFilter());
        el('current-plans-missions-prev')?.addEventListener('click', () => changeMissionPage(-1));
        el('current-plans-missions-next')?.addEventListener('click', () => changeMissionPage(1));
        el('current-plans-create-mission')?.addEventListener('click', () => openCreateMissionDialog());
        el('current-plans-create-form')?.addEventListener('submit', (event) => createMission(event));
        el('current-plans-export-csv')?.addEventListener('click', () => exportSelectedMissionCsv());
    }

    function reloadAll() {
        if (state.selectedSatelliteId) {
            loadMissions(state.selectedSatelliteId, state.missionsPage, state.selectedMissionId);
        } else {
            loadSatellites();
        }
    }

    async function loadSatellites() {
        showAlert('');
        setCreateMissionEnabled(false);
        clearMissions('Выберите спутник для загрузки миссий.');
        clearModes('Режимы появятся после выбора миссии.');
        setTableLoading('current-plans-satellites-body', 3, 'Загрузка спутников...');

        try {
            const satellites = await fetchJson('/api/current-plans/satellites');
            state.satellites = Array.isArray(satellites) ? satellites : [];
            applySatelliteFilter();
        } catch (error) {
            state.satellites = [];
            state.filteredSatellites = [];
            renderSatellites();
            showAlert(error.message || 'Не удалось загрузить список спутников.', 'danger');
        }
    }

    function applySatelliteFilter() {
        const query = (el('current-plans-satellite-filter')?.value || '').trim().toLowerCase();
        state.filteredSatellites = state.satellites.filter((satellite) => {
            const text = [
                satellite.id,
                satellite.noradId,
                satellite.code,
                satellite.name,
                satellite.typeCode
            ].filter((value) => value !== null && value !== undefined)
                .join(' ')
                .toLowerCase();
            return !query || text.includes(query);
        });
        renderSatellites();
    }

    function renderSatellites() {
        const tbody = el('current-plans-satellites-body');
        if (!tbody) return;

        if (!state.filteredSatellites.length) {
            tbody.innerHTML = emptyRow(3, 'Спутники не найдены.');
            return;
        }

        tbody.innerHTML = state.filteredSatellites.map((satellite) => {
            const selected = Number(satellite.id) === Number(state.selectedSatelliteId) ? ' current-plans-selected-row' : '';
            const title = escapeHtml(satellite.name || satellite.code || `КА ${satellite.id}`);
            const code = escapeHtml(satellite.code || '—');
            const norad = satellite.noradId ? `NORAD ${escapeHtml(satellite.noradId)}` : 'NORAD —';
            return `
                <tr class="${selected}" data-satellite-id="${escapeHtml(satellite.id)}">
                    <td class="current-plans-id">${escapeHtml(satellite.id)}</td>
                    <td>
                        <div class="current-plans-main-text">${title}</div>
                        <div class="current-plans-sub-text">${code} · ${norad}</div>
                    </td>
                    <td>${escapeHtml(satellite.typeCode || '—')}</td>
                </tr>`;
        }).join('');

        tbody.querySelectorAll('tr[data-satellite-id]').forEach((row) => {
            row.addEventListener('click', () => selectSatellite(Number(row.dataset.satelliteId)));
        });
    }

    function selectSatellite(satelliteId) {
        state.selectedSatelliteId = satelliteId;
        state.selectedMissionId = null;
        state.selectedMission = null;
        state.missionsPage = 0;
        renderSatellites();
        const satellite = selectedSatellite();
        el('current-plans-selected-satellite').textContent = satelliteLabel(satellite);
        setCreateMissionEnabled(true);
        clearModes('Выберите миссию в верхней таблице.');
        loadMissions(satelliteId, 0);
    }

    async function loadMissions(satelliteId, page, missionIdToSelect = null) {
        setTableLoading('current-plans-missions-body', 6, 'Загрузка миссий...');
        el('current-plans-missions-empty')?.classList.add('d-none');
        el('current-plans-missions-wrap')?.classList.remove('d-none');
        setMissionPaging(false);

        try {
            const data = await fetchJson(`/api/current-plans/satellites/${encodeURIComponent(satelliteId)}/missions?page=${page}&size=${state.missionsSize}`);
            state.missionsPage = data.page || 0;
            state.missionsTotalPages = data.totalPages || 0;
            state.missionsTotalElements = data.totalElements || 0;
            state.missions = data.items || [];
            renderMissions(state.missions);
            updateMissionPaging();
            if (missionIdToSelect && state.missions.some((mission) => mission.missionId === missionIdToSelect)) {
                selectMission(missionIdToSelect);
            }
        } catch (error) {
            clearMissions('Не удалось загрузить миссии выбранного спутника.');
            showAlert(error.message || 'Не удалось загрузить миссии выбранного спутника.', 'danger');
        }
    }

    function renderMissions(missions) {
        const tbody = el('current-plans-missions-body');
        if (!tbody) return;

        if (!missions.length) {
            tbody.innerHTML = emptyRow(6, 'Для выбранного спутника миссии не найдены.');
            clearModes('Выберите миссию в верхней таблице.');
            return;
        }

        tbody.innerHTML = missions.map((mission) => {
            const selected = mission.missionId === state.selectedMissionId ? ' current-plans-selected-row' : '';
            return `
                <tr class="${selected}" data-mission-id="${escapeHtml(mission.missionId)}">
                    <td class="current-plans-id">${shortUuid(mission.missionId)}</td>
                    <td>${formatDateTime(mission.missionStart)}</td>
                    <td>${formatDateTime(mission.missionStop)}</td>
                    <td>${escapeHtml(mission.station || '—')}</td>
                    <td><span class="current-plans-badge">${escapeHtml(mission.status || '—')}</span></td>
                    <td class="current-plans-actions-cell">
                        <button type="button"
                                class="btn btn-outline-primary btn-sm current-plans-action-btn"
                                data-action="calculate-surveys"
                                data-mission-id="${escapeHtml(mission.missionId)}">Расчёт съёмки</button>
                        <button type="button"
                                class="btn btn-outline-success btn-sm current-plans-action-btn"
                                data-action="calculate-drops"
                                data-mission-id="${escapeHtml(mission.missionId)}">Расчёт сбросов</button>
                        <button type="button"
                                class="btn btn-success btn-sm current-plans-action-btn"
                                data-action="confirm-mission"
                                data-mission-id="${escapeHtml(mission.missionId)}">Утвердить</button>
                    </td>
                </tr>`;
        }).join('');

        tbody.querySelectorAll('tr[data-mission-id]').forEach((row) => {
            row.addEventListener('click', () => selectMission(row.dataset.missionId));
        });
        tbody.querySelectorAll('button[data-action]').forEach((button) => {
            button.addEventListener('click', (event) => {
                event.preventDefault();
                event.stopPropagation();
                const missionId = button.dataset.missionId;
                if (button.dataset.action === 'calculate-surveys') {
                    calculateSurveys(missionId, button);
                } else if (button.dataset.action === 'calculate-drops') {
                    calculateDrops(missionId, button);
                } else if (button.dataset.action === 'confirm-mission') {
                    confirmMission(missionId, button);
                }
            });
        });
    }

    function selectMission(missionId) {
        state.selectedMissionId = missionId;
        state.selectedMission = state.missions.find((mission) => mission.missionId === missionId) || null;
        el('current-plans-selected-mission').textContent = missionCaption(state.selectedMission, missionId);
        el('current-plans-missions-body')?.querySelectorAll('tr').forEach((row) => {
            row.classList.toggle('current-plans-selected-row', row.dataset.missionId === missionId);
        });
        loadModes(missionId);
    }

    async function loadModes(missionId) {
        setTableLoading('current-plans-modes-body', 6, 'Загрузка режимов...');
        el('current-plans-modes-empty')?.classList.add('d-none');
        el('current-plans-modes-wrap')?.classList.remove('d-none');
        setExportEnabled(false);

        try {
            const modes = await fetchJson(`/api/current-plans/missions/${encodeURIComponent(missionId)}/modes`);
            state.currentModes = Array.isArray(modes) ? modes : [];
            state.bookedSlotsById = await loadBookedSlotsForModes(state.currentModes);
            renderModes(state.currentModes);
            setExportEnabled(true);
            return state.currentModes;
        } catch (error) {
            clearModes('Не удалось загрузить режимы выбранной миссии.');
            showAlert(error.message || 'Не удалось загрузить режимы выбранной миссии.', 'danger');
            return [];
        }
    }

    async function loadBookedSlotsForModes(modes) {
        const bookedSlotIds = [...new Set((modes || []).flatMap((mode) => mode.bookedSlotIds || []))]
            .filter((slotId) => Number(slotId) > 0);
        if (!bookedSlotIds.length) {
            return {};
        }

        const params = new URLSearchParams();
        bookedSlotIds.forEach((slotId) => params.append('ids', slotId));

        try {
            const bookedSlots = await fetchJson(`/api/current-plans/booked-slots?${params.toString()}`);
            return (bookedSlots || []).reduce((acc, slot) => {
                acc[slot.bookedSlotId] = slot;
                return acc;
            }, {});
        } catch (error) {
            showAlert(error.message || 'Не удалось загрузить детали забронированных слотов.', 'warning');
            return {};
        }
    }

    function renderModes(modes) {
        const tbody = el('current-plans-modes-body');
        if (!tbody) return;

        if (!modes.length) {
            tbody.innerHTML = emptyRow(6, 'В выбранной миссии нет режимов работы.');
            return;
        }

        tbody.innerHTML = modes.flatMap((mode) => {
            const type = mode.type || '—';
            const duration = formatNumber(mode.duration);
            const rowClass = modeRowClass(mode);
            const badgeClass = modeBadgeClass(mode);
            const expandable = isExpandableMode(mode);
            const expandMarker = expandable ? '<span class="current-plans-expand-marker">▸</span>' : '';
            const row = `
                <tr class="${rowClass}${expandable ? ' current-plans-expandable-row' : ''}" data-mode-id="${escapeHtml(mode.id ?? '')}">
                    <td>${expandMarker}<span class="current-plans-badge ${badgeClass}">${escapeHtml(type)}</span></td>
                    <td>${formatDateTime(mode.timeStart)}</td>
                    <td>${escapeHtml(mode.revolution ?? '—')}</td>
                    <td>${duration}</td>
                    <td class="current-plans-mode-params">${modeParams(mode)}</td>
                    <td>${modeStatus(mode)}</td>
                </tr>`;
            const details = expandable ? modeDetailsRow(mode) : null;
            return details ? [row, details] : [row];
        }).join('');

        tbody.querySelectorAll('.current-plans-expandable-row').forEach((row) => {
            row.addEventListener('click', () => toggleModeDetails(row.dataset.modeId));
        });
    }

    function modeRowClass(mode) {
        if (mode.type === 'DROP') return 'current-plans-mode-row-drop';
        if (isSlotsLikeSource(mode.source)) return 'current-plans-mode-row-slots';
        return '';
    }

    function modeBadgeClass(mode) {
        if (mode.type === 'DROP') return 'current-plans-badge-drop';
        if (isSlotsLikeSource(mode.source)) return 'current-plans-badge-slots';
        return '';
    }

    function isSlotsLikeSource(source) {
        return source === 'SLOTS' || source === 'MIXED';
    }

    function isExpandableMode(mode) {
        return (Array.isArray(mode.bookedSlotIds) && mode.bookedSlotIds.length > 0)
            || (mode.type === 'DROP' && Array.isArray(mode.surveys) && mode.surveys.length > 0);
    }

    function toggleModeDetails(modeId) {
        const row = document.querySelector(`#current-plans-modes-body tr[data-mode-id="${cssEscape(modeId)}"]`);
        const details = document.querySelector(`#current-plans-modes-body tr[data-parent-mode-id="${cssEscape(modeId)}"]`);
        if (!row || !details) return;

        const collapsed = details.classList.toggle('d-none');
        row.classList.toggle('current-plans-expanded-row', !collapsed);
        const marker = row.querySelector('.current-plans-expand-marker');
        if (marker) marker.textContent = collapsed ? '▸' : '▾';
    }

    function modeDetailsRow(mode) {
        const content = mode.type === 'DROP'
            ? dropDetails(mode)
            : bookedSlotDetails(mode);

        return `
            <tr class="current-plans-mode-details-row d-none" data-parent-mode-id="${escapeHtml(mode.id ?? '')}">
                <td colspan="6">
                    <div class="current-plans-mode-details">${content}</div>
                </td>
            </tr>`;
    }

    function bookedSlotDetails(mode) {
        const slotIds = Array.isArray(mode.bookedSlotIds) ? mode.bookedSlotIds : [];
        const rows = slotIds.map((slotId) => {
            const slot = state.bookedSlotsById[slotId];
            if (!slot) {
                return `<tr><td class="current-plans-id">#${escapeHtml(slotId)}</td><td colspan="6">Детали слота не найдены</td></tr>`;
            }
            return `
                <tr>
                    <td class="current-plans-id">#${escapeHtml(slot.bookedSlotId)}</td>
                    <td>${formatDateTime(slot.timeStart)}</td>
                    <td>${formatDuration(slot.durationSeconds)}</td>
                    <td>${escapeHtml(slot.slotNum ?? '—')}</td>
                    <td>${escapeHtml(slot.cycle ?? '—')}</td>
                    <td>${escapeHtml(slot.satelliteId ?? '—')}</td>
                    <td>${escapeHtml(slot.status || '—')}</td>
                </tr>`;
        }).join('');

        return `
            <div class="current-plans-detail-title">Забронированные слоты (${slotIds.length})</div>
            <div class="table-responsive">
                <table class="table table-sm mb-0 current-plans-detail-table">
                    <thead>
                    <tr>
                        <th>ID</th>
                        <th>Начало</th>
                        <th>Длительность</th>
                        <th>Слот</th>
                        <th>Цикл</th>
                        <th>Спутник</th>
                        <th>Статус</th>
                    </tr>
                    </thead>
                    <tbody>${rows}</tbody>
                </table>
            </div>`;
    }

    function dropDetails(mode) {
        const surveyIds = Array.isArray(mode.surveys) ? mode.surveys : [];
        const surveysById = new Map((state.currentModes || []).map((item) => [Number(item.id), item]));
        const rows = surveyIds.map((surveyId) => {
            const survey = surveysById.get(Number(surveyId));
            if (!survey) {
                return `<tr><td class="current-plans-id">#${escapeHtml(surveyId)}</td><td colspan="4">Съёмка не найдена в текущем списке режимов</td></tr>`;
            }

            const booked = Array.isArray(survey.bookedSlotIds) && survey.bookedSlotIds.length
                ? survey.bookedSlotIds.map((slotId) => {
                    const slot = state.bookedSlotsById[slotId];
                    return slot ? `#${slot.bookedSlotId} slot ${slot.slotNum}/cycle ${slot.cycle}` : `#${slotId}`;
                }).join(', ')
                : '—';
            return `
                <tr>
                    <td class="current-plans-id">#${escapeHtml(survey.id)}</td>
                    <td>${formatDateTime(survey.timeStart)}</td>
                    <td>${escapeHtml(survey.revolution ?? '—')}</td>
                    <td>${escapeHtml(survey.source || '—')}</td>
                    <td>${escapeHtml(booked)}</td>
                </tr>`;
        }).join('');

        return `
            <div class="current-plans-detail-title">Сбрасываемые съёмки (${surveyIds.length})</div>
            <div class="table-responsive">
                <table class="table table-sm mb-0 current-plans-detail-table">
                    <thead>
                    <tr>
                        <th>ID</th>
                        <th>Начало</th>
                        <th>Виток</th>
                        <th>Источник</th>
                        <th>Booked slots</th>
                    </tr>
                    </thead>
                    <tbody>${rows}</tbody>
                </table>
            </div>`;
    }

    function modeParams(mode) {
        if (mode.type === 'DROP') {
            const surveys = Array.isArray(mode.surveys) ? mode.surveys.length : 0;
            return `
                <div>Станция: ${escapeHtml(mode.station || '—')}</div>
                <div class="current-plans-sub-text">Съёмок: ${surveys}</div>`;
        }

        const booked = Array.isArray(mode.bookedSlotIds) && mode.bookedSlotIds.length
            ? ` · slots: ${mode.bookedSlotIds.length}`
            : '';
        return `
            <div>Крен: ${formatNumber(mode.roll)}°</div>
            <div class="current-plans-sub-text">lat ${formatNumber(mode.lat)}, lon ${formatNumber(mode.longitude)}${booked}</div>`;
    }

    function modeStatus(mode) {
        if (mode.type === 'DROP') {
            return '<span class="current-plans-badge current-plans-badge-drop">DROP</span>';
        }
        const sourceBadgeClass = isSlotsLikeSource(mode.source) ? ' current-plans-badge-slots' : '';
        return `
            <div><span class="current-plans-badge">${escapeHtml(mode.status || '—')}</span></div>
            <div><span class="current-plans-badge${sourceBadgeClass}">${escapeHtml(mode.source || '—')}</span></div>`;
    }

    function openCreateMissionDialog() {
        if (!state.selectedSatelliteId) {
            showAlert('Сначала выберите спутник.', 'warning');
            return;
        }
        const satellite = selectedSatellite();
        el('current-plans-create-satellite').value = satelliteLabel(satellite);
        const startInput = el('current-plans-create-start');
        const stopInput = el('current-plans-create-stop');
        if (startInput && !startInput.value) startInput.value = toDateTimeLocalValue(new Date());
        if (stopInput && !stopInput.value) stopInput.value = toDateTimeLocalValue(new Date(Date.now() + 24 * 60 * 60 * 1000));
        const modalElement = el('current-plans-create-modal');
        if (window.bootstrap && modalElement) {
            window.bootstrap.Modal.getOrCreateInstance(modalElement).show();
        }
    }

    async function createMission(event) {
        event.preventDefault();
        if (!state.selectedSatelliteId) {
            showAlert('Сначала выберите спутник.', 'warning');
            return;
        }

        const missionStart = el('current-plans-create-start')?.value;
        const missionStop = el('current-plans-create-stop')?.value;
        if (!missionStart || !missionStop) {
            showAlert('Укажите начало и конец миссии.', 'warning');
            return;
        }
        if (new Date(missionStart).getTime() > new Date(missionStop).getTime()) {
            showAlert('Время начала миссии должно быть меньше или равно времени окончания.', 'warning');
            return;
        }

        const button = el('current-plans-create-submit');
        setButtonLoading(button, true, 'Создание...');
        const body = {
            satelliteId: Number(state.selectedSatelliteId),
            station: trimToNull(el('current-plans-create-station')?.value),
            missionStart: PcpUtcTime.toUtcIsoFromInput(missionStart),
            missionStop: PcpUtcTime.toUtcIsoFromInput(missionStop),
            status: trimToNull(el('current-plans-create-status')?.value) || 'NEW'
        };

        try {
            const mission = await fetchJson('/api/current-plans/missions', {
                method: 'POST',
                headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},
                body: JSON.stringify(body)
            });
            closeCreateMissionDialog();
            showAlert(`План создан: ${mission?.missionId || ''}`, 'success');
            await loadMissions(state.selectedSatelliteId, 0, mission?.missionId);
        } catch (error) {
            showAlert(error.message || 'Не удалось создать план.', 'danger');
        } finally {
            setButtonLoading(button, false);
        }
    }

    async function calculateSurveys(missionId, button) {
        setButtonLoading(button, true, 'Расчёт...');
        try {
            const result = await fetchJson(`/api/current-plans/missions/${encodeURIComponent(missionId)}/surveys/calculate`, {
                method: 'POST',
                headers: {'Accept': 'application/json'}
            });
            showAlert(surveyCalculationMessage(result), 'success');
            if (state.selectedMissionId === missionId) {
                await loadModes(missionId);
            }
        } catch (error) {
            showAlert(error.message || 'Не удалось рассчитать съёмку.', 'danger');
        } finally {
            setButtonLoading(button, false);
        }
    }

    async function calculateDrops(missionId, button) {
        setButtonLoading(button, true, 'Расчёт...');
        try {
            await fetchJson(`/api/current-plans/missions/${encodeURIComponent(missionId)}/drops/calculate`, {
                method: 'POST',
                headers: {'Accept': 'application/json'}
            });
            const modes = state.selectedMissionId === missionId
                ? await loadModes(missionId)
                : await fetchJson(`/api/current-plans/missions/${encodeURIComponent(missionId)}/modes`);
            showAlert(`Расчёт сбросов выполнен. ${modeCountsMessage(Array.isArray(modes) ? modes : [])}`, 'success');
        } catch (error) {
            showAlert(error.message || 'Не удалось рассчитать сбросы.', 'danger');
        } finally {
            setButtonLoading(button, false);
        }
    }

    async function confirmMission(missionId, button) {
        setButtonLoading(button, true, 'Утверждение...');
        try {
            await fetchJson(`/api/current-plans/missions/${encodeURIComponent(missionId)}/confirm`, {
                method: 'POST',
                headers: {'Accept': 'application/json'}
            });
            showAlert('План утверждён.', 'success');
            if (state.selectedSatelliteId) {
                await loadMissions(state.selectedSatelliteId, state.missionsPage, missionId);
            }
        } catch (error) {
            showAlert(error.message || 'Не удалось утвердить план.', 'danger');
        } finally {
            setButtonLoading(button, false);
        }
    }

    function exportSelectedMissionCsv() {
        if (!state.selectedMissionId) {
            showAlert('Выберите миссию для сохранения CSV.', 'warning');
            return;
        }
        const mission = state.selectedMission || state.missions.find((item) => item.missionId === state.selectedMissionId);
        const satellite = selectedSatellite();
        const modes = state.currentModes || [];
        const csv = buildMissionCsv(mission, satellite, modes);
        const filename = `mission-${state.selectedMissionId}-modes.csv`;
        downloadTextFile(filename, csv, 'text/csv;charset=utf-8;');
    }

    function buildMissionCsv(mission, satellite, modes) {
        const lines = [];
        lines.push(['Миссия', mission?.missionId || state.selectedMissionId].map(csvCell).join(';'));
        lines.push(['Спутник ID', mission?.satelliteId || state.selectedSatelliteId].map(csvCell).join(';'));
        lines.push(['Спутник', satelliteLabel(satellite)].map(csvCell).join(';'));
        lines.push(['Начало миссии', mission?.missionStart || ''].map(csvCell).join(';'));
        lines.push(['Конец миссии', mission?.missionStop || ''].map(csvCell).join(';'));
        lines.push(['Станция', mission?.station || ''].map(csvCell).join(';'));
        lines.push(['Статус', mission?.status || ''].map(csvCell).join(';'));
        lines.push(['Количество режимов', modes.length].map(csvCell).join(';'));
        lines.push(['Состав режимов', modeCountsPlain(modes)].map(csvCell).join(';'));
        lines.push('');
        lines.push(['Тип', 'ID', 'Plan ID', 'Начало', 'Виток', 'Длительность, с', 'Статус', 'Источник', 'Крен', 'Широта', 'Долгота', 'Станция', 'Связанные съёмки', 'Booked slots'].map(csvCell).join(';'));
        modes.forEach((mode) => {
            lines.push([
                mode.type || '',
                mode.id ?? '',
                mode.planId ?? '',
                formatDateTime(mode.timeStart),
                mode.revolution ?? '',
                mode.duration ?? '',
                mode.status || '',
                mode.source || '',
                mode.roll ?? '',
                mode.lat ?? '',
                mode.longitude ?? '',
                mode.station || '',
                Array.isArray(mode.surveys) ? mode.surveys.join('|') : '',
                Array.isArray(mode.bookedSlotIds) ? mode.bookedSlotIds.join('|') : ''
            ].map(csvCell).join(';'));
        });
        return '\ufeff' + lines.join('\r\n');
    }

    function changeMissionPage(delta) {
        if (!state.selectedSatelliteId) return;
        const next = state.missionsPage + delta;
        if (next < 0 || (state.missionsTotalPages > 0 && next >= state.missionsTotalPages)) return;
        state.selectedMissionId = null;
        state.selectedMission = null;
        clearModes('Выберите миссию в верхней таблице.');
        loadMissions(state.selectedSatelliteId, next);
    }

    function updateMissionPaging() {
        const pageText = state.missionsTotalPages > 0
            ? `${state.missionsPage + 1} / ${state.missionsTotalPages}`
            : '0 / 0';
        el('current-plans-missions-page').textContent = `${pageText} · ${state.missionsTotalElements}`;
        setMissionPaging(true);
    }

    function setMissionPaging(enabled) {
        const hasPrev = enabled && state.missionsPage > 0;
        const hasNext = enabled && state.missionsTotalPages > 0 && state.missionsPage < state.missionsTotalPages - 1;
        el('current-plans-missions-prev').disabled = !hasPrev;
        el('current-plans-missions-next').disabled = !hasNext;
    }

    function clearMissions(message) {
        el('current-plans-missions-wrap')?.classList.add('d-none');
        el('current-plans-missions-empty')?.classList.remove('d-none');
        el('current-plans-missions-empty').textContent = message;
        el('current-plans-missions-body').innerHTML = '';
        state.missions = [];
        state.missionsTotalPages = 0;
        state.missionsTotalElements = 0;
        updateMissionPaging();
    }

    function clearModes(message) {
        el('current-plans-modes-wrap')?.classList.add('d-none');
        el('current-plans-modes-empty')?.classList.remove('d-none');
        el('current-plans-modes-empty').textContent = message;
        el('current-plans-modes-body').innerHTML = '';
        el('current-plans-selected-mission').textContent = 'Выберите миссию в верхней таблице.';
        state.currentModes = [];
        state.bookedSlotsById = {};
        setExportEnabled(false);
    }

    function setTableLoading(bodyId, colspan, message) {
        const tbody = el(bodyId);
        if (tbody) tbody.innerHTML = emptyRow(colspan, message);
    }

    function selectedSatellite() {
        return state.satellites.find((item) => Number(item.id) === Number(state.selectedSatelliteId)) || null;
    }

    function satelliteLabel(satellite) {
        if (!satellite) return `Спутник ${state.selectedSatelliteId}`;
        const name = satellite.name || satellite.code || `КА ${satellite.id}`;
        const type = satellite.typeCode ? `, тип ${satellite.typeCode}` : '';
        const norad = satellite.noradId ? `, NORAD ${satellite.noradId}` : '';
        return `${name} (ID ${satellite.id}${norad}${type})`;
    }

    function missionCaption(mission, missionId) {
        if (!mission) return `Миссия ${missionId}`;
        return `Миссия ${mission.missionId}: ${formatDateTime(mission.missionStart)} — ${formatDateTime(mission.missionStop)}`;
    }

    async function fetchJson(url, options = {}) {
        const response = await fetch(url, options.headers ? options : {...options, headers: {'Accept': 'application/json'}});
        if (!response.ok) {
            const text = await response.text();
            throw new Error(extractError(text) || `HTTP ${response.status}`);
        }
        if (response.status === 204) return null;
        const text = await response.text();
        return text ? JSON.parse(text) : null;
    }

    function extractError(text) {
        if (!text) return '';
        try {
            const json = JSON.parse(text);
            return json.message || json.error || text;
        } catch (_) {
            return text;
        }
    }

    function showAlert(message, type = 'danger') {
        const target = el('current-plans-alert');
        if (!target) return;
        if (!message) {
            target.innerHTML = '';
            return;
        }
        target.innerHTML = `<div class="alert alert-${type} alert-dismissible fade show" role="alert">
            ${escapeHtml(message)}
            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
        </div>`;
    }

    function surveyCalculationMessage(result) {
        if (!result) return 'Расчёт съёмки выполнен.';
        return [
            `Расчёт съёмки выполнен. Всего режимов съёмки: ${result.surveyModesCount ?? 0}`,
            `COMPLAN: ${result.complanModesCount ?? 0}`,
            `SLOTS: ${result.slotsModesCount ?? 0}`,
            `MIXED: ${result.mixedModesCount ?? 0}`,
            `MANUAL: ${result.manualModesCount ?? 0}`,
            `booked slots: ${result.bookedSlotIdsCount ?? 0}`
        ].join(' · ');
    }

    function modeCountsMessage(modes) {
        const counts = countModes(modes);
        const parts = Object.keys(counts).sort().map((type) => `${type}: ${counts[type]}`);
        return parts.length ? `Режимы: ${parts.join(' · ')}` : 'Режимы отсутствуют.';
    }

    function modeCountsPlain(modes) {
        const counts = countModes(modes);
        return Object.keys(counts).sort().map((type) => `${type}: ${counts[type]}`).join(', ');
    }

    function countModes(modes) {
        return (modes || []).reduce((acc, mode) => {
            const type = mode.type || 'UNKNOWN';
            acc[type] = (acc[type] || 0) + 1;
            return acc;
        }, {});
    }

    function emptyRow(colspan, message) {
        return `<tr><td colspan="${colspan}" class="text-muted text-center py-4">${escapeHtml(message)}</td></tr>`;
    }

    function shortUuid(value) {
        if (!value) return '—';
        return `${String(value).slice(0, 8)}…`;
    }

    function formatDateTime(value) {
        return value ? PcpUtcTime.formatDateTime(value) : '—';
    }

    function formatNumber(value) {
        const number = Number(value);
        if (!Number.isFinite(number)) return '—';
        return number.toLocaleString('ru-RU', {maximumFractionDigits: 2});
    }

    function formatDuration(seconds) {
        const totalSeconds = Number(seconds);
        if (!Number.isFinite(totalSeconds)) return '—';

        const rounded = Math.max(0, Math.round(totalSeconds));
        const hours = Math.floor(rounded / 3600);
        const minutes = Math.floor((rounded % 3600) / 60);
        const remainingSeconds = rounded % 60;
        const pad = (number) => String(number).padStart(2, '0');

        return hours > 0
            ? `${hours}:${pad(minutes)}:${pad(remainingSeconds)}`
            : `${minutes}:${pad(remainingSeconds)}`;
    }

    function toDateTimeLocalValue(date) {
        return PcpUtcTime.toLocalInputValue(date);
    }

    function trimToNull(value) {
        const trimmed = String(value || '').trim();
        return trimmed || null;
    }

    function setCreateMissionEnabled(enabled) {
        const button = el('current-plans-create-mission');
        if (button) button.disabled = !enabled;
    }

    function setExportEnabled(enabled) {
        const button = el('current-plans-export-csv');
        if (button) button.disabled = !enabled || !state.selectedMissionId;
    }

    function setButtonLoading(button, loading, text = 'Загрузка...') {
        if (!button) return;
        if (loading) {
            button.dataset.originalText = button.textContent;
            button.textContent = text;
            button.disabled = true;
        } else {
            button.textContent = button.dataset.originalText || button.textContent;
            button.disabled = false;
        }
    }

    function closeCreateMissionDialog() {
        const modalElement = el('current-plans-create-modal');
        if (window.bootstrap && modalElement) {
            window.bootstrap.Modal.getOrCreateInstance(modalElement).hide();
        }
    }

    function csvCell(value) {
        const text = String(value ?? '');
        return `"${text.replaceAll('"', '""')}"`;
    }

    function downloadTextFile(filename, content, type) {
        const blob = new Blob([content], {type});
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = filename;
        document.body.appendChild(link);
        link.click();
        link.remove();
        URL.revokeObjectURL(url);
    }

    function escapeHtml(value) {
        return String(value ?? '')
            .replaceAll('&', '&amp;')
            .replaceAll('<', '&lt;')
            .replaceAll('>', '&gt;')
            .replaceAll('"', '&quot;')
            .replaceAll("'", '&#039;');
    }

    function cssEscape(value) {
        if (window.CSS && typeof window.CSS.escape === 'function') {
            return window.CSS.escape(String(value ?? ''));
        }
        return String(value ?? '').replaceAll('"', '\\"');
    }
})();
