"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VersionsDatasource = exports.resetCache = void 0;
const error_messages_1 = require("../../../constants/error-messages");
const logger_1 = require("../../../logger");
const external_host_error_1 = require("../../../types/errors/external-host-error");
const date_1 = require("../../../util/date");
const http_1 = require("../../../util/http");
const regex_1 = require("../../../util/regex");
const datasource_1 = require("../datasource");
const registryCaches = {};
// Note: use only for tests
function resetCache() {
    Object.keys(registryCaches).forEach((key) => {
        registryCaches[key].lastSync = new Date('2000-01-01');
        registryCaches[key].packageReleases = Object.create(null);
        registryCaches[key].contentLength = 0;
        registryCaches[key].isSupported = false;
    });
}
exports.resetCache = resetCache;
class VersionsDatasource extends datasource_1.Datasource {
    constructor(id, registryUrl) {
        super(id);
        this.id = id;
        this.updateRubyGemsVersionsPromise = null;
        this.registryUrl = registryUrl;
        if (!registryCaches[registryUrl]) {
            registryCaches[registryUrl] = {
                lastSync: new Date('2000-01-01'),
                packageReleases: Object.create(null),
                contentLength: 0,
                isSupported: false,
            };
        }
        this.registryCache = registryCaches[registryUrl];
    }
    async getReleases({ packageName, }) {
        logger_1.logger.debug(`getRubygemsOrgDependency(${packageName})`);
        await this.syncVersions();
        if (!this.registryCache.isSupported) {
            throw new Error(error_messages_1.PAGE_NOT_FOUND_ERROR);
        }
        if (!this.registryCache.packageReleases[packageName]) {
            return null;
        }
        const dep = {
            releases: this.registryCache.packageReleases[packageName].map((version) => ({
                version,
            })),
        };
        return dep;
    }
    /**
     * https://bugs.chromium.org/p/v8/issues/detail?id=2869
     */
    static copystr(x) {
        return (' ' + x).slice(1);
    }
    async updateRubyGemsVersions() {
        const url = `${this.registryUrl}/versions`;
        const options = {
            headers: {
                'accept-encoding': 'identity',
                range: `bytes=${this.registryCache.contentLength}-`,
            },
        };
        let newLines;
        try {
            logger_1.logger.debug('Rubygems: Fetching rubygems.org versions');
            const startTime = Date.now();
            newLines = (await this.http.get(url, options)).body;
            const durationMs = Math.round(Date.now() - startTime);
            logger_1.logger.debug(`Rubygems: Fetched rubygems.org versions in ${durationMs}`);
            this.registryCache.isSupported = true;
        }
        catch (err) /* istanbul ignore next */ {
            if (err instanceof http_1.HttpError && err.response?.statusCode === 404) {
                this.registryCache.isSupported = false;
                return;
            }
            if (err.statusCode !== 416) {
                this.registryCache.contentLength = 0;
                this.registryCache.packageReleases = Object.create(null); // Because we might need a "constructor" key
                logger_1.logger.debug({ err }, 'Rubygems fetch error');
                throw new external_host_error_1.ExternalHostError(new Error('Rubygems fetch error'));
            }
            logger_1.logger.debug('Rubygems: No update');
            this.registryCache.lastSync = new Date();
            return;
        }
        for (const line of newLines.split(regex_1.newlineRegex)) {
            this.processLine(line);
        }
        this.registryCache.lastSync = new Date();
    }
    processLine(line) {
        let split;
        let pkg;
        let versions;
        try {
            const l = line.trim();
            if (!l.length || l.startsWith('created_at:') || l === '---') {
                return;
            }
            split = l.split(' ');
            [pkg, versions] = split;
            pkg = VersionsDatasource.copystr(pkg);
            this.registryCache.packageReleases[pkg] =
                this.registryCache.packageReleases[pkg] || [];
            const lineVersions = versions.split(',').map((version) => version.trim());
            for (const lineVersion of lineVersions) {
                if (lineVersion.startsWith('-')) {
                    const deletedVersion = lineVersion.slice(1);
                    logger_1.logger.trace({ pkg, deletedVersion }, 'Rubygems: Deleting version');
                    this.registryCache.packageReleases[pkg] =
                        this.registryCache.packageReleases[pkg].filter((version) => version !== deletedVersion);
                }
                else {
                    this.registryCache.packageReleases[pkg].push(VersionsDatasource.copystr(lineVersion));
                }
            }
        }
        catch (err) /* istanbul ignore next */ {
            logger_1.logger.warn({ err, line, split, pkg, versions }, 'Rubygems line parsing error');
        }
    }
    isDataStale() {
        return (0, date_1.getElapsedMinutes)(this.registryCache.lastSync) >= 15;
    }
    async syncVersions() {
        if (this.isDataStale()) {
            this.updateRubyGemsVersionsPromise =
                this.updateRubyGemsVersionsPromise ?? this.updateRubyGemsVersions();
            await this.updateRubyGemsVersionsPromise;
            this.updateRubyGemsVersionsPromise = null;
        }
    }
}
exports.VersionsDatasource = VersionsDatasource;
//# sourceMappingURL=versions-datasource.js.map