



    function drawReq(show, req){
        if (req){
            if (!show){
                var name = 'req_'+req.requestId
                while (viewer.dataSources.remove(viewer.dataSources.getByName(name)[0])) {}
            } else{

                var u = '/requests/req-with-cells/'+req.requestId
                fetch(u,  {
                    method: "post",
                    headers: {
                        'Accept': 'application/json',
                        'Content-Type': 'application/json'
                    },
                })
                .then((response)=>response.json())
                .then((json) =>{
                    viewer.dataSources.add(Cesium.CzmlDataSource.load(json));
                })
            }
        }
    }


        function drawOrbit(show, req, startInput, endInput){
            if (req){
                if (!show){
                    var name = 'sat_'+req.noradId
                    while (viewer.dataSources.remove(viewer.dataSources.getByName(name)[0])) {}
                    checkModels();
                } else{


                   const inputOrb = document.getElementById('toggleOrbit');
                   let valueOrb = inputOrb.checked;
                   const inputFl = document.getElementById('toggleFL');
                   let valueFl = inputFl.checked;
                   const inputSw = document.getElementById('toggleSwath');
                   let valueSw = inputSw.checked;

                    var sceneMode = (viewer.scene.mode == Cesium.SceneMode.SCENE2D);
                    var u = '/requests/sat/'+req.noradId+
                    '?view='+sceneMode+'&orbit='+valueOrb+
                    '&sw='+valueSw+
                    '&fl='+valueFl
                    if (startInput && endInput) {
                        u += '&tn=' + encodeURIComponent(startInput) + '&tk=' + encodeURIComponent(endInput)
                    }

                    console.log(u)

                    fetch(u,  {
                        method: "post",
                        headers: {
                            'Accept': 'application/json',
                            'Content-Type': 'application/json'
                        },
                    })
                    .then((response)=>response.json())
                    .then((json) =>{
                        console.log(json)
                        viewer.dataSources.add(Cesium.CzmlDataSource.load(json));
                        checkModels();
                    })
                }
            }
        }


        function drawSt(show, req){
            if (req){
                if (!show){
                    var name = 'station_'+req.id
                    while (viewer.dataSources.remove(viewer.dataSources.getByName(name)[0])) {}
                    checkModels();
                } else{
                    var sceneMode = (viewer.scene.mode == Cesium.SceneMode.SCENE2D);
                    var u = '/requests/station/'+req.id+'?view='+sceneMode+
                        '&orbitHeightKm='+encodeURIComponent(selectedStationOrbitHeightKm())
                    fetch(u,  {
                        method: "post",
                        headers: {
                            'Accept': 'application/json',
                            'Content-Type': 'application/json'
                        },
                    })
                    .then(async (response) => {
                        const text = await response.text();
                        let payload;
                        try {
                            payload = text ? JSON.parse(text) : [];
                        } catch (error) {
                            payload = { message: text || `HTTP error! status: ${response.status}` };
                        }
                        if (!response.ok) {
                            throw new Error(payload.error || payload.message || `HTTP error! status: ${response.status}`);
                        }
                        return payload;
                    })
                    .then((json) =>{
                        if (!Array.isArray(json) || !json[0] || json[0].id !== 'document') {
                            throw new Error('Некорректный CZML для плана спутника');
                        }
                        viewer.dataSources.add(Cesium.CzmlDataSource.load(json));
                        checkModels();
                    })
                    .catch(error => {
                        console.error('Ошибка при загрузке плана спутника:', error);
                        alert(error.message);
                    })
                }
            }
        }

        function selectedStationOrbitHeightKm() {
            const input = document.getElementById('stationOrbitHeightKm');
            const value = Number(input?.value || 500);
            if (!Number.isFinite(value) || value <= 0) {
                if (input) input.value = '500';
                return 500;
            }
            return value;
        }


        function drawSat(show, req, startInput, endInput, runId){
            if (req){
                if (!show){
                    var name = 'plan_'+req.noradId
                    while (viewer.dataSources.remove(viewer.dataSources.getByName(name)[0])) {}
                    checkModels();
                } else{

                   // const url = `/requests/req-with-covs/${requestId}?tn=${startInput}&tk=${endInput}&cov=`+cover+sign;
                    let u = '/requests/plan/'+req.noradId+'?tn='+startInput+'&tk='+endInput
                    if (runId) {
                        u += '&runId=' + encodeURIComponent(runId)
                    }
                    console.log(u)
                    fetch(u,  {
                        method: "post",
                        headers: {
                            'Accept': 'application/json',
                            'Content-Type': 'application/json'
                        },
                    })
                    .then((response)=>response.json())
                    .then((json) =>{
                        viewer.dataSources.add(Cesium.CzmlDataSource.load(json));
                        checkModels();
                    })
                }
            }
        }

        // выбрать все НИП/ППИ
        function selectAllRequests(){
          $('input:checkbox[name="request"]:visible:not(:checked)').each(function(){$(this).trigger('click')});
        }

        // скрыть все НИП/ППИ
        function deselectAllRequests(){
          $('input:checkbox[name="request"]:checked').each(function(){$(this).trigger('click')});
        }

        // Функция для отображения тепловой карты
        function showHeatmap(importance, countLat, countLong) {
            if (!window.viewer) return;
            hideHeatmap();
            var params = new URLSearchParams({ importance: importance });
            if (countLat) {
                params.append('countLat', countLat);
            }
            if (countLong) {
                params.append('countLong', countLong);
            }
            var u = '/requests?' + params.toString()
            fetch(u,  {
                method: "post",
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
            })
            .then((response)=>response.json())
            .then((json) =>{
                viewer.dataSources.add(Cesium.CzmlDataSource.load(json));
            })
        }

        // Функция для скрытия тепловой карты
        function hideHeatmap() {
            if (!window.viewer) return;
            // Удаляем слой тепловой карты, если он существует
            while (viewer.dataSources.remove(viewer.dataSources.getByName('heatMap')[0])) {}
        }


        function useSceneMode(sceneMode){
            if (sceneMode == Cesium.SceneMode.SCENE2D){
                console.log('2D')
            } else
                console.log('3D')
        }





        function checkModels(){

            var sceneMode = (viewer.scene.mode == Cesium.SceneMode.SCENE2D);

            viewer.dataSources._dataSources.forEach(dataSource => {
              dataSource._entityCollection._entities._array.forEach( entity => {
                if (entity.id.includes('station_')){
                    entity.model.show=!sceneMode
                    entity.billboard.show=sceneMode
                } else if (entity.id.includes('orbit_')){
                    entity.model.show=!sceneMode
                    entity.point.show=sceneMode
                }
              })



            });

        }

        function handleOrbits(show){

                viewer.dataSources._dataSources.forEach(dataSource => {
                      dataSource._entityCollection._entities._array.forEach( entity => {
                         if (entity.id.includes('orbit_')){
                            entity.path.show=show
                        }
                      })
                    });
        }

        function handleSw(show){
                viewer.dataSources._dataSources.forEach(dataSource => {
                      dataSource._entityCollection._entities._array.forEach( entity => {
                         if (entity.id.includes('swath_')){
                            entity.path.show=show
                        }
                      })
                    });
        }


        function handleFl(show){
                viewer.dataSources._dataSources.forEach(dataSource => {
                      dataSource._entityCollection._entities._array.forEach( entity => {
                         if (entity.id.includes('flight_line_')){
                            entity.path.show=show
                            entity.point.show=show
                        }
                      })
                    });
        }







// Глобальные переменные для хранения данных слотов
let currentSlotsData = [];
let currentRequestId = null;
let currentSlotsContext = null;
let slotsSortConfig = {
    column: 'tn',
    direction: 'asc'
};

function removeDataSourceByName(dataSourceName) {
    let dataSource = viewer.dataSources.getByName(dataSourceName)[0];
    while (dataSource && viewer.dataSources.remove(dataSource)) {
        dataSource = viewer.dataSources.getByName(dataSourceName)[0];
    }
}

function getCurrentSlotsDataSource() {
    if (!currentSlotsContext || !currentSlotsContext.dataSourceName) {
        return null;
    }

    return viewer.dataSources.getByName(currentSlotsContext.dataSourceName)[0] || null;
}

function getCoverageSchemeDataSourceName(requestId) {
    return `req_cov_scheme_${requestId}`;
}

function setSlotsContext(context) {
    currentSlotsContext = context;
    updateSlotsActionButtons();
}

function updateSlotsActionButtons() {
    const isRequestContext = currentSlotsContext && currentSlotsContext.type === 'request';
    const bookButton = document.getElementById('bookSlotsBtn');
    const cancelButton = document.getElementById('cancelBookingBtn');

    if (bookButton) {
        bookButton.style.display = isRequestContext ? '' : 'none';
    }
    if (cancelButton) {
        cancelButton.style.display = isRequestContext ? '' : 'none';
    }
}

function getSatelliteBookedSlotsDataSourceName(satelliteId) {
    return `sat_booked_${satelliteId}`;
}

function getSatelliteSlotsDataSourceName(satelliteId) {
    return `sat_slots_${satelliteId}`;
}

function buildSlotEntityId(slot, index) {
    if (slot.entityId) {
        return slot.entityId;
    }

    const satelliteId = slot.satelliteId ?? 'unknown';
    const cycle = slot.cycle ?? slot.revolution ?? 0;
    const slotNumber = slot.slotNumber
        ?? (Array.isArray(slot.slotIds) && slot.slotIds.length > 0 ? slot.slotIds[0] : index);

    return `slot_${satelliteId}_${cycle}_${slotNumber}`;
}

function normalizeRequestSlots(slots) {
    return (slots || []).map((slot, index) => ({
        ...slot,
        index: index,
        entityId: buildSlotEntityId(slot, index)
    }));
}

function normalizeSatelliteBookedSlots(slots) {
    return (slots || []).map((slot, index) => ({
        ...slot,
        cycle: slot.revolution ?? 0,
        slotNumber: Array.isArray(slot.slotIds) && slot.slotIds.length > 0 ? slot.slotIds[0] : index + 1,
        state: 'BOOKED',
        index: index,
        entityId: buildSlotEntityId({
            ...slot,
            cycle: slot.revolution ?? 0,
            slotNumber: Array.isArray(slot.slotIds) && slot.slotIds.length > 0 ? slot.slotIds[0] : index + 1
        }, index)
    }));
}

function normalizeSatelliteSlots(slots) {
    return (slots || []).map((slot, index) => ({
        ...slot,
        index: index,
        entityId: buildSlotEntityId(slot, index)
    }));
}

function normalizeCoverageSchemeModes(modes) {
    return (modes || []).map((mode, index) => ({
        ...mode,
        index: index,
        sequence: mode.sequence ?? (index + 1),
        cycle: mode.cycle ?? mode.sequence ?? (index + 1),
        tn: mode.tn ?? mode.timeStart,
        tk: mode.tk ?? mode.timeEnd,
        roll: mode.roll ?? mode.rollDegrees,
        contour: mode.contour ?? mode.contourWkt,
        entityId: mode.entityId ?? `coverage_mode_${mode.satelliteId ?? 'unknown'}_${mode.sequence ?? index + 1}`
    }));
}

function parsePolygonWktToDegreesArray(contourWkt) {
    if (typeof contourWkt !== 'string') {
        return null;
    }

    const trimmed = contourWkt.trim();
    if (!trimmed.toUpperCase().startsWith('POLYGON')) {
        return null;
    }

    const body = trimmed
        .replace(/^POLYGON\s*\(\(/i, '')
        .replace(/\)\)\s*$/i, '');
    const outerRingText = body.split('),(')[0];

    const coordinates = outerRingText
        .split(',')
        .map(pair => pair.trim().split(/\s+/))
        .map(parts => ({
            longitude: Number(parts[0]),
            latitude: Number(parts[1])
        }))
        .filter(point => Number.isFinite(point.longitude) && Number.isFinite(point.latitude));

    if (coordinates.length < 3) {
        return null;
    }

    const degrees = [];
    coordinates.forEach(point => {
        degrees.push(point.longitude, point.latitude, 0.0);
    });

    return degrees;
}

function closeDegreesArrayHeights(positions) {
    if (!Array.isArray(positions) || positions.length < 9) {
        return positions;
    }

    const first = positions.slice(0, 3);
    const last = positions.slice(-3);
    const isClosed = first.length === last.length && first.every((value, index) => value === last[index]);

    return isClosed ? positions : positions.concat(first);
}

function getSatelliteCesiumColor(satellite) {
    const red = Number(satellite && satellite.red);
    const green = Number(satellite && satellite.green);
    const blue = Number(satellite && satellite.blue);

    return Cesium.Color.fromBytes(
        Number.isFinite(red) ? red : 255,
        Number.isFinite(green) ? green : 140,
        Number.isFinite(blue) ? blue : 0,
        255
    );
}

function createSatelliteBookedSlotsDataSource(dataSourceName, satellite, slots) {
    const dataSource = new Cesium.CustomDataSource(dataSourceName);
    const color = getSatelliteCesiumColor(satellite);

    normalizeSatelliteBookedSlots(slots).forEach(slot => {
        const positions = parsePolygonWktToDegreesArray(slot.contour);
        if (!positions) {
            console.warn('Не удалось разобрать контур забронированного слота', slot);
            return;
        }

        dataSource.entities.add({
            id: slot.entityId,
            name: `Забронированный слот КА ${slot.satelliteId}`,
            description: [
                `Спутник: ${slot.satelliteId ?? '-'}`,
                `Начало: ${formatDateTime(slot.tn)}`,
                `Окончание: ${formatDateTime(slot.tk)}`,
                `Длительность: ${formatDuration(calculateDuration(slot.tn, slot.tk))}`,
                `Виток: ${slot.revolution ?? '-'}`,
                `Слоты: ${(slot.slotIds || []).join(', ') || '-'}`
            ].join('<br>'),
            polygon: {
                hierarchy: Cesium.Cartesian3.fromDegreesArrayHeights(positions),
                material: color.withAlpha(0.28),
                outline: true,
                outlineColor: color,
                outlineWidth: 2.0
            },
            polyline: {
                positions: Cesium.Cartesian3.fromDegreesArrayHeights(closeDegreesArrayHeights(positions)),
                width: 2.0,
                material: color
            }
        });
    });

    return dataSource;
}

function createSatelliteSlotsDataSource(dataSourceName, satellite, slots) {
    const dataSource = new Cesium.CustomDataSource(dataSourceName);
    const color = getSatelliteCesiumColor(satellite);

    normalizeSatelliteSlots(slots).forEach(slot => {
        const positions = parsePolygonWktToDegreesArray(slot.contour);
        if (!positions) {
            console.warn('Не удалось разобрать контур слота', slot);
            return;
        }

        dataSource.entities.add({
            id: slot.entityId,
            name: `Слот КА ${slot.satelliteId}`,
            description: [
                `Спутник: ${slot.satelliteId ?? '-'}`,
                `Цикл: ${slot.cycle ?? '-'}`,
                `Начало: ${formatDateTime(slot.tn)}`,
                `Окончание: ${formatDateTime(slot.tk)}`,
                `Длительность: ${formatDuration(calculateDuration(slot.tn, slot.tk))}`,
                `Виток: ${slot.revolution ?? '-'}`,
                `Номер слота: ${slot.slotNumber ?? '-'}`
            ].join('<br>'),
            polygon: {
                hierarchy: Cesium.Cartesian3.fromDegreesArrayHeights(positions),
                material: color.withAlpha(0.18),
                outline: true,
                outlineColor: color,
                outlineWidth: 2.0
            },
            polyline: {
                positions: Cesium.Cartesian3.fromDegreesArrayHeights(closeDegreesArrayHeights(positions)),
                width: 2.0,
                material: color
            }
        });
    });

    return dataSource;
}

function processAndDisplaySatelliteBookedSlots(slots, satellite) {
    currentSlotsData = normalizeSatelliteBookedSlots(slots);
    currentRequestId = null;
    setSlotsContext({
        type: 'satellite-booked',
        key: String(satellite.noradId),
        dataSourceName: getSatelliteBookedSlotsDataSourceName(satellite.noradId)
    });

    document.getElementById('slotsTitle').textContent =
        `Забронированные слоты спутника "${satellite.name}" (${currentSlotsData.length} интервалов)`;
    document.getElementById('slotsContainer').style.display = 'block';

    if (currentSlotsData.length === 0) {
        showEmptySlotsMessage('Для выбранного временного диапазона забронированные слоты не найдены');
        updateSlotsSummary();
        return;
    }

    renderSlotsTable();
    updateSlotsSummary();
}

function processAndDisplaySatelliteSlots(slots, satellite) {
    currentSlotsData = normalizeSatelliteSlots(slots);
    currentRequestId = null;
    setSlotsContext({
        type: 'satellite-slots',
        key: String(satellite.noradId),
        dataSourceName: getSatelliteSlotsDataSourceName(satellite.noradId)
    });

    document.getElementById('slotsTitle').textContent =
        `Слоты спутника "${satellite.name}" (${currentSlotsData.length} интервалов)`;
    document.getElementById('slotsContainer').style.display = 'block';

    if (currentSlotsData.length === 0) {
        showEmptySlotsMessage('Для выбранного временного диапазона слоты не найдены');
        updateSlotsSummary();
        return;
    }

    updateSlotsTableHeaders();
    renderSlotsTable();
    updateSlotsSummary();
}

function drawSatelliteBookedSlots(show, satellite, startInput, endInput) {
    if (!satellite) {
        return;
    }

    const dataSourceName = getSatelliteBookedSlotsDataSourceName(satellite.noradId);

    if (!show) {
        removeDataSourceByName(dataSourceName);

        if (currentSlotsContext
            && currentSlotsContext.type === 'satellite-booked'
            && currentSlotsContext.key === String(satellite.noradId)) {
            clearSlotsTable();
        }
        return;
    }

    const params = new URLSearchParams({
        timeStart: startInput,
        timeStop: endInput
    });

    fetch(`/api/satellites/${satellite.noradId}/booked-slots?${params.toString()}`, {
        method: 'GET',
        headers: {
            'Accept': 'application/json'
        }
    })
    .then(async response => {
        if (!response.ok) {
            const rawError = await response.text();
            let errorMessage = rawError;
            try {
                const parsedError = JSON.parse(rawError);
                errorMessage = parsedError.error || parsedError.message || rawError;
            } catch (_) {}
            throw new Error(`HTTP error! status: ${response.status} message: ${errorMessage}`);
        }
        return response.json();
    })
    .then(slots => {
        removeDataSourceByName(dataSourceName);
        viewer.dataSources.add(createSatelliteBookedSlotsDataSource(dataSourceName, satellite, slots));
        processAndDisplaySatelliteBookedSlots(slots, satellite);
    })
    .catch(error => {
        console.error('Ошибка при загрузке забронированных слотов спутника:', error);
        alert(error.message);
    });
}

function drawSatelliteSlots(show, satellite, startInput, endInput) {
    if (!satellite) {
        return;
    }

    const dataSourceName = getSatelliteSlotsDataSourceName(satellite.noradId);

    if (!show) {
        removeDataSourceByName(dataSourceName);

        if (currentSlotsContext
            && currentSlotsContext.type === 'satellite-slots'
            && currentSlotsContext.key === String(satellite.noradId)) {
            clearSlotsTable();
        }
        return;
    }

    const params = new URLSearchParams({
        timeStart: startInput,
        timeStop: endInput
    });

    fetch(`/api/satellites/${satellite.noradId}/slots?${params.toString()}`, {
        method: 'GET',
        headers: {
            'Accept': 'application/json'
        }
    })
    .then(async response => {
        if (!response.ok) {
            const rawError = await response.text();
            let errorMessage = rawError;
            try {
                const parsedError = JSON.parse(rawError);
                errorMessage = parsedError.error || parsedError.message || rawError;
            } catch (_) {}
            throw new Error(`HTTP error! status: ${response.status} message: ${errorMessage}`);
        }
        return response.json();
    })
    .then(slots => {
        removeDataSourceByName(dataSourceName);
        viewer.dataSources.add(createSatelliteSlotsDataSource(dataSourceName, satellite, slots));
        processAndDisplaySatelliteSlots(slots, satellite);
    })
    .catch(error => {
        console.error('Ошибка при загрузке слотов спутника:', error);
        alert(error.message);
    });
}

function drawReqCov(show, req, startInput, endInput, node, cover, satellites, sun) {
    if (req) {
        const requestId = req.requestId;
        const dataSourceName = 'req_cov_' + requestId;

        if (!show) {
            // Удаляем данные покрытия с карты
            removeDataSourceByName(dataSourceName);

            // Если это текущая отображаемая заявка, очищаем таблицу слотов
            if (currentRequestId === requestId) {
                clearSlotsTable();
            }
        } else {


            const params = new URLSearchParams({
                tn: startInput,
                tk: endInput,
                cov: cover,
                sun: sun
            });

            if (node == 1) {
                params.append('sign', 'ASC');
            } else if (node == 2) {
                params.append('sign', 'DESC');
            }

            if (Array.isArray(satellites)) {
                satellites.forEach(satelliteId => params.append('satellites', satelliteId));
            }

            params.append('sun', sun)
            const url = `/requests/req-with-covs/${requestId}?${params.toString()}`;
            console.log('Запрос покрытия:', url);

            fetch(url, {
                method: "POST",
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
            })
            .then(async response => {
                if (!response.ok) {
                    const rawError = await response.text();
                    let errorMessage = rawError;
                    try {
                        const parsedError = JSON.parse(rawError);
                        errorMessage = parsedError.error || parsedError.message || rawError;
                    } catch (_) {}
                    throw new Error(`HTTP error! status: ${response.status} message: ${errorMessage}`);
                }
                return response.json();
            })
            .then(json => {
                // Отображаем покрытие на карте
                if (json.cov && json.cov.length > 0) {
                    viewer.dataSources.add(Cesium.CzmlDataSource.load(json.cov));
                }

                // Обрабатываем и отображаем слоты в таблице
                if (json.slots && json.slots.length > 0) {
                    processAndDisplaySlots(json.slots, req);
                } else {
                    showEmptySlotsMessage('Нет интервалов покрытия для выбранного временного диапазона');
                }
            })
            .catch(error => {
                console.error('Ошибка при загрузке данных покрытия:', error);
                alert(error.message);
            });
        }
    }
}

function drawReqCoverageScheme(show, req, startInput, endInput, satellites, options) {
    if (!req) {
        return;
    }

    const requestId = req.requestId;
    const dataSourceName = getCoverageSchemeDataSourceName(requestId);

    if (!show) {
        removeDataSourceByName(dataSourceName);

        if (currentSlotsContext
            && currentSlotsContext.type === 'coverage-scheme'
            && currentSlotsContext.key === requestId) {
            clearSlotsTable();
        }
        return;
    }

    if (!Array.isArray(satellites) || satellites.length === 0) {
        alert('Для расчета схемы покрытия выберите хотя бы один спутник');
        return;
    }

    const params = new URLSearchParams({
        tn: startInput,
        tk: endInput
    });
    satellites.forEach(satelliteId => params.append('satellites', satelliteId));
    if (options) {
        if (options.rollStepDegrees !== null && options.rollStepDegrees !== undefined) {
            params.append('rollStepDegrees', options.rollStepDegrees);
        }
        if (options.minimumTechnologyOverlap !== null && options.minimumTechnologyOverlap !== undefined) {
            params.append('minimumTechnologyOverlap', options.minimumTechnologyOverlap);
        }
        if (options.strictContinuousCoverage !== null && options.strictContinuousCoverage !== undefined) {
            params.append('strictContinuousCoverage', options.strictContinuousCoverage);
        }
        if (options.allowPartialCoverage !== null && options.allowPartialCoverage !== undefined) {
            params.append('allowPartialCoverage', options.allowPartialCoverage);
        }
        if (options.orientationToleranceDegrees !== null && options.orientationToleranceDegrees !== undefined) {
            params.append('orientationToleranceDegrees', options.orientationToleranceDegrees);
        }
        if (options.includeDebugInfo !== null && options.includeDebugInfo !== undefined) {
            params.append('includeDebugInfo', options.includeDebugInfo);
        }
    }

    fetch(`/requests/req-with-coverage-scheme/${requestId}?${params.toString()}`, {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        }
    })
    .then(async response => {
        if (!response.ok) {
            const rawError = await response.text();
            let errorMessage = rawError;
            try {
                const parsedError = JSON.parse(rawError);
                errorMessage = parsedError.error || parsedError.message || rawError;
            } catch (_) {}
            throw new Error(`HTTP error! status: ${response.status} message: ${errorMessage}`);
        }
        return response.json();
    })
    .then(json => {
        removeDataSourceByName(dataSourceName);

        if (json.cov && json.cov.length > 0) {
            viewer.dataSources.add(Cesium.CzmlDataSource.load(json.cov));
        }

        if (json.slots && json.slots.length > 0) {
            processAndDisplayCoverageSchemeModes(json.slots, req);
        } else {
            document.getElementById('slotsTitle').textContent =
                `Схема покрытия заявки "${req.name}"`;
            setSlotsContext({
                type: 'coverage-scheme',
                key: requestId,
                dataSourceName: dataSourceName
            });
            updateSlotsTableHeaders();
            showEmptySlotsMessage('Для выбранного временного диапазона режимы схемы покрытия не найдены');
        }
    })
    .catch(error => {
        console.error('Ошибка при загрузке схемы покрытия:', error);
        alert(error.message);
    });
}

function getCoverageSchemeRequestOptions() {
    const parseOptionalNumber = (elementId) => {
        const input = document.getElementById(elementId);
        if (!input) {
            return null;
        }

        const rawValue = String(input.value ?? '').trim();
        if (rawValue === '') {
            return null;
        }

        const parsedValue = Number.parseFloat(rawValue);
        return Number.isFinite(parsedValue) ? parsedValue : null;
    };

    return {
        rollStepDegrees: parseOptionalNumber('coverageSchemeRollStep'),
        minimumTechnologyOverlap: parseOptionalNumber('coverageSchemeMinOverlap'),
        orientationToleranceDegrees: parseOptionalNumber('coverageSchemeOrientationTolerance'),
        strictContinuousCoverage: document.getElementById('coverageSchemeStrictContinuous')?.checked ?? true,
        allowPartialCoverage: document.getElementById('coverageSchemeAllowPartial')?.checked ?? false,
        includeDebugInfo: document.getElementById('coverageSchemeIncludeDebug')?.checked ?? true
    };
}

// Обработка и отображение слотов
function processAndDisplaySlots(slots, request) {
    currentSlotsData = normalizeRequestSlots(slots);
    currentRequestId = request.requestId;
    setSlotsContext({
        type: 'request',
        key: request.requestId,
        dataSourceName: 'req_cov_' + request.requestId
    });

    // Обновляем заголовок
    document.getElementById('slotsTitle').textContent =
        `Интервалы покрытия заявки "${request.name}" (${currentSlotsData.length} интервалов)`;
    updateSlotsTableHeaders();

    // Показываем контейнер слотов
    document.getElementById('slotsContainer').style.display = 'block';

    // Рендерим таблицу
    renderSlotsTable();

    // Обновляем сводную информацию
    updateSlotsSummary();
}

function processAndDisplayCoverageSchemeModes(modes, request) {
    currentSlotsData = normalizeCoverageSchemeModes(modes);
    currentRequestId = null;
    setSlotsContext({
        type: 'coverage-scheme',
        key: request.requestId,
        dataSourceName: getCoverageSchemeDataSourceName(request.requestId)
    });

    document.getElementById('slotsTitle').textContent =
        `Схема покрытия заявки "${request.name}" (${currentSlotsData.length} режимов)`;
    document.getElementById('slotsContainer').style.display = 'block';

    updateSlotsTableHeaders();
    renderSlotsTable();
    updateSlotsSummary();
}

function updateSlotsTableHeaders() {
    const primaryHeader = document.getElementById('slotsPrimaryHeader');
    const stateHeader = document.getElementById('slotsStateHeader');
    const isCoverageScheme = currentSlotsContext && currentSlotsContext.type === 'coverage-scheme';

    if (primaryHeader) {
        primaryHeader.childNodes[0].textContent = isCoverageScheme ? '№ режима' : '№ слота';
    }
    if (stateHeader) {
        stateHeader.childNodes[0].textContent = isCoverageScheme ? '' : 'Статус';
    }
}

// Рендеринг таблицы слотов
function renderSlotsTable() {
    const tbody = document.getElementById('slotsTableBody');
    if (!tbody) return;

    // Очищаем таблицу
    tbody.innerHTML = '';

    if (currentSlotsData.length === 0) {
        tbody.innerHTML = `
            <tr>
                <td colspan="7" class="text-center text-muted py-4">
                    Нет данных для отображения
                </td>
            </tr>
        `;
        return;
    }



    // Сортируем данные
    const sortedSlots = [...currentSlotsData].sort((a, b) => {
        const column = slotsSortConfig.column;
        const direction = slotsSortConfig.direction === 'asc' ? 1 : -1;

        let aValue, bValue;

        switch(column) {
            case 'cycle':
                aValue = a.cycle || a.sequence || 0;
                bValue = b.cycle || b.sequence || 0;
                return (aValue - bValue) * direction;

            case 'satellite':
                aValue = a.satelliteId || 0;
                bValue = b.satelliteId || 0;
                return (aValue - bValue) * direction;

            case 'tn':
                aValue = PcpUtcTime.parseApiDate(a.tn)?.getTime() ?? 0;
                bValue = PcpUtcTime.parseApiDate(b.tn)?.getTime() ?? 0;
                return (aValue - bValue) * direction;

            case 'tk':
                aValue = PcpUtcTime.parseApiDate(a.tk)?.getTime() ?? 0;
                bValue = PcpUtcTime.parseApiDate(b.tk)?.getTime() ?? 0;
                return (aValue - bValue) * direction;

            case 'duration':
                aValue = calculateDuration(a.tn, a.tk);
                bValue = calculateDuration(b.tn, b.tk);
                return (aValue - bValue) * direction;

            case 'roll':
                aValue = a.roll || 0;
                bValue = b.roll || 0;
                return (aValue - bValue) * direction;

            case 'revolution':
                aValue = a.revolution || 0;
                bValue = b.revolution || 0;
                return (aValue - bValue) * direction;

            case 'sign':
                aValue = a.revolutionSign || '';
                bValue = b.revolutionSign || '';
                return aValue.localeCompare(bValue) * direction;

            case 'slotNumber':
                aValue = a.slotNumber || 0;
                bValue = b.slotNumber || 0;
                return (aValue - bValue) * direction;

            case 'state':
                aValue = getStateSortOrder(a.state);
                bValue = getStateSortOrder(b.state);
                return (aValue - bValue) * direction;

            default:
                return 0;
        }
    });


    // Рендерим строки
    sortedSlots.forEach((slot, index) => {
        const row = document.createElement('tr');
        row.innerHTML = createSlotRowHTML(slot, slot.index);
        tbody.appendChild(row);
    });



    // Обновляем иконки сортировки
    updateSlotsSortIcons();
}

// Создание HTML для строки слота
function createSlotRowHTML(slot, index) {
    const tn = formatDateTime(slot.tn);
    const duration = calculateDuration(slot.tn, slot.tk);
    const durationFormatted = formatDuration(duration);
    const isCoverageScheme = currentSlotsContext && currentSlotsContext.type === 'coverage-scheme';
    const primaryValue = isCoverageScheme
        ? (slot.sequence || '-')
        : (slot.slotNumber || '-');
    const stateContent = slot.state === undefined || slot.state === null || slot.state === ''
        ? ''
        : (() => {
            const stateFormatted = formatState(slot.state);
            const stateClass = getStateClass(slot.state);
            return `
                <span class="badge ${stateClass}" title="${stateFormatted.text}">
                    ${stateFormatted.text}
                </span>
            `;
        })();

    return `
        <td>${primaryValue}</td>
        <td class="text-nowrap">SAT-${slot.satelliteId || '-'}</td>
        <td class="text-nowrap">${tn}</td>
        <td class="text-nowrap text-center">${stateContent}</td>
        <td class="text-nowrap">${durationFormatted}</td>
        <td class="text-center">
            <button class="btn btn-sm btn-outline-primary"
                    onclick="flyToSlot(${index})"
                    title="Найти на карте">
                <i class="bi bi-geo-alt"></i>
            </button>
        </td>
        <td class="text-center">
            <button class="btn btn-sm btn-outline-primary "
                    data-action="toggle-slot-visibility"
                    data-slot-index="${index}"
                    onclick="viewSlot(${index}, this)"
                    title="Скрыть на карте">
                <i class="bi bi-eye"></i>
            </button>
        </td>
    `;
}

// Форматирование даты и времени
function formatDateTime(dateTimeString) {
    if (!dateTimeString) return '-';
    return PcpUtcTime.formatDateTime(dateTimeString, {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit'
    });
}

// Расчет длительности в минутах
function calculateDuration(tn, tk) {
    if (!tn || !tk) return 0;

    const startDate = PcpUtcTime.parseApiDate(tn);
    const endDate = PcpUtcTime.parseApiDate(tk);
    if (!startDate || !endDate) return 0;

    return (endDate - startDate) / (1000 * 60); // в минутах
}

// Форматирование длительности
function formatDuration(minutes) {
    if (minutes < 1) {
        const seconds = Math.round(minutes * 60);
        return `${seconds} сек`;
    }

    const hours = Math.floor(minutes / 60);
    const mins = Math.round(minutes % 60);

    if (hours > 0) {
        return `${hours}ч ${mins}м`;
    }

    return `${mins} мин`;
}

// Форматирование крена
function formatRoll(roll) {
    if (roll === undefined || roll === null) return '-';
    return roll.toFixed(2) + '°';
}

// Форматирование направления витка
function formatRevolutionSign(sign) {
    const signs = {
        'ASC': { text: '↑', class: 'bg-info', title: 'Восходящий виток' },
        'DESC': { text: '↓', class: 'bg-warning', title: 'Нисходящий виток' },
        'NONE': { text: '-', class: 'bg-secondary', title: 'Не определено' }
    };

    return signs[sign] || signs['NONE'];
}

// Форматирование статуса
function formatState(state) {
    const stateKey = typeof state === 'string' ? state.trim().toUpperCase() : state;

    const states = {
        BOOKED: { text: 'Бронь', class: 'bg-success text-white' },
        AVAILABLE: { text: 'Свободен', class: 'bg-white text-dark border border-secondary' },
        BUSY: { text: 'Блок', class: 'bg-secondary text-white' },
        1: { text: 'Бронь', class: 'bg-success text-white' },
        0: { text: 'Свободен', class: 'bg-white text-dark border border-secondary' },
        2: { text: 'Блок', class: 'bg-secondary text-white' },
        3: { text: 'Блок', class: 'bg-secondary text-white' },
        4: { text: 'Блок', class: 'bg-secondary text-white' }
    };

    return states[stateKey] || { text: String(state ?? '-'), class: 'bg-secondary text-white' };
}

function getStateClass(state) {
    const stateInfo = formatState(state);
    return stateInfo.class;
}

function getStateSortOrder(state) {
    const stateKey = typeof state === 'string' ? state.trim().toUpperCase() : state;

    switch (stateKey) {
        case 'AVAILABLE':
        case 0:
            return 0;
        case 'BOOKED':
        case 1:
            return 1;
        case 'BUSY':
        case 2:
        case 3:
        case 4:
            return 2;
        default:
            return 99;
    }
}

// Обновление сводной информации
function updateSlotsSummary() {
    if (currentSlotsData.length === 0) {
        document.getElementById('totalSlotsCount').textContent = '0';
        document.getElementById('totalDuration').textContent = '0';
        document.getElementById('satellitesCount').textContent = '0';
        document.getElementById('coveragePeriod').textContent = '-';
        return;
    }

    // Общее количество
    const totalSlots = currentSlotsData.length;

    // Общая длительность
    const totalDuration = currentSlotsData.reduce((sum, slot) => {
        return sum + calculateDuration(slot.tn, slot.tk);
    }, 0);

    // Количество уникальных спутников
    const uniqueSatellites = new Set(
        currentSlotsData.map(slot => slot.satelliteId)
    ).size;

    // Период покрытия
    const startTimes = currentSlotsData.map(slot => PcpUtcTime.parseApiDate(slot.tn)).filter(Boolean);
    const endTimes = currentSlotsData.map(slot => PcpUtcTime.parseApiDate(slot.tk)).filter(Boolean);
    const earliest = startTimes.length ? new Date(Math.min(...startTimes)) : null;
    const latest = endTimes.length ? new Date(Math.max(...endTimes)) : null;

    const period = earliest && latest
        ? `${earliest.toLocaleDateString('ru-RU')} - ${latest.toLocaleDateString('ru-RU')}`
        : '-';

    // Обновляем элементы
    document.getElementById('totalSlotsCount').textContent = totalSlots;
    document.getElementById('totalDuration').textContent = totalDuration.toFixed(1);
    document.getElementById('satellitesCount').textContent = uniqueSatellites;
    document.getElementById('coveragePeriod').textContent = period;
}

// Сортировка таблицы слотов
function sortSlotsTable(column) {
    // Переключаем направление сортировки
    if (slotsSortConfig.column === column) {
        slotsSortConfig.direction = slotsSortConfig.direction === 'asc' ? 'desc' : 'asc';
    } else {
        slotsSortConfig.column = column;
        slotsSortConfig.direction = 'asc';
    }

    // Перерисовываем таблицу
    renderSlotsTable();
}

// Обновление иконок сортировки слотов
function updateSlotsSortIcons() {
    // Сбрасываем все иконки
    document.querySelectorAll('.sortable-slot .sort-icon i').forEach(icon => {
        icon.className = 'bi bi-arrow-down-up';
    });

    // Устанавливаем активную иконку
    const activeIcon = document.getElementById(`sort-icon-${slotsSortConfig.column}`);
    if (activeIcon) {
        const icon = activeIcon.querySelector('i');
        if (icon) {
            icon.className = slotsSortConfig.direction === 'asc'
                ? 'bi bi-sort-down'
                : 'bi bi-sort-up';
        }
    }
}

// Очистка таблицы слотов
function clearSlotsTable() {
    currentSlotsData = [];
    currentRequestId = null;
    setSlotsContext(null);
    updateSlotsTableHeaders();

    const tbody = document.getElementById('slotsTableBody');
    if (tbody) {
        tbody.innerHTML = `
            <tr>
                <td colspan="7" class="text-center text-muted py-4">
                    Выберите заявку и включите покрытие для отображения интервалов
                </td>
            </tr>
        `;
    }

    // Обновляем сводку
    document.getElementById('totalSlotsCount').textContent = '0';
    document.getElementById('totalDuration').textContent = '0';
    document.getElementById('satellitesCount').textContent = '0';
    document.getElementById('coveragePeriod').textContent = '-';
    const toggleAllCheckbox = document.getElementById('toggleAllSlotsVisibility');
    if (toggleAllCheckbox) {
        toggleAllCheckbox.checked = true;
    }

    // Скрываем контейнер
    document.getElementById('slotsContainer').style.display = 'none';
}

// Показ сообщения об отсутствии данных
function showEmptySlotsMessage(message) {
    const tbody = document.getElementById('slotsTableBody');
    if (tbody) {
        tbody.innerHTML = `
            <tr>
                <td colspan="7" class="text-center text-muted py-4">
                    ${message || 'Нет данных для отображения'}
                </td>
            </tr>
        `;
    }
    document.getElementById('slotsContainer').style.display = 'block';
    updateSlotsActionButtons();
}

// Экспорт в CSV
function exportSlotsToCSV() {
    if (currentSlotsData.length === 0) {
        alert('Нет данных для экспорта');
        return;
    }

    // Заголовки CSV
    const headers = [
        'Цикл', 'Спутник', 'Начало', 'Окончание', 'Длительность (мин)',
        'Крен (°)', 'Виток', 'Направление', '№ слота', 'Статус'
    ];

    // Данные
    const rows = currentSlotsData.map(slot => [
        slot.cycle || slot.sequence || '',
        slot.satelliteId || '',
        formatDateTime(slot.tn),
        formatDateTime(slot.tk),
        calculateDuration(slot.tn, slot.tk).toFixed(2),
        slot.roll === undefined || slot.roll === null ? '' : Number(slot.roll).toFixed(2),
        slot.revolution || '',
        slot.revolutionSign || '',
        slot.slotNumber || '',
        slot.state || ''
    ]);

    // Формируем CSV
    let csvContent = 'data:text/csv;charset=utf-8,\uFEFF';
    csvContent += headers.join(';') + '\n';
    csvContent += rows.map(row => row.join(';')).join('\n');

    // Создаем ссылку для скачивания
    const encodedUri = encodeURI(csvContent);
    const link = document.createElement('a');
    link.setAttribute('href', encodedUri);
    const contextKey = currentSlotsContext ? currentSlotsContext.key : 'export';
    link.setAttribute('download', `slots_${contextKey}_${new Date().toISOString().slice(0,10)}.csv`);
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
}

function collectVisibleSlotsForBooking() {
    const visibleButtons = document.querySelectorAll(
        '#slotsTableBody button[title="Скрыть на карте"]'
    );
    const uniqueSlots = new Map();

    visibleButtons.forEach(button => {
        const dataSlotIndex = button.getAttribute('data-slot-index');
        let slotIndex = null;

        if (dataSlotIndex !== null && dataSlotIndex !== '') {
            const parsed = Number(dataSlotIndex);
            if (Number.isFinite(parsed)) {
                slotIndex = parsed;
            }
        }

        if (!Number.isFinite(slotIndex)) {
            const onClickValue = button.getAttribute('onclick') || '';
            const matches = onClickValue.match(/viewSlot\((\d+)/);
            if (matches) {
                const parsed = Number(matches[1]);
                if (Number.isFinite(parsed)) {
                    slotIndex = parsed;
                }
            }
        }

        if (!Number.isFinite(slotIndex) || !currentSlotsData[slotIndex]) {
            return;
        }

        const slot = currentSlotsData[slotIndex];
        const satelliteId = Number(slot.satelliteId);
        const cycle = Number(slot.cycle);
        const rawSlotNum = slot.slotNumber ?? slot.slotNum;
        const slotNum = Number(rawSlotNum);

        if (!Number.isFinite(satelliteId) || !Number.isFinite(slotNum) || !Number.isFinite(cycle)) {
            return;
        }

        const slotKey = `${satelliteId}:${slotNum}:${cycle}`;
        if (!uniqueSlots.has(slotKey)) {
            uniqueSlots.set(slotKey, {
                satelliteId: satelliteId,
                slotNum: slotNum,
                cycle: cycle
            });
        }
    });

    return Array.from(uniqueSlots.values());
}

function bookVisibleSlots(button) {
    if (!currentRequestId) {
        alert('Нет выбранной заявки для бронирования');
        return;
    }

    const slots = collectVisibleSlotsForBooking();
    if (slots.length === 0) {
        alert('Нет слотов с активной кнопкой "Скрыть на карте"');
        return;
    }

    const requestBody = {
        requestId: String(currentRequestId),
        slots: slots
    };

    if (button) {
        button.disabled = true;
    }

    fetch('/api/slots/booking/request', {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(requestBody)
    })
    .then(async response => {
        if (!response.ok) {
            const rawError = await response.text();
            let errorMessage = rawError;
            try {
                const parsedError = JSON.parse(rawError);
                errorMessage = parsedError.error || parsedError.message || rawError;
            } catch (_) {}
            throw new Error(`HTTP error! status: ${response.status} message: ${errorMessage}`);
        }
        return response.json();
    })
    .then(json => {
        const bookedCount = Array.isArray(json) ? json.length : 0;
        alert(`Забронировано слотов: ${bookedCount}`);
    })
    .catch(error => {
        console.error('Ошибка при бронировании слотов:', error);
        alert('Ошибка при бронировании слотов: ' + error.message);
    })
    .finally(() => {
        if (button) {
            button.disabled = false;
        }
    });
}

function cancelRequestBooking(button) {
    if (!currentRequestId) {
        alert('Нет выбранной заявки для отмены бронирования');
        return;
    }

    if (button) {
        button.disabled = true;
    }

    fetch(`/api/slots/booking/request?requestId=${encodeURIComponent(String(currentRequestId))}`, {
        method: 'DELETE',
        headers: {
            'Accept': 'application/json'
        }
    })
    .then(async response => {
        if (!response.ok) {
            const rawError = await response.text();
            let errorMessage = rawError;
            try {
                const parsedError = JSON.parse(rawError);
                errorMessage = parsedError.error || parsedError.message || rawError;
            } catch (_) {}
            throw new Error(`HTTP error! status: ${response.status} message: ${errorMessage}`);
        }
        return response.json();
    })
    .then(result => {
        const cancelledCount = Number(result) || 0;
        alert(`Отменено бронирований: ${cancelledCount}`);
    })
    .catch(error => {
        console.error('Ошибка при отмене бронирования:', error);
        alert('Ошибка при отмене бронирования: ' + error.message);
    })
    .finally(() => {
        if (button) {
            button.disabled = false;
        }
    });
}


let prevPoly = null
let materialColor = Cesium.Color.YELLOW
let outlineColor = Cesium.Color.RED


// Полет к слоту на карте
function flyToSlot(slotIndex) {
    if (!currentSlotsData[slotIndex]) return;

    const slot = currentSlotsData[slotIndex];
    const dataSource = getCurrentSlotsDataSource();

    if (!dataSource) {
        console.warn('DataSource не найден для слота');
        return;
    }

    const entityName = slot.entityId || buildSlotEntityId(slot, slotIndex);
    const entity = dataSource.entities.getById(entityName);

    if (entity) {

        if (prevPoly != null){
                prevPoly.polygon.material = materialColor;
                prevPoly.polygon.outlineColor = outlineColor;
        }

        prevPoly = entity
        materialColor = entity.polygon.material
        outlineColor = entity.polygon.outlineColor

        entity.polygon.material = Cesium.Color.YELLOW.withAlpha(0.4);
        entity.polygon.outline = true;
        entity.polygon.outlineColor = Cesium.Color.BLUE;
        entity.polygon.outlineWidth = 3.0;

        viewer.selectedEntity = entity;

        viewer.flyTo(entity, {
            duration: 2
        });
    } else {
        console.warn('Entity не найден для слота:', entityName);
    }
}




function viewSlot(slotIndex, button) {
    if (!currentSlotsData[slotIndex]) return;

    const slot = currentSlotsData[slotIndex];
    const dataSource = getCurrentSlotsDataSource();

    if (!dataSource) {
        console.warn('DataSource не найден для слота');
        return;
    }

    const entityName = slot.entityId || buildSlotEntityId(slot, slotIndex);
    const entity = dataSource.entities.getById(entityName);

    if (entity) {
        //console.log(entityName)
        let visibility = entity.polygon.show
        //console.log(visibility)
        if (visibility == false){
           entity.polygon.show = true
           updateVisibilityButton(button, true);
        }
        else {
           entity.polygon.show = false
           updateVisibilityButton(button, false);
        }


    } else {
        console.warn('Entity не найден для слота:', entityName);
    }
}

// Обновление состояния кнопки
function updateVisibilityButton(button, isVisible) {
    const icon = button.querySelector('i');
    if (!icon) return;

    if (isVisible) {
        // Полигон видим - показываем иконку "скрыть"
        icon.className = 'bi bi-eye';
        button.title = 'Скрыть на карте';
        button.classList.remove('btn-outline-secondary');
        button.classList.add('btn-outline-primary');
    } else {
        // Полигон скрыт - показываем иконку "показать"
        icon.className = 'bi bi-eye-slash';
        button.title = 'Показать на карте';
        button.classList.remove('btn-outline-primary');
        button.classList.add('btn-outline-secondary');
    }
}
