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 maplibre from 'maplibre-gl';
import { Component } from 'react';
import { MapAttribution } from '../attribution.js';
import { Config } from '../config.js';
import { getTileGrid, locationTransform } from '../tile.matrix.js';
import { Debug } from './debug.js';
import { MapLabelControl } from './map.label.js';
import { MapSwitcher } from './map.switcher.js';
/**
 * Map loading in maplibre is weird, the on('load') event is different to 'loaded'
 * this function waits until the map.loaded() function is true before being run.
 */
export function onMapLoaded(map, cb) {
    if (map.loaded())
        return cb();
    setTimeout(() => onMapLoaded(map, cb), 100);
}
export class Basemaps extends Component {
    constructor() {
        super(...arguments);
        Object.defineProperty(this, "map", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "el", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "mapAttr", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        /** Ignore the location updates */
        Object.defineProperty(this, "ignoreNextLocationUpdate", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: false
        });
        Object.defineProperty(this, "controlScale", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "controlTerrain", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "controlGeo", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "updateLocation", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: () => {
                if (this.ignoreNextLocationUpdate) {
                    this.ignoreNextLocationUpdate = false;
                    return;
                }
                const location = Config.map.location;
                this.map.setZoom(location.zoom);
                this.map.setCenter([location.lon, location.lat]);
                if (location.bearing != null)
                    this.map.setBearing(location.bearing);
                if (location.pitch != null)
                    this.map.setPitch(location.pitch);
            }
        });
        Object.defineProperty(this, "updateTerrainFromEvent", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: () => {
                const terrain = this.map.getTerrain();
                Config.map.setTerrain(terrain?.source ?? null);
            }
        });
        Object.defineProperty(this, "updateBounds", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: (bounds) => {
                if (Config.map.tileMatrix !== GoogleTms) {
                    // Transform bounds to current tileMatrix
                    const lngLatBounds = maplibre.LngLatBounds.convert(bounds);
                    const upperLeft = lngLatBounds.getNorthEast();
                    const lowerRight = lngLatBounds.getSouthWest();
                    const zoom = this.map.getZoom();
                    const upperLeftLocation = locationTransform({ lat: upperLeft.lat, lon: upperLeft.lng, zoom }, Config.map.tileMatrix, GoogleTms);
                    const lowerRightLocation = locationTransform({ lat: lowerRight.lat, lon: lowerRight.lng, zoom }, Config.map.tileMatrix, GoogleTms);
                    bounds = [
                        [upperLeftLocation.lon, upperLeftLocation.lat],
                        [lowerRightLocation.lon, lowerRightLocation.lat],
                    ];
                }
                this.map.fitBounds(bounds);
            }
        });
        Object.defineProperty(this, "updateStyle", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: () => {
                this.ensureGeoControl();
                this.ensureScaleControl();
                this.ensureElevationControl();
                const tileGrid = getTileGrid(Config.map.tileMatrix.identifier);
                const style = tileGrid.getStyle(Config.map);
                this.map.setStyle(style);
                if (Config.map.tileMatrix !== GoogleTms) {
                    this.map.setMaxBounds([-179.9, -85, 179.9, 85]);
                }
                else {
                    this.map.setMaxBounds();
                }
                // TODO check and only update when Config.map.layer changes.
                this.forceUpdate();
            }
        });
        Object.defineProperty(this, "_events", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: []
        });
        Object.defineProperty(this, "updateUrlTimer", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: null
        });
        Object.defineProperty(this, "onRender", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: () => {
                if (this.updateUrlTimer != null)
                    return;
                this.updateUrlTimer = setTimeout(() => this.setLocationUrl(), 1000);
            }
        });
    }
    /**
     * Only show the geocontrol on GoogleTMS
     * As it does not work with the projection logic we are currently using
     */
    ensureGeoControl() {
        if (Config.map.debug['debug.screenshot'])
            return;
        if (Config.map.tileMatrix === GoogleTms) {
            if (this.controlGeo != null)
                return;
            this.controlGeo = new maplibre.GeolocateControl({});
            this.map.addControl(this.controlGeo, 'top-left');
        }
        else {
            if (this.controlGeo == null)
                return;
            this.map.removeControl(this.controlGeo);
        }
    }
    /**
     * Only show the elevation on GoogleTMS, as there is no data for other projections yet
     */
    ensureElevationControl() {
        if (Config.map.debug['debug.screenshot'])
            return;
        if (Config.map.isDebug)
            return;
        if (this.controlTerrain != null)
            return;
        // Try to find terrain source and add to the control
        for (const [key, source] of Object.entries(this.map.getStyle().sources)) {
            if (source.type === 'raster-dem') {
                this.controlTerrain = new maplibre.TerrainControl({
                    source: key,
                    exaggeration: DefaultExaggeration[Config.map.tileMatrix.identifier] ?? DefaultExaggeration[GoogleTms.identifier],
                });
                this.map.addControl(this.controlTerrain, 'top-left');
                return;
            }
        }
        if (this.controlTerrain == null)
            return;
        this.map.removeControl(this.controlTerrain);
        this.controlTerrain = null;
    }
    /**
     * Only show the scale on GoogleTMS
     * As it does not work with the projection logic we are currently using
     */
    ensureScaleControl() {
        if (Config.map.debug['debug.screenshot'])
            return;
        if (Config.map.tileMatrix === GoogleTms) {
            if (this.controlScale != null)
                return;
            this.controlScale = new maplibre.ScaleControl({});
            this.map.addControl(this.controlScale, 'bottom-right');
        }
        else {
            if (this.controlScale == null)
                return;
            this.map.removeControl(this.controlScale);
            this.controlScale = null;
        }
    }
    componentDidMount() {
        // Force the URL to be read before loading the map
        Config.map.updateFromUrl();
        this.el = document.getElementById('map');
        if (this.el == null)
            throw new Error('Unable to find #map element');
        const cfg = Config.map;
        const tileGrid = getTileGrid(cfg.tileMatrix.identifier);
        const style = tileGrid.getStyle(Config.map);
        const location = locationTransform(cfg.location, cfg.tileMatrix, GoogleTms);
        this.map = new maplibre.Map({
            container: this.el,
            style,
            maxPitch: LocationUrl.PitchMaxDegrees, // allow users to see more of the horizon, above 60 is experimental
            center: [location.lon, location.lat], // starting position [lon, lat]
            zoom: location.zoom, // starting zoom
            bearing: cfg.location.bearing ?? 0,
            pitch: cfg.location.pitch ?? 0,
            attributionControl: false,
        });
        this.mapAttr = new MapAttribution();
        this.map.addControl(this.mapAttr, 'bottom-right');
        if (Config.map.debug['debug.screenshot'] !== true) {
            const nav = new maplibre.NavigationControl({ visualizePitch: true });
            this.map.addControl(nav, 'top-left');
            if (!Config.map.isDebug)
                this.map.addControl(new maplibre.FullscreenControl({ container: this.el }));
            this.map.addControl(new MapLabelControl(), 'top-left');
            this.controlScale = new maplibre.ScaleControl({});
            this.map.addControl(this.controlScale, 'bottom-right');
        }
        this.map.on('render', this.onRender);
        onMapLoaded(this.map, () => {
            this._events.push(Config.map.on('location', this.updateLocation), Config.map.on('tileMatrix', this.updateStyle), Config.map.on('layer', this.updateStyle), Config.map.on('labels', this.updateStyle), Config.map.on('bounds', this.updateBounds));
            this.map.on('terrain', this.updateTerrainFromEvent);
            this.updateStyle();
            // Need to ensure the debug layer has access to the map
            this.forceUpdate();
        });
    }
    componentWillUnmount() {
        if (this.map)
            this.map.remove();
        for (const unbind of this._events)
            unbind();
        this._events = [];
    }
    render() {
        const isLayerSwitcherEnabled = Config.map.tileMatrix === GoogleTms && !Config.map.isDebug;
        return (_jsxs("div", { style: { flex: 1, position: 'relative' }, children: [_jsx("div", { id: "map", style: { width: '100%', height: '100%' } }), this.map && Config.map.isDebug ? _jsx(Debug, { map: this.map }) : undefined, isLayerSwitcherEnabled ? _jsx(MapSwitcher, {}) : undefined] }));
    }
    /** Update the window.location with the current location information */
    setLocationUrl() {
        this.updateUrlTimer = null;
        const location = Config.map.getLocation(this.map);
        this.ignoreNextLocationUpdate = true;
        Config.map.setLocation(location);
        const path = LocationUrl.toSlug(location);
        const url = new URL(window.location.href);
        url.pathname = path;
        url.hash = ''; // Ensure the hash is removed, to ensure the redirect from #@location to /@location
        window.history.replaceState(null, '', url);
    }
}
//# sourceMappingURL=map.js.map