"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _FileFetcher_instances, _FileFetcher_allowRemote, _FileFetcher_authTokens, _FileFetcher_cache, _FileFetcher_cacheSetting, _FileFetcher_httpCache, _FileFetcher_httpCachePromise, _FileFetcher_httpCacheFactory, _FileFetcher_fetchBlobDataUrl, _FileFetcher_fetchCached, _FileFetcher_fetchRemote, _FileFetcher_resolveOptions, _FileFetcher_resolveHttpCache;
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchWithRetries = exports.FileFetcher = void 0;
// Copyright 2018-2024 the Deno authors. MIT license.
const dntShim = __importStar(require("../../../../../_dnt.shims.js"));
const auth_tokens_js_1 = require("./auth_tokens.js");
const deps_js_1 = require("./deps.js");
function shouldUseCache(cacheSetting, specifier) {
    switch (cacheSetting) {
        case "only":
        case "use":
            return true;
        // @ts-ignore old setting
        case "reloadAll":
        case "reload":
            return false;
        default: {
            const specifierStr = specifier.toString();
            for (const value of cacheSetting) {
                if (specifierStr.startsWith(value)) {
                    return false;
                }
            }
            return true;
        }
    }
}
const SUPPORTED_SCHEMES = [
    "data:",
    "blob:",
    "file:",
    "http:",
    "https:",
];
function getValidatedScheme(specifier) {
    const scheme = specifier.protocol;
    // deno-lint-ignore no-explicit-any
    if (!SUPPORTED_SCHEMES.includes(scheme)) {
        throw new TypeError(`Unsupported scheme "${scheme}" for module "${specifier.toString()}". Supported schemes: ${JSON.stringify(SUPPORTED_SCHEMES)}.`);
    }
    return scheme;
}
function hasHashbang(value) {
    return value[0] === 35 /* # */ && value[1] === 33 /* ! */;
}
function stripHashbang(value) {
    if (hasHashbang(value)) {
        const text = new TextDecoder().decode(value);
        const lineIndex = text.indexOf("\n");
        if (lineIndex > 0) {
            return text.slice(lineIndex + 1);
        }
        else {
            return value;
        }
    }
    else {
        return value;
    }
}
async function fetchLocal(specifier) {
    const local = (0, deps_js_1.fromFileUrl)(specifier);
    if (!local) {
        throw new TypeError(`Invalid file path.\n  Specifier: "${specifier.toString()}"`);
    }
    try {
        const content = stripHashbang(await dntShim.Deno.readFile(local));
        return {
            kind: "module",
            content,
            specifier: specifier.toString(),
        };
    }
    catch {
        // ignoring errors, we will just return undefined
    }
}
class FileFetcher {
    constructor(httpCacheFactory, cacheSetting = "use", allowRemote = true) {
        _FileFetcher_instances.add(this);
        _FileFetcher_allowRemote.set(this, void 0);
        _FileFetcher_authTokens.set(this, void 0);
        _FileFetcher_cache.set(this, new Map());
        _FileFetcher_cacheSetting.set(this, void 0);
        _FileFetcher_httpCache.set(this, void 0);
        _FileFetcher_httpCachePromise.set(this, void 0);
        _FileFetcher_httpCacheFactory.set(this, void 0);
        dntShim.Deno.permissions.request({ name: "env", variable: "DENO_AUTH_TOKENS" });
        __classPrivateFieldSet(this, _FileFetcher_authTokens, new auth_tokens_js_1.AuthTokens(dntShim.Deno.env.get("DENO_AUTH_TOKENS")), "f");
        __classPrivateFieldSet(this, _FileFetcher_allowRemote, allowRemote, "f");
        __classPrivateFieldSet(this, _FileFetcher_cacheSetting, cacheSetting, "f");
        __classPrivateFieldSet(this, _FileFetcher_httpCacheFactory, httpCacheFactory, "f");
    }
    async fetch(specifier, options) {
        const scheme = getValidatedScheme(specifier);
        if (scheme === "file:") {
            return fetchLocal(specifier);
        }
        const response = __classPrivateFieldGet(this, _FileFetcher_cache, "f").get(specifier.toString());
        if (response) {
            return response;
        }
        else if (scheme === "data:" || scheme === "blob:") {
            const response = await __classPrivateFieldGet(this, _FileFetcher_instances, "m", _FileFetcher_fetchBlobDataUrl).call(this, specifier, __classPrivateFieldGet(this, _FileFetcher_instances, "m", _FileFetcher_resolveOptions).call(this, options), await __classPrivateFieldGet(this, _FileFetcher_instances, "m", _FileFetcher_resolveHttpCache).call(this));
            await __classPrivateFieldGet(this, _FileFetcher_cache, "f").set(specifier.toString(), response);
            return response;
        }
        else if (!__classPrivateFieldGet(this, _FileFetcher_allowRemote, "f")) {
            throw new dntShim.Deno.errors.PermissionDenied(`A remote specifier was requested: "${specifier.toString()}", but --no-remote is specified.`);
        }
        else {
            const response = await __classPrivateFieldGet(this, _FileFetcher_instances, "m", _FileFetcher_fetchRemote).call(this, specifier, 10, __classPrivateFieldGet(this, _FileFetcher_instances, "m", _FileFetcher_resolveOptions).call(this, options), await __classPrivateFieldGet(this, _FileFetcher_instances, "m", _FileFetcher_resolveHttpCache).call(this));
            if (response) {
                await __classPrivateFieldGet(this, _FileFetcher_cache, "f").set(specifier.toString(), response);
            }
            return response;
        }
    }
}
exports.FileFetcher = FileFetcher;
_FileFetcher_allowRemote = new WeakMap(), _FileFetcher_authTokens = new WeakMap(), _FileFetcher_cache = new WeakMap(), _FileFetcher_cacheSetting = new WeakMap(), _FileFetcher_httpCache = new WeakMap(), _FileFetcher_httpCachePromise = new WeakMap(), _FileFetcher_httpCacheFactory = new WeakMap(), _FileFetcher_instances = new WeakSet(), _FileFetcher_fetchBlobDataUrl = async function _FileFetcher_fetchBlobDataUrl(specifier, options, httpCache) {
    const cached = __classPrivateFieldGet(this, _FileFetcher_instances, "m", _FileFetcher_fetchCached).call(this, specifier, 0, options, httpCache);
    if (cached) {
        return cached;
    }
    if (options.cacheSetting === "only") {
        throw new dntShim.Deno.errors.NotFound(`Specifier not found in cache: "${specifier.toString()}", --cached-only is specified.`);
    }
    const response = await fetchWithRetries(specifier.toString());
    const content = new Uint8Array(await response.arrayBuffer());
    const headers = {};
    for (const [key, value] of response.headers) {
        headers[key.toLowerCase()] = value;
    }
    httpCache.set(specifier, headers, content);
    return {
        kind: "module",
        specifier: specifier.toString(),
        headers,
        content,
    };
}, _FileFetcher_fetchCached = function _FileFetcher_fetchCached(specifier, redirectLimit, options, httpCache) {
    if (redirectLimit < 0) {
        throw new dntShim.Deno.errors.Http(`Too many redirects.\n  Specifier: "${specifier.toString()}"`);
    }
    const headers = httpCache.getHeaders(specifier);
    if (!headers) {
        return undefined;
    }
    const location = headers["location"];
    if (location != null && location.length > 0) {
        const redirect = new URL(location, specifier);
        return __classPrivateFieldGet(this, _FileFetcher_instances, "m", _FileFetcher_fetchCached).call(this, redirect, redirectLimit - 1, options, httpCache);
    }
    const content = httpCache.get(specifier, options);
    if (content == null) {
        return undefined;
    }
    return {
        kind: "module",
        specifier: specifier.toString(),
        headers,
        content,
    };
}, _FileFetcher_fetchRemote = async function _FileFetcher_fetchRemote(specifier, redirectLimit, options, httpCache) {
    if (redirectLimit < 0) {
        throw new dntShim.Deno.errors.Http(`Too many redirects.\n  Specifier: "${specifier.toString()}"`);
    }
    if (shouldUseCache(options.cacheSetting, specifier)) {
        const response = __classPrivateFieldGet(this, _FileFetcher_instances, "m", _FileFetcher_fetchCached).call(this, specifier, redirectLimit, options, httpCache);
        if (response) {
            return response;
        }
    }
    if (options.cacheSetting === "only") {
        throw new dntShim.Deno.errors.NotFound(`Specifier not found in cache: "${specifier.toString()}", --cached-only is specified.`);
    }
    const requestHeaders = new dntShim.Headers();
    const cachedHeaders = httpCache.getHeaders(specifier);
    if (cachedHeaders) {
        const etag = cachedHeaders["etag"];
        if (etag != null && etag.length > 0) {
            requestHeaders.append("if-none-match", etag);
        }
    }
    const authToken = __classPrivateFieldGet(this, _FileFetcher_authTokens, "f").get(specifier);
    if (authToken) {
        requestHeaders.append("authorization", authToken);
    }
    // deno-lint-ignore no-console
    console.error(`${deps_js_1.colors.green("Download")} ${specifier.toString()}`);
    const response = await fetchWithRetries(specifier.toString(), {
        headers: requestHeaders,
    });
    if (!response.ok) {
        if (response.status === 404) {
            return undefined;
        }
        else {
            throw new dntShim.Deno.errors.Http(`${response.status} ${response.statusText}`);
        }
    }
    // WHATWG fetch follows redirects automatically, so we will try to
    // determine if that occurred and cache the value.
    if (specifier.toString() !== response.url) {
        const headers = { "location": response.url };
        httpCache.set(specifier, headers, new Uint8Array());
    }
    const url = new URL(response.url);
    const content = new Uint8Array(await response.arrayBuffer());
    const headers = {};
    for (const [key, value] of response.headers) {
        headers[key.toLowerCase()] = value;
    }
    httpCache.set(url, headers, content);
    if (options?.checksum != null) {
        const digest = await crypto.subtle.digest("SHA-256", content);
        const actualChecksum = Array.from(new Uint8Array(digest))
            .map((b) => b.toString(16).padStart(2, "0"))
            .join("");
        if (actualChecksum != options.checksum) {
            throw new Error(`Integrity check failed for ${url}\n\nActual: ${actualChecksum}\nExpected: ${options.checksum}`);
        }
    }
    return {
        kind: "module",
        specifier: response.url,
        headers,
        content,
    };
}, _FileFetcher_resolveOptions = function _FileFetcher_resolveOptions(options) {
    options ??= {};
    options.cacheSetting = options.cacheSetting ?? __classPrivateFieldGet(this, _FileFetcher_cacheSetting, "f");
    return options;
}, _FileFetcher_resolveHttpCache = function _FileFetcher_resolveHttpCache() {
    if (__classPrivateFieldGet(this, _FileFetcher_httpCache, "f") != null) {
        return Promise.resolve(__classPrivateFieldGet(this, _FileFetcher_httpCache, "f"));
    }
    if (!__classPrivateFieldGet(this, _FileFetcher_httpCachePromise, "f")) {
        __classPrivateFieldSet(this, _FileFetcher_httpCachePromise, __classPrivateFieldGet(this, _FileFetcher_httpCacheFactory, "f").call(this).then((cache) => {
            __classPrivateFieldSet(this, _FileFetcher_httpCache, cache, "f");
            __classPrivateFieldSet(this, _FileFetcher_httpCachePromise, undefined, "f");
            return cache;
        }), "f");
    }
    return __classPrivateFieldGet(this, _FileFetcher_httpCachePromise, "f");
};
async function fetchWithRetries(url, init) {
    const maxRetries = 3;
    let sleepMs = 250;
    let iterationCount = 0;
    while (true) {
        iterationCount++;
        try {
            const res = await dntShim.fetch(url, init);
            if (res.ok || iterationCount > maxRetries ||
                res.status >= 400 && res.status < 500) {
                return res;
            }
        }
        catch (err) {
            if (iterationCount > maxRetries) {
                throw err;
            }
        }
        // deno-lint-ignore no-console
        console.warn(`${deps_js_1.colors.yellow("WARN")} Failed fetching ${url}. Retrying in ${sleepMs}ms...`);
        await new Promise((resolve) => setTimeout(resolve, sleepMs));
        sleepMs = Math.min(sleepMs * 2, 10000);
    }
}
exports.fetchWithRetries = fetchWithRetries;
