import { Epsg, EpsgCode, GoogleTms, LocationUrl, Nztm2000QuadTms, Nztm2000Tms, TileMatrixSets, } from '@basemaps/geo';
import { Emitter } from '@servie/events';
import { ConfigDebug, DebugDefaults } from './config.debug.js';
import { Config } from './config.js';
import { locationTransform, mapToBoundingBox } from './tile.matrix.js';
import { ensureBase58, WindowUrl } from './url.js';
/** Default center point if none provided */
const DefaultCenter = {
    [GoogleTms.identifier]: { lat: -41.88999621, lon: 174.04924373, zoom: 5 },
    [Nztm2000Tms.identifier]: { lat: -41.277848, lon: 174.6763921, zoom: 3 },
    [Nztm2000QuadTms.identifier]: { lat: -41.88999621, lon: 174.04924373, zoom: 3 },
};
export class MapConfig extends Emitter {
    constructor() {
        super(...arguments);
        Object.defineProperty(this, "style", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: null
        });
        Object.defineProperty(this, "layerId", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'aerial'
        });
        Object.defineProperty(this, "tileMatrix", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: GoogleTms
        });
        Object.defineProperty(this, "config", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: null
        });
        Object.defineProperty(this, "debug", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: { ...DebugDefaults }
        });
        Object.defineProperty(this, "visibleLayers", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: null
        });
        Object.defineProperty(this, "filter", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: { date: { before: undefined } }
        });
        Object.defineProperty(this, "terrain", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: null
        });
        /** Should labels be added to the current layer */
        Object.defineProperty(this, "labels", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: false
        });
        Object.defineProperty(this, "pipeline", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: null
        });
        Object.defineProperty(this, "imageFormat", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: null
        });
        Object.defineProperty(this, "_layers", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        /** Map location in WGS84 */
        Object.defineProperty(this, "_location", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
    }
    get layers() {
        if (this._layers == null)
            this._layers = loadAllLayers();
        return this._layers;
    }
    get isDebug() {
        return this.debug.debug === true;
    }
    get location() {
        if (this._location == null) {
            window.addEventListener('popstate', () => {
                const location = {
                    ...DefaultCenter[this.tileMatrix.identifier],
                    // TODO 2023-09 location.hash for storing basemaps locations
                    // is deprecated we should remove this at some stage
                    ...LocationUrl.fromSlug(window.location.hash),
                    ...LocationUrl.fromSlug(window.location.pathname),
                };
                this.setLocation(location);
            });
            this.updateFromUrl();
            this._location = {
                ...DefaultCenter[this.tileMatrix.identifier],
                ...LocationUrl.fromSlug(window.location.hash),
                ...LocationUrl.fromSlug(window.location.pathname),
            };
        }
        return this._location;
    }
    /** Map location in tileMatrix projection */
    get transformedLocation() {
        return this.transformLocation(this.location.lat, this.location.lon, this.location.zoom);
    }
    get isVector() {
        return this.layerId === 'topographic';
    }
    /** Key to reference the combined layer & style  */
    get layerKey() {
        if (this.style == null)
            return this.layerId;
        return `${this.layerId}::${this.style}`;
    }
    /** Key to reference combined layer & style & tile matrix */
    get layerKeyTms() {
        return `${this.layerKey}::${this.tileMatrix.identifier}`;
    }
    /** Used as source and layer id in the Style JSON for a given layer ID */
    get styleId() {
        return Config.map.style == null ? `basemaps-${Config.map.layerId}` : `basemaps-${Config.map.style}`;
    }
    updateFromUrl(search = window.location.search) {
        const urlParams = new URLSearchParams(search);
        const style = urlParams.get('s') ?? urlParams.get('style');
        const config = urlParams.get('c') ?? urlParams.get('config');
        const layerId = urlParams.get('i') ?? style ?? 'aerial';
        const terrain = urlParams.get('t') ?? urlParams.get('terrain');
        const labels = urlParams.get('labels');
        const projectionParam = (urlParams.get('p') ?? urlParams.get('tileMatrix') ?? GoogleTms.identifier).toLowerCase();
        let tileMatrix = TileMatrixSets.All.find((f) => f.identifier.toLowerCase() === projectionParam);
        if (tileMatrix == null)
            tileMatrix = TileMatrixSets.get(Epsg.parse(projectionParam) ?? Epsg.Google);
        if (tileMatrix.identifier === Nztm2000Tms.identifier)
            tileMatrix = Nztm2000QuadTms;
        const debugChanged = ConfigDebug.fromUrl(this.debug, urlParams);
        if (debugChanged)
            this.emit('change');
        const previousUrl = MapConfig.toUrl(this);
        this.config = config;
        this.terrain = terrain;
        this.style = style ?? null;
        this.layerId = layerId.startsWith('im_') ? layerId.slice(3) : layerId;
        this.tileMatrix = tileMatrix;
        if (labels == null) {
            this.labels = layerId === 'aerial' && this.isDebug === false;
        }
        else {
            this.labels = labels !== 'false';
        }
        if (this.layerId === 'topographic' && this.style == null)
            this.style = 'topographic';
        this.emit('tileMatrix', this.tileMatrix);
        this.emit('layer', this.layerId, this.style, this.pipeline, this.imageFormat);
        if (previousUrl !== MapConfig.toUrl(this))
            this.emit('change');
    }
    static toUrl(opts) {
        const urlParams = new URLSearchParams();
        if (opts.style)
            urlParams.append('style', opts.style);
        if (opts.layerId !== 'aerial')
            urlParams.append('i', opts.layerId);
        if (opts.tileMatrix.identifier !== GoogleTms.identifier)
            urlParams.append('tileMatrix', opts.tileMatrix.identifier);
        // Config by far the longest so make it the last parameter
        if (opts.config)
            urlParams.append('config', ensureBase58(opts.config));
        // We don't need to set terrain parameter for debug, as we got debug.terrain parameter to replace
        if (opts.terrain && !opts.isDebug)
            urlParams.append('terrain', opts.terrain);
        if (opts.labels)
            urlParams.append('labels', 'true');
        ConfigDebug.toUrl(opts.debug, urlParams);
        return urlParams.toString();
    }
    toTileUrl(urlType, tileMatrix = this.tileMatrix, layerId = this.layerId, style = this.style, config = this.config, date = this.filter.date, pipeline = this.pipeline, terrain = this.terrain, imageFormat = this.imageFormat) {
        return WindowUrl.toTileUrl({ urlType, tileMatrix, layerId, style, config, date, pipeline, terrain, imageFormat });
    }
    getLocation(map) {
        const center = map.getCenter();
        if (center == null)
            throw new Error('Invalid Map location');
        const zoom = Math.floor((map.getZoom() ?? 0) * 10e3) / 10e3;
        const location = Config.map.transformLocation(center.lat, center.lng, zoom);
        const bearing = map.getBearing();
        const pitch = map.getPitch();
        if (bearing !== 0)
            location.bearing = bearing;
        if (pitch !== 0)
            location.pitch = pitch;
        const bounds = mapToBoundingBox(map, zoom, Config.map.tileMatrix);
        location.extent = bounds;
        return location;
    }
    transformLocation(lat, lon, zoom) {
        return locationTransform({ lat, lon, zoom }, GoogleTms, this.tileMatrix);
    }
    setLocation(l) {
        if (l.lat === this.location.lat &&
            l.lon === this.location.lon &&
            l.zoom === this.location.zoom &&
            l.bearing === this.location.bearing &&
            l.pitch === this.location.pitch &&
            sameExtent(l, this.location)) {
            return;
        }
        this.location.lat = l.lat;
        this.location.lon = l.lon;
        this.location.zoom = l.zoom;
        this.location.bearing = l.bearing;
        this.location.pitch = l.pitch;
        this.location.extent = l.extent;
        this.emit('location', this.location);
        this.emit('change');
    }
    setTerrain(terrain) {
        if (this.terrain === terrain)
            return;
        this.terrain = terrain;
        window.history.pushState(null, '', `?${MapConfig.toUrl(Config.map)}`);
        this.emit('change');
    }
    setTileMatrix(tms) {
        if (this.tileMatrix.identifier === tms.identifier)
            return;
        this.emit('tileMatrix', this.tileMatrix);
        this.emit('change');
    }
    setFilterDateRange(dateRange) {
        if (this.filter.date === dateRange)
            return;
        this.filter.date = dateRange;
        this.emit('filter', this.filter);
        this.emit('change');
    }
    setLabels(labels) {
        if (this.labels === labels)
            return;
        this.labels = labels;
        window.history.pushState(null, '', `?${MapConfig.toUrl(Config.map)}`);
        this.emit('labels', this.labels);
        this.emit('change');
    }
    setLayerId(layer, style = null, pipeline = null, imageFormat = null) {
        if (this.layerId === layer &&
            this.style === style &&
            this.pipeline === pipeline &&
            this.imageFormat === imageFormat) {
            return;
        }
        this.layerId = layer;
        this.style = style;
        this.imageFormat = imageFormat;
        this.pipeline = pipeline;
        this.emit('layer', this.layerId, this.style, this.pipeline, this.imageFormat);
        this.emit('change');
    }
    setDebug(key, value = DebugDefaults[key]) {
        if (this.debug[key] === value)
            return;
        this.debug[key] = value;
        this.emit('change');
    }
}
/**
 * Are two location's extents the same
 * @param a location A
 * @param b location B/
 * @returns true if the extents are exactly the same false otherwise
 */
function sameExtent(a, b) {
    if (a.extent == null && b.extent == null)
        return true;
    if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
        for (let i = 0; i < a.length; i++) {
            if (a[i] !== b[i])
                return false;
        }
        return true;
    }
    return false;
}
async function loadAllLayers() {
    const output = new Map();
    const res = await fetch(WindowUrl.toBaseWmts());
    if (!res.ok)
        return output;
    const dom = new DOMParser();
    const xmlDoc = dom.parseFromString(await res.text(), 'text/xml');
    const layers = xmlDoc.getElementsByTagName('Layer');
    const allLayers = [];
    for (let i = 0; i < layers.length; i++) {
        const layer = layers.item(i);
        if (layer == null)
            continue;
        const title = layer.getElementsByTagName('ows:Title').item(0)?.textContent;
        const id = layer.getElementsByTagName('ows:Identifier').item(0)?.textContent;
        if (title == null || id == null)
            continue;
        if (title === 'aerial')
            continue;
        const category = layer.getElementsByTagName('ows:Keyword').item(0)?.textContent;
        const boundEl = layer.getElementsByTagName('ows:WGS84BoundingBox').item(0);
        const upperLeft = boundEl?.getElementsByTagName('ows:UpperCorner').item(0)?.textContent?.split(' ').map(Number);
        const lowerRight = boundEl?.getElementsByTagName('ows:LowerCorner').item(0)?.textContent?.split(' ').map(Number);
        const tmsTags = layer.getElementsByTagName('TileMatrixSet');
        const projections = new Set();
        for (let j = 0; j < tmsTags.length; j++) {
            const epsg = tmsIdToEpsg(tmsTags.item(j)?.textContent ?? '');
            if (epsg == null)
                continue;
            projections.add(epsg.code);
        }
        if (upperLeft == null || lowerRight == null || upperLeft.length !== 2)
            continue;
        allLayers.push({
            id,
            title: title.replace('aerial ', ''),
            upperLeft,
            lowerRight,
            projections,
            category,
        });
    }
    allLayers.sort((a, b) => a.title.localeCompare(b.title));
    addDefaultLayers(output);
    for (const l of allLayers)
        output.set(l.id, l);
    return output;
}
/**
 * The server currently has no way of telling the client the full list of tilesets it could use
 * so hard code a few default tilesets with their projections
 *
 * @param output layers list to add to
 */
function addDefaultLayers(output) {
    const layers = [
        {
            id: 'aerial',
            title: 'Aerial Imagery',
            projections: new Set([EpsgCode.Nztm2000, EpsgCode.Google]),
            category: 'Basemaps',
        },
        {
            id: 'topographic::topographic',
            title: 'Topographic',
            projections: new Set([EpsgCode.Google]),
            category: 'Basemaps',
        },
        {
            id: 'topographic::topolite',
            title: 'Topolite',
            projections: new Set([EpsgCode.Google]),
            category: 'Basemaps',
        },
        {
            id: 'topo-raster::topo-raster',
            title: 'NZ Topo Gridless Maps',
            projections: new Set([EpsgCode.Nztm2000, EpsgCode.Google]),
            category: 'Basemaps',
        },
        {
            id: 'elevation',
            title: 'Elevation',
            projections: new Set([EpsgCode.Google]),
            category: 'Basemaps',
            pipeline: 'terrain-rgb',
            imageFormat: 'png',
        },
        {
            id: 'scanned-aerial-imagery-pre-1990-01-01',
            title: 'Scanned Aerial Imagery pre 1 January 1990',
            projections: new Set([EpsgCode.Nztm2000, EpsgCode.Google]),
            category: 'Scanned Aerial Imagery Basemaps',
        },
        {
            id: 'scanned-aerial-imagery-post-1989-12-31',
            title: 'Scanned Aerial Imagery post 31 December 1989',
            projections: new Set([EpsgCode.Nztm2000, EpsgCode.Google]),
            category: 'Scanned Aerial Imagery Basemaps',
        },
    ];
    for (const l of layers)
        output.set(l.id, l);
}
/** Lookup a projection from either "EPSG:3857" or "WebMercatorQuad" */
function tmsIdToEpsg(id) {
    if (id.toLowerCase().startsWith('epsg'))
        return Epsg.parse(id);
    const tms = TileMatrixSets.find(id);
    if (tms == null)
        return null;
    return tms.projection;
}
//# sourceMappingURL=config.map.js.map