(function () {
    const storageKey = 'pcp.dynamicPlan.visibleRuns';
    const routePageLimit = 1000;
    const intervalPageLimit = 1000;
    const maxRoutesToDraw = 10000;
    const maxIntervalsToDraw = 10000;
    const dataSourcePrefix = 'dynamic_plan_run_';
    let mapViewer = null;

    function readRuns() {
        try {
            const value = window.localStorage.getItem(storageKey);
            const parsed = value ? JSON.parse(value) : [];
            return Array.isArray(parsed) ? parsed.slice(0, 1) : [];
        } catch (error) {
            console.warn('Failed to read dynamic plan map runs', error);
            return [];
        }
    }

    function writeRuns(runs) {
        window.localStorage.setItem(storageKey, JSON.stringify(runs));
        window.dispatchEvent(new CustomEvent('dynamic-plan-map-runs-changed', { detail: runs }));
    }

    function normalizeRun(run) {
        return {
            runId: String(run.runId),
            requestId: run.requestId ? String(run.requestId) : null,
            routesCount: Number.isFinite(Number(run.routesCount)) ? Number(run.routesCount) : null,
            addedAt: new Date().toISOString()
        };
    }

    function showRun(run) {
        if (!run || !run.runId) {
            return;
        }
        const normalizedRun = normalizeRun(run);
        writeRuns([normalizedRun]);
    }

    function hideRun(runId) {
        const id = String(runId);
        writeRuns(readRuns().filter(item => String(item.runId) !== id));
        removeRunDataSource(id);
    }

    function isRunVisible(runId) {
        const id = String(runId);
        return readRuns().some(item => String(item.runId) === id);
    }

    function dataSourceName(runId) {
        return `${dataSourcePrefix}${runId}`;
    }

    function removeRunDataSource(runId) {
        if (!mapViewer) {
            return;
        }
        let dataSource = mapViewer.dataSources.getByName(dataSourceName(runId))[0];
        while (dataSource && mapViewer.dataSources.remove(dataSource)) {
            dataSource = mapViewer.dataSources.getByName(dataSourceName(runId))[0];
        }
    }

    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.every((value, index) => value === last[index]);
        return isClosed ? positions : positions.concat(first);
    }

    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*\(\s*\(/i, '')
            .replace(/\)\s*\)\s*$/i, '');
        const outerRingText = body.split(/\)\s*,\s*\(/)[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 intervalColor(revSign) {
        return revSign === 'DESC'
            ? Cesium.Color.CYAN
            : Cesium.Color.YELLOW;
    }

    function colorForSatellite(satelliteId) {
        const id = Number(satelliteId);
        const seed = Number.isFinite(id) ? Math.abs(id) : 0;
        const hue = ((seed * 37) % 360) / 360;
        return Cesium.Color.fromHsl(hue, 0.72, 0.52, 1.0);
    }

    function formatValue(value) {
        return value === null || value === undefined || value === '' ? '-' : String(value);
    }

    function formatDateValue(value) {
        return value === null || value === undefined || value === '' ? '-' : PcpUtcTime.formatDateTime(value);
    }

    function createRoutesDataSource(run, routes, intervals) {
        const dataSource = new Cesium.CustomDataSource(dataSourceName(run.runId));
        intervals.forEach((interval, index) => {
            const positions = parsePolygonWktToDegreesArray(interval.contourWkt);
            if (!positions) {
                console.warn('Failed to parse dynamic plan interval contour', interval);
                return;
            }

            const color = intervalColor(interval.revSign);
            dataSource.entities.add({
                id: `dynamic_plan_interval_${run.runId}_${interval.id || index}`,
                name: `Dynamic Plan interval ${interval.revSign || '-'}`,
                description: [
                    `Run ID: ${formatValue(run.runId)}`,
                    `Request ID: ${formatValue(interval.requestId || run.requestId)}`,
                    `Interval ID: ${formatValue(interval.intervalId)}`,
                    `Region ID: ${formatValue(interval.regionId)}`,
                    `Revolution sign: ${formatValue(interval.revSign)}`,
                    `Observation parameters: ${formatValue(interval.observationParametersCount)}`
                ].join('<br>'),
                polygon: {
                    hierarchy: Cesium.Cartesian3.fromDegreesArrayHeights(positions),
                    material: color.withAlpha(0.08),
                    outline: true,
                    outlineColor: color.withAlpha(0.95),
                    outlineWidth: 1.0
                },
                polyline: {
                    positions: Cesium.Cartesian3.fromDegreesArrayHeights(closeDegreesArrayHeights(positions)),
                    width: 1.0,
                    material: color.withAlpha(0.95)
                }
            });
        });

        routes.forEach((route, index) => {
            const positions = parsePolygonWktToDegreesArray(route.contourWkt);
            if (!positions) {
                console.warn('Failed to parse dynamic plan route contour', route);
                return;
            }

            const color = colorForSatellite(route.satelliteId);
            dataSource.entities.add({
                id: `dynamic_plan_route_${run.runId}_${route.id || index}`,
                name: `Dynamic Plan route ${route.satelliteId || '-'}`,
                description: [
                    `Run ID: ${formatValue(run.runId)}`,
                    `Request ID: ${formatValue(route.requestId || run.requestId)}`,
                    `Satellite: ${formatValue(route.satelliteId)}`,
                    `Start: ${formatDateValue(route.startTime)}`,
                    `End: ${formatDateValue(route.endTime)}`,
                    `Duration: ${formatValue(route.duration)}`,
                    `Revolution: ${formatValue(route.revolution)}`,
                    `Roll: ${formatValue(route.roll)}`
                ].join('<br>'),
                polygon: {
                    hierarchy: Cesium.Cartesian3.fromDegreesArrayHeights(positions),
                    material: color.withAlpha(0.22),
                    outline: true,
                    outlineColor: color,
                    outlineWidth: 2.0
                },
                polyline: {
                    positions: Cesium.Cartesian3.fromDegreesArrayHeights(closeDegreesArrayHeights(positions)),
                    width: 2.0,
                    material: color
                }
            });
        });
        return dataSource;
    }

    async function requestJson(url) {
        const response = await fetch(url, {
            method: 'GET',
            headers: {
                'Accept': 'application/json'
            }
        });
        const isJson = response.headers.get('content-type')?.includes('application/json');
        const body = isJson ? await response.json() : await response.text();
        if (!response.ok) {
            if (body && typeof body === 'object') {
                throw new Error(body.error || body.message || Object.values(body).join('; '));
            }
            throw new Error(body || `HTTP ${response.status}`);
        }
        return body;
    }

    async function loadRoutes(run) {
        const routes = [];
        let offset = 0;
        while (routes.length < maxRoutesToDraw) {
            const page = await requestJson(
                `/api/dynamic-plan/runs/${encodeURIComponent(run.runId)}/routes?limit=${routePageLimit}&offset=${offset}`
            );
            if (!Array.isArray(page) || page.length === 0) {
                break;
            }
            routes.push(...page);
            if (page.length < routePageLimit) {
                break;
            }
            offset += routePageLimit;
        }
        if (run.routesCount !== null && run.routesCount > maxRoutesToDraw) {
            console.warn(`Dynamic plan run ${run.runId} has ${run.routesCount} routes; only ${maxRoutesToDraw} are drawn`);
        }
        return routes.slice(0, maxRoutesToDraw);
    }

    async function loadIntervals(run) {
        const intervals = [];
        let offset = 0;
        while (intervals.length < maxIntervalsToDraw) {
            const page = await requestJson(
                `/api/dynamic-plan/runs/${encodeURIComponent(run.runId)}/intervals?limit=${intervalPageLimit}&offset=${offset}`
            );
            if (!Array.isArray(page) || page.length === 0) {
                break;
            }
            intervals.push(...page);
            if (page.length < intervalPageLimit) {
                break;
            }
            offset += intervalPageLimit;
        }
        if (intervals.length >= maxIntervalsToDraw) {
            console.warn(`Dynamic plan run ${run.runId} has at least ${maxIntervalsToDraw} intervals; only ${maxIntervalsToDraw} are drawn`);
        }
        return intervals.slice(0, maxIntervalsToDraw);
    }

    async function drawRun(run) {
        if (!mapViewer || !run || !run.runId) {
            return;
        }

        removeRunDataSource(run.runId);
        const [intervals, routes] = await Promise.all([
            loadIntervals(run),
            loadRoutes(run)
        ]);
        const dataSource = createRoutesDataSource(run, routes, intervals);
        await mapViewer.dataSources.add(dataSource);
        if (dataSource.entities.values.length > 0) {
            mapViewer.flyTo(dataSource, { duration: 1.5 });
        }
    }

    function syncMap() {
        if (!mapViewer) {
            return;
        }

        const runs = readRuns();
        const visibleIds = new Set(runs.map(run => String(run.runId)));
        mapViewer.dataSources._dataSources
            .filter(dataSource => dataSource.name && dataSource.name.startsWith(dataSourcePrefix))
            .forEach(dataSource => {
                const runId = dataSource.name.substring(dataSourcePrefix.length);
                if (!visibleIds.has(runId)) {
                    mapViewer.dataSources.remove(dataSource);
                }
            });

        runs.forEach(run => {
            const existing = mapViewer.dataSources.getByName(dataSourceName(run.runId))[0];
            if (!existing) {
                drawRun(run).catch(error => console.error('Failed to draw dynamic plan run', run.runId, error));
            }
        });
    }

    function initializeMap(viewer) {
        mapViewer = viewer;
        syncMap();
    }

    window.addEventListener('storage', event => {
        if (event.key === storageKey) {
            syncMap();
        }
    });
    window.addEventListener('dynamic-plan-map-runs-changed', syncMap);

    window.PcpDynamicPlanMapLayers = {
        initializeMap,
        showRun,
        hideRun,
        isRunVisible,
        visibleRuns: readRuns
    };
})();
