"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VersionsDatasource = exports.memCache = void 0;
const zod_1 = require("zod");
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 schema_utils_1 = require("../../../util/schema-utils");
const string_1 = require("../../../util/string");
const datasource_1 = require("../datasource");
exports.memCache = new Map();
const Lines = zod_1.z
    .string()
    .transform((x) => x.split(regex_1.newlineRegex))
    .pipe((0, schema_utils_1.LooseArray)(zod_1.z
    .string()
    .transform((line) => line.trim())
    .refine((line) => line.length > 0)
    .refine((line) => !line.startsWith('created_at:'))
    .refine((line) => line !== '---')
    .transform((line) => line.split(' '))
    .pipe(zod_1.z.tuple([zod_1.z.string(), zod_1.z.string()]).rest(zod_1.z.string()))
    .transform(([packageName, versions]) => {
    const deletedVersions = new Set();
    const addedVersions = [];
    for (const version of versions.split(',')) {
        if (version.startsWith('-')) {
            deletedVersions.add(version.slice(1));
        }
        else {
            addedVersions.push(version);
        }
    }
    return { packageName, deletedVersions, addedVersions };
})));
class VersionsDatasource extends datasource_1.Datasource {
    id;
    constructor(id) {
        super(id);
        this.id = id;
    }
    getRegistryCache(registryUrl) {
        const cacheKey = `rubygems-versions-cache:${registryUrl}`;
        const regCache = exports.memCache.get(cacheKey) ?? {
            lastSync: new Date('2000-01-01'),
            packageReleases: new Map(),
            contentLength: 0,
            isSupported: false,
            registryUrl,
        };
        exports.memCache.set(cacheKey, regCache);
        return regCache;
    }
    async getReleases({ registryUrl, packageName, }) {
        logger_1.logger.debug(`getRubygemsOrgDependency(${packageName})`);
        // istanbul ignore if
        if (!registryUrl) {
            return null;
        }
        const regCache = this.getRegistryCache(registryUrl);
        await this.syncVersions(regCache);
        if (!regCache.isSupported) {
            throw new Error(error_messages_1.PAGE_NOT_FOUND_ERROR);
        }
        const versions = regCache.packageReleases.get(packageName);
        if (!versions) {
            return null;
        }
        const releases = versions.map((version) => ({ version }));
        return { releases };
    }
    updatePackageReleases(packageReleases, lines) {
        for (const line of lines) {
            const packageName = (0, string_1.copystr)(line.packageName);
            let versions = packageReleases.get(packageName) ?? [];
            const { deletedVersions, addedVersions } = line;
            if (deletedVersions.size > 0) {
                versions = versions.filter((v) => !deletedVersions.has(v));
            }
            if (addedVersions.length > 0) {
                const existingVersions = new Set(versions);
                for (const addedVersion of addedVersions) {
                    if (!existingVersions.has(addedVersion)) {
                        const version = (0, string_1.copystr)(addedVersion);
                        versions.push(version);
                    }
                }
            }
            packageReleases.set(packageName, versions);
        }
    }
    async updateRubyGemsVersions(regCache) {
        const url = `${regCache.registryUrl}/versions`;
        const options = {
            headers: {
                'accept-encoding': 'gzip',
                range: `bytes=${regCache.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}`);
        }
        catch (err) /* istanbul ignore next */ {
            if (err instanceof http_1.HttpError && err.response?.statusCode === 404) {
                regCache.isSupported = false;
                return;
            }
            if (err.statusCode === 416) {
                logger_1.logger.debug('Rubygems: No update');
                regCache.lastSync = new Date();
                return;
            }
            regCache.contentLength = 0;
            regCache.packageReleases.clear();
            logger_1.logger.debug({ err }, 'Rubygems fetch error');
            throw new external_host_error_1.ExternalHostError(err);
        }
        regCache.isSupported = true;
        regCache.lastSync = new Date();
        const lines = Lines.parse(newLines);
        this.updatePackageReleases(regCache.packageReleases, lines);
    }
    updateRubyGemsVersionsPromise = null;
    async syncVersions(regCache) {
        const isStale = (0, date_1.getElapsedMinutes)(regCache.lastSync) >= 15;
        if (isStale) {
            this.updateRubyGemsVersionsPromise =
                this.updateRubyGemsVersionsPromise ??
                    this.updateRubyGemsVersions(regCache);
            await this.updateRubyGemsVersionsPromise;
            this.updateRubyGemsVersionsPromise = null;
        }
    }
}
exports.VersionsDatasource = VersionsDatasource;
//# sourceMappingURL=versions-datasource.js.map