"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchHubApiDownloadFile = fetchHubApiDownloadFile;
const HubError_1 = require("../../../HubError");
const get_prop_1 = require("../../../objects/get-prop");
const wait_1 = require("../../../utils/wait");
const downloadOperationStatus_1 = require("../../enums/downloadOperationStatus");
const serviceDownloadFormat_1 = require("../../enums/serviceDownloadFormat");
const types_1 = require("../../types");
/**
 * @private
 * Fetches a download file url from the Hub Download API. If we know the file will be served from
 * a cache, we also return the status of the cache.
 *
 * NOTE: The Hub Download API only works with a certain subset of Feature and Map services
 * and performs different operations (i.e., calling createReplica or paging the service's
 * features) depending on the service type and capabilities.
 *
 * This function does it's best to abstract those differences and provide a consistent
 * interface for downloading data from any service supported by the Hub Download API.
 *
 * @param options options for refining / filtering the resulting download file
 * @returns the url to download the file and cache status
 */
async function fetchHubApiDownloadFile(options) {
    validateOptions(options);
    const requestUrl = getDownloadApiRequestUrl(options);
    const { pollInterval, updateCache, progressCallback } = options;
    let cacheQueryParam;
    if (updateCache) {
        // The download api has 2 query params that can be used for controlling cache behavior.
        // This one should only be used as part of an initial request to update the cache
        cacheQueryParam = "updateCache";
    }
    return pollDownloadApi(requestUrl, pollInterval, cacheQueryParam, progressCallback);
}
function validateOptions(options) {
    const { layers = [] } = options;
    // The Hub Download API currently requires a target layer to be specified
    if (layers.length === 0) {
        throw new HubError_1.default("fetchHubApiDownloadFileUrl", "No layers provided for download");
    }
    // The Hub Download API currently only supports downloading one
    // layer at a time, though it could allow multiple in the future
    if (layers.length > 1) {
        throw new HubError_1.default("fetchHubApiDownloadFileUrl", "Multiple layer downloads are not yet supported");
    }
}
/**
 * @private
 * Generates a URL to the Hub Download API that can be polled until the download is ready
 *
 * @param options options for refining / filtering the resulting download file
 * @returns a download api url that can be polled
 */
function getDownloadApiRequestUrl(options) {
    const { entity, format, context, layers, geometry, where } = options;
    const searchParams = new URLSearchParams({
        redirect: "false", // Needed to get the download URL instead of the file itself
        layers: layers[0].toString(),
    });
    if (geometry) {
        const geometryJSON = geometry.toJSON();
        // Not sure why type isn't included in the toJSON() output, but our API expects it
        geometryJSON.type = geometry.type;
        searchParams.append("geometry", JSON.stringify(geometryJSON));
    }
    // GeoJSON and KML are only supported in WGS84, so we need to specify the spatial reference here
    if ([serviceDownloadFormat_1.ServiceDownloadFormat.GEOJSON, serviceDownloadFormat_1.ServiceDownloadFormat.KML].includes(format)) {
        searchParams.append("spatialRefId", "4326");
    }
    // if you are updating this file, remove this comment and address the lint error
    // eslint-disable-next-line @typescript-eslint/no-unused-expressions
    where && searchParams.append("where", where);
    const token = (0, get_prop_1.getProp)(context, "hubRequestOptions.authentication.token");
    // if you are updating this file, remove this comment and address the lint error
    // eslint-disable-next-line @typescript-eslint/no-unused-expressions
    token && searchParams.append("token", token);
    return `${context.hubUrl}/api/download/v1/items/${entity.id}/${format}?${searchParams.toString()}`;
}
/**
 * @private
 * Polls the Hub Download API until the download is ready, then returns the download file URL
 *
 * @param requestUrl Hub Download Api URL to poll
 * @param pollInterval interval in milliseconds to poll for job completion
 * @param cacheQueryParam an optional query param to control cache behavior. 'updateCache' should
 * only be used with the initial request when a download cache update is desired. All subsequent
 * requests should use'trackCacheUpdate' to follow the progress of the cache update.
 * for subsequent requests when polling the progress of a cache update.
 * @param progressCallback an optional callback to report download generation progress
 * @returns the final file URL
 */
async function pollDownloadApi(requestUrl, pollInterval, cacheQueryParam, progressCallback) {
    var _a;
    // If requested, append the cache query param to the request URL
    let withCacheQueryParam = requestUrl;
    if (cacheQueryParam) {
        withCacheQueryParam = `${requestUrl}&${cacheQueryParam}=true`;
    }
    const response = await fetch(withCacheQueryParam);
    if (!response.ok) {
        const errorBody = await response.json();
        const errorMessage = errorBody.message || ((_a = errorBody.errors) === null || _a === void 0 ? void 0 : _a.message);
        // TODO: Add standarized messageId when available
        // Checks for "file too large" errors from different workflows:
        // - The following message is returned by the createReplica workflow:
        //   "Error downloading Shapefile. File size is larger than the 2GB limit."
        // - The following message is returned by the Hub Download System workflow:
        //   "The dataset is too large for shapefile generation"
        if (errorMessage ===
            "Error downloading Shapefile. File size is larger than the 2GB limit." ||
            errorMessage === "The dataset is too large for shapefile generation") {
            throw new types_1.ArcgisHubDownloadFileTooLargeError({
                rawMessage: errorMessage,
            });
        }
        // throws a generic download error
        throw new types_1.ArcgisHubDownloadError({
            rawMessage: errorMessage,
        });
    }
    const { status, progressInPercent, resultUrl, cacheStatus, } = await response.json();
    const operationStatus = toDownloadOperationStatus(status);
    if (operationStatus === downloadOperationStatus_1.DownloadOperationStatus.FAILED) {
        throw new HubError_1.default("fetchHubApiDownloadFileUrl", "Download operation failed with a 200");
    }
    if (resultUrl) {
        // Operation complete, return the download URL and cache status
        return {
            type: "url",
            href: resultUrl,
            cacheStatus,
        };
    }
    // Operation still in progress. Report progress if a callback was provided and poll again.
    // if you are updating this file, remove this comment and address the lint error
    // eslint-disable-next-line @typescript-eslint/no-unused-expressions
    progressCallback && progressCallback(operationStatus, progressInPercent);
    await (0, wait_1.wait)(pollInterval);
    // always have `trackCacheUpdate` present when polling Hub download API
    // because polling is always done to update cache or create a new cache
    // if not already present
    const updatedCacheQueryParam = "trackCacheUpdate";
    return pollDownloadApi(requestUrl, pollInterval, updatedCacheQueryParam, progressCallback);
}
/**
 * @private
 * Returns a standardized status string based on what is returned by the Hub Download API.
 * This is necessary because the Hub Download API returns a variety of statuses that are too
 * technical in nature that need to be translated into a more user-friendly status.
 *
 * @param status status returned by the Hub Download API
 * @returns a standardized download operation status
 */
function toDownloadOperationStatus(status) {
    // Statuses that come back if the Download API uses createReplica under the hood
    const createReplicaStatusMap = {
        // Statuses that we expect to see (listed in the order they could occur)
        Pending: downloadOperationStatus_1.DownloadOperationStatus.PENDING, // Job hasn't started yet
        InProgress: downloadOperationStatus_1.DownloadOperationStatus.PROCESSING, // Job is in progress
        ExportingData: downloadOperationStatus_1.DownloadOperationStatus.PROCESSING, // Features are being exported, progress available
        ExportAttachments: downloadOperationStatus_1.DownloadOperationStatus.PROCESSING, // Reported by Khaled Hassen, unsure when this actually happens
        Completed: downloadOperationStatus_1.DownloadOperationStatus.COMPLETED,
        CompletedWithErrors: downloadOperationStatus_1.DownloadOperationStatus.FAILED, // Reported by Khaled Hassen, unsure when this actually happens
        Failed: downloadOperationStatus_1.DownloadOperationStatus.FAILED,
        // These statuses are not expected to be returned by the API, but are included in the documentation
        ProvisioningReplica: downloadOperationStatus_1.DownloadOperationStatus.PROCESSING, // NOTE: This used to occur before ExportingData, but according to Khalid Hassen we shouldn't see it anymore
        ImportChanges: downloadOperationStatus_1.DownloadOperationStatus.PROCESSING,
        ExportChanges: downloadOperationStatus_1.DownloadOperationStatus.PROCESSING,
        ExportingSnapshot: downloadOperationStatus_1.DownloadOperationStatus.PROCESSING,
        ImportAttachments: downloadOperationStatus_1.DownloadOperationStatus.PROCESSING,
        UnRegisteringReplica: downloadOperationStatus_1.DownloadOperationStatus.PROCESSING,
    };
    // Statuses that come back if the Download API pages the service's features
    // under the hood They are listed in the order they are expected to occur
    const pagingJobStatusMap = {
        Pending: downloadOperationStatus_1.DownloadOperationStatus.PENDING,
        InProgress: downloadOperationStatus_1.DownloadOperationStatus.PROCESSING,
        PagingData: downloadOperationStatus_1.DownloadOperationStatus.PROCESSING,
        ConvertingData: downloadOperationStatus_1.DownloadOperationStatus.CONVERTING,
        Failed: downloadOperationStatus_1.DownloadOperationStatus.FAILED,
        Completed: downloadOperationStatus_1.DownloadOperationStatus.COMPLETED,
    };
    return (createReplicaStatusMap[status] ||
        pagingJobStatusMap[status]);
}
//# sourceMappingURL=fetchHubApiDownloadFile.js.map