import { getAllImagery, TileSetType } from '@basemaps/config';
import { Bounds, GoogleTms, Stac, TileMatrixSet, } from '@basemaps/geo';
import { extractYearRangeFromName, Projection, titleizeImageryName } from '@basemaps/shared';
import { multiPolygonToWgs84, union, Wgs84 } from '@linzjs/geojson';
import { HttpHeader, LambdaHttpResponse } from '@linzjs/lambda';
import { ConfigLoader } from '../util/config.loader.js';
import { Etag } from '../util/etag.js';
import { NotFound, NotModified } from '../util/response.js';
import { Validate } from '../util/validate.js';
/** Amount to pad imagery bounds to avoid fragmenting polygons  */
const SmoothPadding = 1 + 1e-10; // about 1/100th of a millimeter at equator
const Precision = 10 ** 8;
/**
 * Limit precision to 8 decimal places.
 */
function roundNumber(n) {
    return Math.round(n * Precision) / Precision;
}
function roundPair(p) {
    return [roundNumber(p[0]), roundNumber(p[1])];
}
/**
 * Convert a list of COG file bounds into a MultiPolygon. If the bounds spans more than half the
 * globe then return a simple MultiPolygon for the bounding box.

 * @param bbox in WGS84
 * @param files in target projection
 * @return MultiPolygon in WGS84
 */
function createCoordinates(bbox, files, proj) {
    if (Wgs84.delta(bbox[0], bbox[2]) <= 0) {
        // This bounds spans more than half the globe which multiPolygonToWgs84 can't handle; just
        // return bbox as polygon
        return Wgs84.bboxToMultiPolygon(bbox);
    }
    let coordinates = [];
    // merge imagery bounds
    for (const image of files) {
        const poly = [Bounds.fromJson(image).pad(SmoothPadding).toPolygon()];
        coordinates = union(coordinates, poly);
    }
    const roundToWgs84 = (p) => roundPair(proj.toWgs84(p));
    return multiPolygonToWgs84(coordinates, roundToWgs84);
}
function getHost(host) {
    if (host == null)
        return undefined;
    return [{ name: host.serviceProvider.name, url: host.serviceProvider.site, roles: ['host'] }];
}
/**
 * Build a Single File STAC for the given TileSet.
 *
 * For now this is the minimal set required for attribution. This can be embellished later with
 * links and assets for a more comprehensive STAC file.
 */
async function tileSetAttribution(req, tileSet, tileMatrix) {
    var _a, _b, _c, _d;
    const proj = Projection.get(tileMatrix);
    const cols = [];
    const items = [];
    const config = await ConfigLoader.load(req);
    const imagery = await getAllImagery(config, tileSet.layers, [tileMatrix.projection]);
    const host = await config.Provider.get(config.Provider.id('linz'));
    for (const layer of tileSet.layers) {
        const imgId = layer[proj.epsg.code];
        if (imgId == null)
            continue;
        const im = imagery.get(imgId);
        if (im == null)
            continue;
        const bbox = proj.boundsToWgs84BoundingBox(im.bounds).map(roundNumber);
        const years = extractYearRangeFromName(im.name);
        if (years[0] === -1) {
            req.log.debug({ imagery: im.name }, 'Attribution:DefaultYear');
            // Put it in the future so people know its a "fake" date
            years[0] = new Date().getUTCFullYear() + 1;
            years[1] = years[0] + 1;
        }
        const interval = [years.map((y) => `${y}-01-01T00:00:00Z`)];
        const extent = { spatial: { bbox: [bbox] }, temporal: { interval } };
        items.push({
            type: 'Feature',
            stac_version: Stac.Version,
            id: imgId + '_item',
            collection: imgId,
            assets: {},
            links: [],
            bbox,
            geometry: { type: 'MultiPolygon', coordinates: createCoordinates(bbox, im.files, proj) },
            properties: {
                title: (_a = im.title) !== null && _a !== void 0 ? _a : titleizeImageryName(im.name),
                category: im.category,
                datetime: null,
                start_datetime: interval[0][0],
                end_datetime: interval[0][1],
            },
        });
        const zoomMin = TileMatrixSet.convertZoomLevel(layer.minZoom ? layer.minZoom : 0, GoogleTms, tileMatrix, true);
        const zoomMax = TileMatrixSet.convertZoomLevel(layer.maxZoom ? layer.maxZoom : 32, GoogleTms, tileMatrix, true);
        cols.push({
            stac_version: Stac.Version,
            license: Stac.License,
            id: im.id,
            providers: getHost(host),
            title: (_b = im.title) !== null && _b !== void 0 ? _b : titleizeImageryName(im.name),
            description: 'No description',
            extent,
            links: [],
            summaries: {
                'linz:category': im.category,
                'linz:zoom': { min: zoomMin, max: zoomMax },
                'linz:priority': [1000 + tileSet.layers.indexOf(layer)],
            },
        });
    }
    return {
        id: tileSet.id,
        type: 'FeatureCollection',
        stac_version: Stac.Version,
        stac_extensions: ['single-file-stac'],
        title: (_c = tileSet.title) !== null && _c !== void 0 ? _c : 'No title',
        description: (_d = tileSet.description) !== null && _d !== void 0 ? _d : 'No Description',
        features: items,
        collections: cols,
        links: [],
    };
}
/**
 * Create a LambdaHttpResponse for a attribution request
 */
export async function tileAttributionGet(req) {
    const tileMatrix = Validate.getTileMatrixSet(req.params.tileMatrix);
    if (tileMatrix == null)
        throw new LambdaHttpResponse(404, 'Tile Matrix not found');
    const config = await ConfigLoader.load(req);
    req.timer.start('tileset:load');
    const tileSet = await config.TileSet.get(config.TileSet.id(req.params.tileSet));
    req.timer.end('tileset:load');
    if (tileSet == null || tileSet.type === TileSetType.Vector)
        return NotFound();
    const cacheKey = Etag.key(tileSet);
    if (Etag.isNotModified(req, cacheKey))
        return NotModified();
    req.timer.start('stac:load');
    const attributions = await tileSetAttribution(req, tileSet, tileMatrix);
    req.timer.end('stac:load');
    if (attributions == null)
        return NotFound();
    const response = new LambdaHttpResponse(200, 'ok');
    response.header(HttpHeader.ETag, cacheKey);
    // Keep fresh for 7 days; otherwise use cache but refresh cache for next time
    response.header(HttpHeader.CacheControl, 'public, max-age=604800, stale-while-revalidate=86400');
    response.json(attributions);
    return response;
}
//# sourceMappingURL=attribution.js.map