import { standardizeLayerName } from '@basemaps/config';
import { Bounds, GoogleTms } from '@basemaps/geo';
import { Projection, toQueryString, V } from '@basemaps/shared';
import { ImageFormatOrder } from '@basemaps/tiler';
const CapabilitiesAttrs = {
    xmlns: 'http://www.opengis.net/wmts/1.0',
    'xmlns:ows': 'http://www.opengis.net/ows/1.1',
    'xmlns:xlink': 'http://www.w3.org/1999/xlink',
    'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
    'xmlns:gml': 'http://www.opengis.net/gml',
    'xsi:schemaLocation': 'http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd',
    version: '1.0.0',
};
function wgs84Extent(tileMatrix, bbox) {
    return Projection.get(tileMatrix).boundsToWgs84BoundingBox(bbox);
}
/** Number of decimal places to use in lat lng */
const LngLatPrecision = 6;
const MeterPrecision = 4;
function formatCoords(x, precision) {
    return Number(x.toFixed(precision)).toString();
}
/** Format a bounding box XY as `${x} ${y}` while restricting to precision decimal places */
function formatBbox(x, y, precision) {
    return `${formatCoords(x, precision)} ${formatCoords(y, precision)}`;
}
export class WmtsCapabilities {
    constructor(params) {
        var _a;
        this.tileMatrixSets = new Map();
        this.isIndividualLayers = false;
        this.minZoom = 0;
        this.maxZoom = 32;
        this.httpBase = params.httpBase;
        this.provider = params.provider;
        this.tileSet = params.tileSet;
        this.config = params.config;
        this.isIndividualLayers = params.isIndividualLayers;
        for (const tms of params.tileMatrix)
            this.tileMatrixSets.set(tms.identifier, tms);
        this.apiKey = params.apiKey;
        this.formats = (_a = params.formats) !== null && _a !== void 0 ? _a : ImageFormatOrder;
        this.imagery = params.imagery;
    }
    buildWgs84BoundingBox(tms, layers) {
        let bbox;
        if (layers.length > 0) {
            let bounds = layers[0];
            for (let i = 1; i < layers.length; i++) {
                bounds = bounds.union(layers[i]);
            }
            bbox = wgs84Extent(tms, bounds.toJson());
        }
        else {
            // No layers provided assume extent is the size of the tile matrix set :shrug: ?
            bbox = wgs84Extent(tms, tms.extent);
        }
        // If east is less than west, then this has crossed the anti meridian, so cover the entire globe
        if (bbox[2] < bbox[0]) {
            bbox[0] = -180;
            bbox[2] = 180;
        }
        return V('ows:WGS84BoundingBox', { crs: 'urn:ogc:def:crs:OGC:2:84' }, [
            V('ows:LowerCorner', formatBbox(bbox[0], bbox[1], LngLatPrecision)),
            V('ows:UpperCorner', formatBbox(bbox[2], bbox[3], LngLatPrecision)),
        ]);
    }
    /** Combine all the bounds of the imagery inside the layers into a extent for the imagery set */
    buildBoundingBoxFromImagery(tms, layers) {
        let bounds;
        for (const layer of layers) {
            const imgId = layer[tms.projection.code];
            if (imgId == null)
                continue;
            const img = this.imagery.get(imgId);
            if (img == null)
                continue;
            if (bounds == null)
                bounds = Bounds.fromJson(img.bounds);
            else
                bounds = bounds.union(Bounds.fromJson(img.bounds));
        }
        if (bounds == null)
            return null;
        const bbox = bounds.toBbox();
        return V('ows:BoundingBox', { crs: tms.projection.toUrn() }, [
            V('ows:LowerCorner', formatBbox(bbox[tms.indexX], bbox[tms.indexY], MeterPrecision)),
            V('ows:UpperCorner', formatBbox(bbox[tms.indexX + 2], bbox[tms.indexY + 2], MeterPrecision)),
        ]);
    }
    buildProvider() {
        if (this.provider == null)
            return [];
        const { serviceIdentification, serviceProvider } = this.provider;
        const { contact } = serviceProvider;
        return [
            V('ows:ServiceIdentification', [
                V('ows:Title', serviceIdentification.title),
                V('ows:Abstract', serviceIdentification.description),
                V('ows:ServiceType', 'OGC WMTS'),
                V('ows:ServiceTypeVersion', '1.0.0'),
                V('ows:Fees', serviceIdentification.fees),
                V('ows:AccessConstraints', serviceIdentification.accessConstraints),
            ]),
            V('ows:ServiceProvider', [
                V('ows:ProviderName', serviceProvider.name),
                V('ows:ProviderSite', { 'xlink:href': serviceProvider.site }),
                V('ows:ServiceContact', [
                    V('ows:IndividualName', contact.individualName),
                    V('ows:PositionName', contact.position),
                    V('ows:ContactInfo', [
                        V('ows:Phone', [V('ows:Voice', contact.phone)]),
                        V('ows:Address', [
                            V('ows:DeliveryPoint', contact.address.deliveryPoint),
                            V('ows:City', contact.address.city),
                            V('ows:PostalCode', contact.address.postalCode),
                            V('ows:Country', contact.address.country),
                            V('ows:ElectronicMailAddress', contact.address.email),
                        ]),
                    ]),
                ]),
            ]),
        ];
    }
    buildTileUrl(tileSetId, suffix) {
        const query = toQueryString({ api: this.apiKey, config: this.config });
        return [
            this.httpBase,
            'v1',
            'tiles',
            tileSetId,
            '{TileMatrixSet}',
            '{TileMatrix}',
            '{TileCol}',
            `{TileRow}.${suffix}${query}`,
        ].join('/');
    }
    buildResourceUrl(tileSetId, suffix) {
        return V('ResourceURL', {
            format: 'image/' + suffix,
            resourceType: 'tile',
            template: this.buildTileUrl(tileSetId, suffix),
        });
    }
    buildLayerFromImagery(layer) {
        var _a, _b;
        const matrixSets = new Set();
        const matrixSetNodes = [];
        for (const tms of this.tileMatrixSets.values()) {
            const imdIg = layer[tms.projection.code];
            if (imdIg == null)
                continue;
            const img = this.imagery.get(imdIg);
            if (img == null)
                continue;
            matrixSetNodes.push(V('TileMatrixSetLink', [V('TileMatrixSet', tms.identifier)]));
            matrixSets.add(tms);
        }
        const layerNameId = standardizeLayerName(layer.name);
        const matrixSetList = [...matrixSets.values()];
        const firstMatrix = matrixSetList[0];
        if (firstMatrix == null)
            return null;
        const firstImg = this.imagery.get((_a = layer[firstMatrix.projection.code]) !== null && _a !== void 0 ? _a : '');
        if (firstImg == null)
            return null;
        return V('Layer', [
            V('ows:Title', (_b = layer.title) !== null && _b !== void 0 ? _b : layerNameId),
            V('ows:Abstract', ''),
            V('ows:Identifier', layerNameId),
            this.buildKeywords(firstImg),
            ...matrixSetList.map((tms) => {
                return this.buildBoundingBoxFromImagery(tms, [layer]);
            }),
            this.buildWgs84BoundingBox(firstMatrix, [Bounds.fromJson(firstImg.bounds)]),
            this.buildStyle(),
            ...this.formats.map((fmt) => V('Format', 'image/' + fmt)),
            ...matrixSetNodes,
            ...this.formats.map((fmt) => this.buildResourceUrl(layerNameId, fmt)),
        ]);
    }
    buildLayer(layer) {
        var _a, _b, _c, _d;
        const matrixSets = new Set();
        const matrixSetNodes = [];
        for (const tms of this.tileMatrixSets.values()) {
            if (layer.layers.find((f) => f[tms.projection.code] != null)) {
                matrixSetNodes.push(V('TileMatrixSetLink', [V('TileMatrixSet', tms.identifier)]));
                matrixSets.add(tms);
            }
        }
        const layerNameId = standardizeLayerName(layer.name);
        const matrixSetList = [...matrixSets.values()];
        const firstMatrix = matrixSetList[0];
        if (firstMatrix == null)
            throw new Error('No matrix sets found for layer ' + layer.name);
        // Prefer using the web mercator tms for bounds
        const webMercatorOrFirst = (_a = matrixSetList.find((f) => f.identifier === GoogleTms.identifier)) !== null && _a !== void 0 ? _a : firstMatrix;
        const bounds = [];
        for (const l of layer.layers) {
            const img = this.imagery.get((_b = l[webMercatorOrFirst.projection.code]) !== null && _b !== void 0 ? _b : '');
            if (img == null)
                continue;
            bounds.push(Bounds.fromJson(img.bounds));
        }
        return V('Layer', [
            V('ows:Title', (_c = layer.title) !== null && _c !== void 0 ? _c : layerNameId),
            V('ows:Abstract', (_d = layer.description) !== null && _d !== void 0 ? _d : ''),
            V('ows:Identifier', layerNameId),
            this.buildKeywords(layer),
            ...[...matrixSets.values()].map((tms) => this.buildBoundingBoxFromImagery(tms, layer.layers)),
            this.buildWgs84BoundingBox(webMercatorOrFirst, bounds),
            this.buildStyle(),
            ...this.formats.map((fmt) => V('Format', 'image/' + fmt)),
            ...matrixSetNodes,
            ...this.formats.map((fmt) => this.buildResourceUrl(layerNameId, fmt)),
        ]);
    }
    buildKeywords(tileSet) {
        if (tileSet.category == null)
            return V('ows:Keywords');
        return V('ows:Keywords', [V('ows:Keyword', tileSet.category)]);
    }
    buildStyle() {
        return V('Style', { isDefault: 'true' }, [V('ows:Title', 'Default Style'), V('ows:Identifier', 'default')]);
    }
    buildTileMatrixSet(tms) {
        return V('TileMatrixSet', [
            V('ows:Title', tms.def.title),
            tms.def.abstract ? V('ows:Abstract', tms.def.abstract) : null,
            V('ows:Identifier', tms.identifier),
            V('ows:SupportedCRS', tms.projection.toUrn()),
            tms.def.wellKnownScaleSet ? V('WellKnownScaleSet', tms.def.wellKnownScaleSet) : null,
            ...tms.def.tileMatrix.slice(this.minZoom, this.maxZoom + 1).map((c) => {
                return V('TileMatrix', [
                    V('ows:Identifier', c.identifier),
                    V('ScaleDenominator', c.scaleDenominator),
                    V('TopLeftCorner', c.topLeftCorner.join(' ')),
                    V('TileWidth', c.tileWidth),
                    V('TileHeight', c.tileHeight),
                    V('MatrixWidth', c.matrixWidth),
                    V('MatrixHeight', c.matrixHeight),
                ]);
            }),
        ]);
    }
    toVNode() {
        const layers = [];
        layers.push(this.buildLayer(this.tileSet));
        if (this.isIndividualLayers) {
            const layerByName = new Map();
            // Dedupe the layers by unique name
            for (const img of this.tileSet.layers)
                layerByName.set(standardizeLayerName(img.name), img);
            const orderedLayers = Array.from(layerByName.values()).sort((a, b) => { var _a, _b; return ((_a = a.title) !== null && _a !== void 0 ? _a : a.name).localeCompare((_b = b.title) !== null && _b !== void 0 ? _b : b.name); });
            for (const img of orderedLayers)
                layers.push(this.buildLayerFromImagery(img));
        }
        for (const tms of this.tileMatrixSets.values())
            layers.push(this.buildTileMatrixSet(tms));
        return V('Capabilities', CapabilitiesAttrs, [...this.buildProvider(), V('Contents', layers)]);
    }
    toXml() {
        return '<?xml version="1.0" encoding="utf-8"?>\n' + this.toVNode().toString();
    }
}
//# sourceMappingURL=wmts.capability.js.map