import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { DefaultExaggeration } from '@basemaps/config/build/config/vector.style.js';
import { GoogleTms, LocationUrl } from '@basemaps/geo';
import { Component, Fragment } from 'react';
import { MapAttrState } from '../attribution.js';
import { Config } from '../config.js';
import { ConfigData } from '../config.layer.js';
import { MapConfig } from '../config.map.js';
import { DebugMap, debugTypes } from '../debug.map.js';
import { WindowUrl } from '../url.js';
import { onMapLoaded } from './map.js';
/** Layer Id for the hillshade layer in the debug map */
const HillShadeLayerId = 'debug-hillshade';
/** dynamic hillshade sources are prefixed with this key */
const HillShadePrefix = '__hillshade-';
/** dynamic linz-elevation source key */
const elevationProdId = 'LINZ-Terrain-Prod';
function debugSourceDropdown(ctx) {
    return (_jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label", children: ctx.label }), _jsx("div", { className: "debug__value", children: _jsx("select", { onChange: ctx.onChange, value: ctx.value, children: ctx.options.map((id) => {
                        return (_jsx("option", { value: id, children: id.replace('basemaps-', '') }, id));
                    }) }) })] }));
}
/** Upper case the first character of the string */
function upperCaseFirstChar(c) {
    return c[0].toUpperCase() + c.slice(1);
}
function debugSlider(label, onInput) {
    return (_jsx("input", { className: "debug__slider", type: "range", min: "0", max: "1", step: "0.05", defaultValue: String(Config.map.debug[`debug.layer.${label}`]), onChange: onInput }));
}
export class Debug extends Component {
    constructor(p) {
        super(p);
        Object.defineProperty(this, "debugMap", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: new DebugMap()
        });
        Object.defineProperty(this, "waitForMap", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: () => {
                const map = this.props.map;
                if (map == null) {
                    setTimeout(this.waitForMap, 20);
                    return;
                }
                window.MaplibreMap = map;
                map.resize();
                onMapLoaded(map, () => {
                    Config.map.on('change', () => {
                        if (this.props.map == null)
                            return;
                        const loc = LocationUrl.toSlug(Config.map.getLocation(this.props.map));
                        const locationSearch = '?' + MapConfig.toUrl(Config.map);
                        window.history.replaceState(null, '', loc + locationSearch);
                        this.updateFromConfig();
                    });
                    this.updateFromConfig();
                    if (Config.map.debug['debug.screenshot']) {
                        async function addLoadedDiv() {
                            // Ensure the attribution data has loaded
                            await MapAttrState.getCurrentAttribution();
                            await new Promise((r) => setTimeout(r, 250));
                            // Jam a div into the page once the map has loaded so tools like playwright can see the map has finished loading
                            const loadedDiv = document.createElement('div');
                            loadedDiv.id = 'map-loaded';
                            loadedDiv.style.width = '1px';
                            loadedDiv.style.height = '1px';
                            document.body.appendChild(loadedDiv);
                        }
                        void map.once('idle', () => {
                            const isTerrainOn = Config.map.debug['debug.terrain'] != null && Config.map.debug['debug.terrain'] !== 'off';
                            const isHSOn = Config.map.debug['debug.hillshade'] != null && Config.map.debug['debug.hillshade'] !== 'off';
                            if (isTerrainOn || isHSOn) {
                                // Ensure hillshade and terrain source is loaded and wait for 2s
                                this.updateFromConfig();
                                setTimeout(() => void addLoadedDiv(), 2000);
                            }
                            else {
                                void addLoadedDiv();
                            }
                        });
                    }
                });
            }
        });
        /** Show the source bounding box on the map */
        Object.defineProperty(this, "toggleTileBoundary", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: (e) => {
                const target = e.target;
                Config.map.setDebug('debug.tile', target.checked);
            }
        });
        /** Show the source bounding box on the map */
        Object.defineProperty(this, "toggleCogs", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: (e) => {
                const target = e.target;
                Config.map.setDebug('debug.cog', target.checked);
                this.setVectorShown(target.checked, debugTypes.cog);
            }
        });
        /** Show the source bounding box on the map */
        Object.defineProperty(this, "toggleSource", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: (e) => {
                const target = e.target;
                Config.map.setDebug('debug.source', target.checked);
                this.setVectorShown(target.checked, debugTypes.source);
            }
        });
        /** Show the capture area on the map */
        Object.defineProperty(this, "toggleCaptureArea", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: (e) => {
                const target = e.target;
                Config.map.setDebug('debug.capture-area', target.checked);
                this.setVectorShown(target.checked, debugTypes['capture-area']);
            }
        });
        Object.defineProperty(this, "_loadingConfig", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: Promise.resolve()
        });
        Object.defineProperty(this, "downloadSource", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: () => {
                const im = this.state.imagery;
                if (im == null)
                    return;
                const geoJson = ConfigData.getGeoJson(im);
                if (geoJson == null)
                    return;
                // Create a magic a href to download the geojson
                const dataStr = `data:text/json;charset=utf-8,` + encodeURIComponent(JSON.stringify(geoJson));
                const aEl = document.createElement('a');
                aEl.setAttribute('href', dataStr);
                aEl.setAttribute('download', im.name + '.json');
                document.body.appendChild(aEl);
                aEl.click();
                aEl.remove();
            }
        });
        Object.defineProperty(this, "selectRasterSource", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: (event) => {
                const sourceId = event.target.value;
                this.setVisibleSource(sourceId);
            }
        });
        Object.defineProperty(this, "selectElevation", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: (event) => {
                const sourceId = event.target.value;
                this.setTerrainShown(sourceId);
            }
        });
        Object.defineProperty(this, "selectHillShade", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: (event) => {
                const sourceId = event.target.value;
                this.setHillShadeShown(sourceId);
            }
        });
        this.state = {};
    }
    componentDidMount() {
        this.waitForMap();
    }
    updateFromConfig() {
        if (this.state.tileSet?.id !== Config.map.layerId || this.state.config !== Config.map.config) {
            this._loadingConfig = this._loadingConfig.then(() => this.loadConfig());
        }
        this.debugMap.setPurple(Config.map.debug['debug.background'] === 'magenta');
        this.debugMap.adjustRaster(this.props.map, 'osm', Config.map.debug['debug.layer.osm']);
        this.debugMap.adjustRaster(this.props.map, 'linz-aerial', Config.map.debug['debug.layer.linz-aerial']);
        void this.debugMap.adjustVector(this.props.map, Config.map.debug['debug.layer.linz-topographic']);
        this.setVectorShown(Config.map.debug['debug.source'], debugTypes.source);
        this.setVectorShown(Config.map.debug['debug.cog'], debugTypes.cog);
        this.setVectorShown(Config.map.debug['debug.capture-area'], debugTypes['capture-area']);
        this.setTerrainShown(Config.map.debug['debug.terrain']);
        this.setHillShadeShown(Config.map.debug['debug.hillshade']);
        this.setVisibleSource(Config.map.debug['debug.layer']);
        this.renderWMTS();
    }
    async loadConfig() {
        const tileSetId = Config.map.layerId;
        if (this.state.tileSet?.id === tileSetId)
            return;
        return ConfigData.getTileSet(tileSetId).then((tileSet) => {
            this.setState({ tileSet, config: Config.map.config });
            if (tileSet == null)
                return;
            if (tileSet.layers.length !== 1)
                return;
            const projectionCode = Config.map.tileMatrix.projection.code;
            const imageryId = tileSet.layers[0][projectionCode];
            if (imageryId == null)
                return;
            void this.debugMap.fetchSourceLayer(imageryId, debugTypes.cog).then((cog) => {
                this.setState({ isCog: cog != null });
            });
            void this.debugMap.fetchSourceLayer(imageryId, debugTypes['capture-area']).then((c) => {
                this.setState({ hasCaptureArea: c != null });
            });
            return ConfigData.getImagery(tileSetId, imageryId).then((imagery) => {
                this.setState({ imagery, config: Config.map.config });
            });
        });
    }
    render() {
        if (Config.map.debug['debug.screenshot'])
            return null;
        const title = this.state.imagery?.title;
        const demSources = this.getSourcesIds('raster-dem');
        return (_jsxs("div", { className: "debug", children: [_jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label", children: "Id" }), _jsx("div", { className: "debug__value", children: Config.map.layerId })] }), title == null ? null : (_jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label", children: "Title" }), _jsx("div", { className: "debug__value", children: title })] })), this.renderWMTS(), _jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label", children: "TileMatrix" }), _jsx("div", { className: "debug__value", children: Config.map.tileMatrix.identifier })] }), this.renderSliders(), this.renderPurple(), this.renderCogToggle(), this.renderSourceToggle(), this.renderCaptureAreaToggle(), this.renderTileToggle(), this.renderRasterSourceDropdown(), this.renderDemSourceDropdown(demSources), this.renderDemHillShadeSourceDropdown(demSources)] }));
    }
    getWMTSLink() {
        return WindowUrl.toTileUrl({
            urlType: "wmts" /* MapOptionType.Wmts */,
            tileMatrix: Config.map.tileMatrix,
            layerId: Config.map.layerId,
            config: Config.map.config,
            date: Config.map.filter.date,
        });
    }
    renderWMTS() {
        const imagery = this.state.imagery;
        return (_jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label" }), _jsxs("div", { className: "debug__value", children: [Config.map.tileMatrix.projection.toEpsgString(), " - ", _jsx("a", { href: this.getWMTSLink(), children: "WMTS " }), imagery == null ? null : _jsxs("div", { title: "Number of tiffs in imagery", children: [" - ", imagery.files.length, " Tiffs"] })] })] }));
    }
    renderPurple() {
        if (Config.map.debug['debug.screenshot'])
            return;
        return (_jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label", children: "Purple" }), _jsx("input", { type: "checkbox", onChange: this.debugMap.togglePurple, checked: Config.map.debug['debug.background'] === 'magenta' })] }));
    }
    renderTileToggle() {
        return (_jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label", children: "Tile Boundaries" }), _jsx("input", { type: "checkbox", onChange: this.toggleTileBoundary, checked: Config.map.debug['debug.tile'] })] }));
    }
    setHillShadeShown(sourceId) {
        Config.map.setDebug('debug.hillshade', sourceId);
        const map = this.props.map;
        const isTurnOff = sourceId === 'off' || sourceId == null;
        const currentLayer = map.getLayer(HillShadeLayerId);
        if (isTurnOff) {
            if (currentLayer)
                map.removeLayer(HillShadeLayerId);
            return;
        }
        const hillShadeSourceId = `${HillShadePrefix}${sourceId}`;
        if (currentLayer?.source === hillShadeSourceId)
            return;
        // Hillshading from an existing raster-dem source gives very mixed results and looks very blury
        // so add a new source layer to generate from
        const existingSource = map.getSource(hillShadeSourceId);
        if (existingSource == null) {
            const source = map.getSource(sourceId);
            if (source?.type !== 'raster-dem') {
                // Source cannot be found, config is invalid
                Config.map.setDebug('debug.hillshade', null);
                return;
            }
            map.addSource(hillShadeSourceId, {
                ...source.serialize(),
                type: 'raster-dem',
                id: undefined,
            });
        }
        if (currentLayer)
            map.removeLayer(HillShadeLayerId);
        map.addLayer({
            id: HillShadeLayerId,
            type: 'hillshade',
            source: hillShadeSourceId,
            paint: { 'hillshade-shadow-color': '#040404' },
        });
    }
    /**
     * Add a terrain source that points to production elevation data for debug
     */
    ensureElevationProd() {
        const map = this.props.map;
        // Enable default linz-elevation terrain
        if (map.getSource(elevationProdId) == null) {
            const url = WindowUrl.toTileUrl({
                urlType: "raster" /* MapOptionType.TileRaster */,
                tileMatrix: Config.map.tileMatrix,
                layerId: 'elevation',
                pipeline: 'terrain-rgb',
                imageFormat: 'png',
            });
            map.addSource(elevationProdId, {
                type: 'raster-dem',
                tiles: [url],
                tileSize: 256,
            });
        }
    }
    setTerrainShown(sourceId) {
        this.ensureElevationProd();
        // Avoid to set debug.terrain for dynamic source, this will return null from the getStyle API with random terrain parameter
        Config.map.setDebug('debug.terrain', sourceId);
        const map = this.props.map;
        const isTurnOff = sourceId === 'off' || sourceId == null;
        const currentTerrain = map.getTerrain();
        if (isTurnOff) {
            map.setTerrain(null);
            return;
        }
        const target = getTerrainForSource(sourceId, Config.map.tileMatrix);
        // no changes
        if (currentTerrain?.source === sourceId && currentTerrain?.exaggeration === target.exaggeration)
            return;
        const terrainSource = this.props.map.getSource(sourceId);
        if (terrainSource == null) {
            // Source cannot be found, config is invalid
            Config.map.setDebug('debug.terrain', null);
            return;
        }
        map.setTerrain(target);
    }
    setVisibleSource(sourceId) {
        Config.map.setDebug('debug.layer', sourceId);
        if (sourceId == null)
            return;
        const map = this.props.map;
        const currentLayer = map.getLayer(Config.map.styleId);
        if (currentLayer?.source === sourceId)
            return;
        const layer = { id: Config.map.styleId, type: 'raster', source: sourceId };
        map.removeLayer(Config.map.styleId);
        map.addLayer(layer, map.getLayer(HillShadeLayerId) ? HillShadeLayerId : undefined);
    }
    renderSourceToggle() {
        if (this.state.imagery == null)
            return null;
        return (_jsxs(Fragment, { children: [_jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label", children: _jsx("a", { onClick: this.downloadSource, href: "#", title: "Source geojson", children: "Source" }) }), _jsx("input", { type: "checkbox", onChange: this.toggleSource, checked: Config.map.debug['debug.source'] })] }), this.state.featureSourceId == null ? null : (_jsxs("div", { className: "debug__info", title: String(this.state.featureSourceName), children: [_jsx("label", { className: "debug__label", children: "SourceId" }), String(this.state.featureSourceName).split('/').pop()] }))] }));
    }
    renderCogToggle() {
        if (this.state.imagery == null)
            return null;
        const cogLocation = WindowUrl.toImageryUrl(this.state.imagery.id, debugTypes.cog.file);
        if (!this.state.isCog)
            return;
        return (_jsxs(Fragment, { children: [_jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label", children: _jsx("a", { href: cogLocation, title: "Source geojson", children: "Cogs" }) }), _jsx("input", { type: "checkbox", onChange: this.toggleCogs, checked: Config.map.debug['debug.cog'] })] }), this.state.featureCogId == null ? null : (_jsxs("div", { className: "debug__info", title: String(this.state.featureCogName), children: [_jsx("label", { className: "debug__label", children: "CogId" }), String(this.state.featureCogName).split('/').pop()] }))] }));
    }
    renderCaptureAreaToggle() {
        if (this.state.imagery == null)
            return null;
        const location = WindowUrl.toImageryUrl(this.state.imagery.id, debugTypes['capture-area'].file);
        if (!this.state.hasCaptureArea)
            return;
        return (_jsx(Fragment, { children: _jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label", children: _jsx("a", { href: location, title: "Capture Area geojson", children: "Capture Area" }) }), _jsx("input", { type: "checkbox", onChange: this.toggleCaptureArea, checked: Config.map.debug['debug.capture-area'] })] }) }));
    }
    getSourcesIds(type) {
        const style = this.props.map.getStyle();
        if (type === 'raster-dem') {
            return Object.keys(style.sources).filter((id) => !id.startsWith(HillShadePrefix) && style.sources[id].type === type);
        }
        else if (type === 'raster') {
            return Object.keys(style.sources).filter((id) => id.startsWith('basemaps') && style.sources[id].type === type);
        }
        else
            throw new Error('Only support to get raster or raster-dem sources for debug dropdown.');
    }
    renderRasterSourceDropdown() {
        // Disable for vector map
        if (Config.map.isVector)
            return;
        // Disable dropdown if only one source
        const sourceIds = this.getSourcesIds('raster');
        if (sourceIds.length <= 1)
            return;
        // Get default source
        const selectedSource = this.props.map.getLayer(Config.map.styleId)?.source ?? 'off';
        return debugSourceDropdown({
            label: 'Layer',
            onChange: this.selectRasterSource,
            value: selectedSource,
            options: [...sourceIds, 'off'],
        });
    }
    renderDemSourceDropdown(sourceIds) {
        if (sourceIds.length === 0)
            return;
        return debugSourceDropdown({
            label: 'Elevation',
            onChange: this.selectElevation,
            value: this.props.map.getTerrain()?.source ?? 'off',
            options: ['off', ...sourceIds],
        });
    }
    renderDemHillShadeSourceDropdown(sourceIds) {
        if (sourceIds.length === 0)
            return;
        return debugSourceDropdown({
            label: 'Hillshade',
            onChange: this.selectHillShade,
            value: this.props.map.getLayer(HillShadeLayerId)?.source.replace(HillShadePrefix, '') ?? 'off',
            options: ['off', ...sourceIds],
        });
    }
    renderSliders() {
        // Disable the sliders for screenshots
        if (Config.map.debug['debug.screenshot'])
            return;
        // Only 3857 currently works with OSM/Topographic map
        if (Config.map.tileMatrix.identifier !== GoogleTms.identifier) {
            return (_jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label", children: "LINZ Aerial" }), debugSlider('linz-aerial', this.debugMap.adjustLinzAerial)] }));
        }
        return (_jsxs(Fragment, { children: [_jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label", children: "OSM" }), debugSlider('osm', this.debugMap.adjustOsm)] }), _jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label", children: "Topographic" }), debugSlider('linz-topographic', this.debugMap.adjustTopographic)] }), _jsxs("div", { className: "debug__info", children: [_jsx("label", { className: "debug__label", children: "LINZ Aerial" }), debugSlider('linz-aerial', this.debugMap.adjustLinzAerial)] })] }));
    }
    trackMouseMove(layerId, type) {
        const sourceId = `${layerId}_${type.name}`;
        const layerFillId = `${sourceId}_fill`;
        const map = this.props.map;
        let lastFeatureId;
        // State names are `featureSource` or `featureCog` etc.
        const stateName = `feature${upperCaseFirstChar(type.name)}`;
        // Onclick copy the location into the clipboard
        map.on('click', layerFillId, (e) => {
            const features = e.features;
            if (features == null || features.length === 0)
                return;
            const firstFeature = features[0];
            const location = (firstFeature?.properties?.['location'] ?? firstFeature.properties?.['name']);
            if (location == null)
                return;
            void navigator.clipboard.writeText(location).then(() => {
                const div = document.createElement('div');
                div.innerText = `Copied ${location}`;
                div.className = 'toast-message';
                div.title = location;
                document.body.appendChild(div);
                setTimeout(() => div.remove(), 2500);
            });
        });
        map.on('mousemove', layerFillId, (e) => {
            const features = e.features;
            if (features == null || features.length === 0)
                return;
            const firstFeature = features[0];
            if (firstFeature.id === lastFeatureId)
                return;
            if (lastFeatureId != null)
                map.setFeatureState({ source: sourceId, id: lastFeatureId }, { hover: false });
            lastFeatureId = firstFeature.id;
            this.setState({
                [`${stateName}Id`]: lastFeatureId,
                [`${stateName}Name`]: firstFeature.properties?.['name'],
            });
            map.setFeatureState({ source: sourceId, id: lastFeatureId }, { hover: true });
        });
        map.on('mouseleave', layerFillId, () => {
            if (lastFeatureId == null)
                return;
            map.setFeatureState({ source: sourceId, id: lastFeatureId }, { hover: false });
            lastFeatureId = undefined;
            this.setState({ [`${stateName}Id`]: undefined, [`${stateName}Name`]: undefined });
        });
    }
    setVectorShown(isShown, type) {
        const map = this.props.map;
        map.showTileBoundaries = Config.map.debug['debug.tile'];
        const layerId = Config.map.layerId;
        const sourceId = `${layerId}_${type.name}`;
        const layerFillId = `${sourceId}_fill`;
        const layerLineId = `${sourceId}_line`;
        const layerLineBlack = `${sourceId}_line_black`;
        if (isShown === false) {
            if (map.getLayer(layerLineId) == null)
                return;
            map.removeLayer(layerFillId);
            map.removeLayer(layerLineId);
            map.removeLayer(layerLineBlack);
            return;
        }
        if (map.getLayer(layerLineId) != null)
            return;
        if (this.state.imagery == null)
            return;
        void this.debugMap.loadSourceLayer(this.props.map, layerId, this.state.imagery, type).then(() => {
            if (map.getLayer(layerLineId) != null)
                return;
            // Fill is needed to make the mouse move work even though it has opacity 0
            if (type.fill) {
                // fillable line polygons
                map.addLayer({
                    id: layerFillId,
                    type: 'fill',
                    source: sourceId,
                    paint: {
                        'fill-opacity': ['case', ['boolean', ['feature-state', 'hover'], false], 0.25, 0],
                        'fill-color': type.color,
                    },
                });
                this.trackMouseMove(layerId, type);
                map.addLayer({
                    id: layerLineId,
                    type: 'line',
                    source: sourceId,
                    paint: {
                        'line-color': type.color,
                        'line-opacity': ['case', ['boolean', ['feature-state', 'hover'], false], 1, 0.5],
                        'line-width': ['case', ['boolean', ['feature-state', 'hover'], false], 2, 1],
                    },
                });
            }
            else {
                // Add a black double weighted line behind the layer line
                map.addLayer({
                    id: layerLineBlack,
                    type: 'line',
                    source: sourceId,
                    paint: {
                        'line-color': '#000000',
                        'line-width': 4,
                    },
                });
                map.addLayer({
                    id: layerLineId,
                    type: 'line',
                    source: sourceId,
                    paint: {
                        'line-color': type.color,
                        'line-width': 2,
                    },
                });
            }
        });
    }
}
/**
 * Terrain needs to be exaggerated based on the tile matrix
 *
 * NZTM2000 offsets zoom levels by 2, which causes terrain to appear too flat
 *
 * @param sourceId maplibre source layer id
 * @param projection current projection
 * @returns
 */
function getTerrainForSource(sourceId, tileMatrix) {
    return {
        source: sourceId,
        exaggeration: DefaultExaggeration[tileMatrix.identifier] ?? DefaultExaggeration[GoogleTms.identifier],
    };
}
//# sourceMappingURL=debug.js.map