// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { createHash } from 'node:crypto';
import { JsonFile, JsonSchema, FileSystem, AlreadyReportedError } from '@rushstack/node-core-library';
import { FileSystemBuildCacheProvider } from '../logic/buildCache/FileSystemBuildCacheProvider';
import { RushConstants } from '../logic/RushConstants';
import { RushUserConfiguration } from './RushUserConfiguration';
import { EnvironmentConfiguration, EnvironmentVariableNames } from './EnvironmentConfiguration';
import { CacheEntryId } from '../logic/buildCache/CacheEntryId';
import schemaJson from '../schemas/build-cache.schema.json';
const BUILD_CACHE_JSON_SCHEMA = JsonSchema.fromLoadedObject(schemaJson);
/**
 * Use this class to load and save the "common/config/rush/build-cache.json" config file.
 * This file provides configuration options for cached project build output.
 * @beta
 */
export class BuildCacheConfiguration {
    constructor({ getCacheEntryId, buildCacheJson, rushUserConfiguration, rushConfiguration, cloudCacheProvider }) {
        var _a;
        this.buildCacheEnabled = (_a = EnvironmentConfiguration.buildCacheEnabled) !== null && _a !== void 0 ? _a : buildCacheJson.buildCacheEnabled;
        this.cacheWriteEnabled =
            !!this.buildCacheEnabled && EnvironmentConfiguration.buildCacheWriteAllowed !== false;
        this.getCacheEntryId = getCacheEntryId;
        this.localCacheProvider = new FileSystemBuildCacheProvider({
            rushUserConfiguration: rushUserConfiguration,
            rushConfiguration: rushConfiguration
        });
        this.cloudCacheProvider = cloudCacheProvider;
        this.cacheHashSalt = buildCacheJson.cacheHashSalt;
    }
    /**
     * Attempts to load the build-cache.json data from the standard file path `common/config/rush/build-cache.json`.
     * If the file has not been created yet, then undefined is returned.
     */
    static async tryLoadAsync(terminal, rushConfiguration, rushSession) {
        const { buildCacheConfiguration } = await BuildCacheConfiguration._tryLoadInternalAsync(terminal, rushConfiguration, rushSession);
        return buildCacheConfiguration;
    }
    /**
     * Loads the build-cache.json data from the standard file path `common/config/rush/build-cache.json`.
     * If the file has not been created yet, or if the feature is not enabled, then an error is reported.
     */
    static async loadAndRequireEnabledAsync(terminal, rushConfiguration, rushSession) {
        const { buildCacheConfiguration, jsonFilePath } = await BuildCacheConfiguration._tryLoadInternalAsync(terminal, rushConfiguration, rushSession);
        if (!buildCacheConfiguration) {
            terminal.writeErrorLine(`The build cache feature is not enabled. This config file is missing:\n` + jsonFilePath);
            terminal.writeLine(`\nThe Rush website documentation has instructions for enabling the build cache.`);
            throw new AlreadyReportedError();
        }
        if (!buildCacheConfiguration.buildCacheEnabled) {
            terminal.writeErrorLine(`The build cache feature is not enabled. You can enable it by editing this config file:\n` +
                jsonFilePath);
            throw new AlreadyReportedError();
        }
        return buildCacheConfiguration;
    }
    /**
     * Gets the absolute path to the build-cache.json file in the specified rush workspace.
     */
    static getBuildCacheConfigFilePath(rushConfiguration) {
        return `${rushConfiguration.commonRushConfigFolder}/${RushConstants.buildCacheFilename}`;
    }
    static async _tryLoadInternalAsync(terminal, rushConfiguration, rushSession) {
        const jsonFilePath = BuildCacheConfiguration.getBuildCacheConfigFilePath(rushConfiguration);
        const buildCacheConfiguration = await BuildCacheConfiguration._tryLoadAsync(jsonFilePath, terminal, rushConfiguration, rushSession);
        return { buildCacheConfiguration, jsonFilePath };
    }
    static async _tryLoadAsync(jsonFilePath, terminal, rushConfiguration, rushSession) {
        let buildCacheJson;
        const buildCacheOverrideJson = EnvironmentConfiguration.buildCacheOverrideJson;
        if (buildCacheOverrideJson) {
            buildCacheJson = JsonFile.parseString(buildCacheOverrideJson);
            BUILD_CACHE_JSON_SCHEMA.validateObject(buildCacheJson, `${EnvironmentVariableNames.RUSH_BUILD_CACHE_OVERRIDE_JSON} environment variable`);
        }
        else {
            const buildCacheOverrideJsonFilePath = EnvironmentConfiguration.buildCacheOverrideJsonFilePath;
            if (buildCacheOverrideJsonFilePath) {
                buildCacheJson = await JsonFile.loadAndValidateAsync(buildCacheOverrideJsonFilePath, BUILD_CACHE_JSON_SCHEMA);
            }
            else {
                try {
                    buildCacheJson = await JsonFile.loadAndValidateAsync(jsonFilePath, BUILD_CACHE_JSON_SCHEMA);
                }
                catch (e) {
                    if (!FileSystem.isNotExistError(e)) {
                        throw e;
                    }
                    else {
                        return undefined;
                    }
                }
            }
        }
        const rushUserConfiguration = await RushUserConfiguration.initializeAsync();
        let innerGetCacheEntryId;
        try {
            innerGetCacheEntryId = CacheEntryId.parsePattern(buildCacheJson.cacheEntryNamePattern);
        }
        catch (e) {
            terminal.writeErrorLine(`Error parsing cache entry name pattern "${buildCacheJson.cacheEntryNamePattern}": ${e}`);
            throw new AlreadyReportedError();
        }
        const { cacheHashSalt = '', cacheProvider } = buildCacheJson;
        const salt = `${RushConstants.buildCacheVersion}${cacheHashSalt ? `${RushConstants.hashDelimiter}${cacheHashSalt}` : ''}`;
        // Extend the cache entry id with to salt the hash
        // This facilitates forcing cache invalidation either when the build cache version changes (new version of Rush)
        // or when the user-side salt changes (need to purge bad cache entries, plugins including additional files)
        const getCacheEntryId = (options) => {
            const saltedHash = createHash('sha1')
                .update(salt)
                .update(options.projectStateHash)
                .digest('hex');
            return innerGetCacheEntryId({
                phaseName: options.phaseName,
                projectName: options.projectName,
                projectStateHash: saltedHash
            });
        };
        let cloudCacheProvider;
        // Don't configure a cloud cache provider if local-only
        if (cacheProvider !== 'local-only') {
            const cloudCacheProviderFactory = rushSession.getCloudBuildCacheProviderFactory(cacheProvider);
            if (!cloudCacheProviderFactory) {
                throw new Error(`Unexpected cache provider: ${cacheProvider}`);
            }
            cloudCacheProvider = await cloudCacheProviderFactory(buildCacheJson);
        }
        return new BuildCacheConfiguration({
            buildCacheJson,
            getCacheEntryId,
            rushConfiguration,
            rushUserConfiguration,
            rushSession,
            cloudCacheProvider
        });
    }
}
//# sourceMappingURL=BuildCacheConfiguration.js.map