// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { FileSystem, JsonFile, JsonSchema, NewlineKind } from '@rushstack/node-core-library';
import { PnpmShrinkwrapFile } from './pnpm/PnpmShrinkwrapFile';
import schemaJson from '../schemas/repo-state.schema.json';
/**
 * This file is used to track the state of various Rush-related features. It is generated
 * and updated by Rush.
 *
 * @public
 */
export class RepoStateFile {
    constructor(repoStateJson, isValid, filePath) {
        this._modified = false;
        this.filePath = filePath;
        this._isValid = isValid;
        if (repoStateJson) {
            this._pnpmShrinkwrapHash = repoStateJson.pnpmShrinkwrapHash;
            this._preferredVersionsHash = repoStateJson.preferredVersionsHash;
            this._packageJsonInjectedDependenciesHash = repoStateJson.packageJsonInjectedDependenciesHash;
            this._pnpmCatalogsHash = repoStateJson.pnpmCatalogsHash;
        }
    }
    /**
     * The hash of the pnpm shrinkwrap file at the end of the last update.
     */
    get pnpmShrinkwrapHash() {
        return this._pnpmShrinkwrapHash;
    }
    /**
     * The hash of all preferred versions at the end of the last update.
     */
    get preferredVersionsHash() {
        return this._preferredVersionsHash;
    }
    /**
     * The hash of all preferred versions at the end of the last update.
     */
    get packageJsonInjectedDependenciesHash() {
        return this._packageJsonInjectedDependenciesHash;
    }
    /**
     * The hash of the PNPM catalog definitions at the end of the last update.
     */
    get pnpmCatalogsHash() {
        return this._pnpmCatalogsHash;
    }
    /**
     * If false, the repo-state.json file is not valid and its values cannot be relied upon
     */
    get isValid() {
        return this._isValid;
    }
    /**
     * Loads the repo-state.json data from the specified file path.
     * If the file has not been created yet, then an empty object is returned.
     *
     * @param jsonFilename - The path to the repo-state.json file.
     */
    static loadFromFile(jsonFilename) {
        let fileContents;
        try {
            fileContents = FileSystem.readFile(jsonFilename);
        }
        catch (error) {
            if (!FileSystem.isNotExistError(error)) {
                throw error;
            }
        }
        let foundMergeConflictMarker = false;
        let repoStateJson = undefined;
        if (fileContents) {
            try {
                repoStateJson = JsonFile.parseString(fileContents);
            }
            catch (error) {
                // Look for a Git merge conflict marker. PNPM gracefully handles merge conflicts in pnpm-lock.yaml,
                // so a user should be able to just run "rush update" if they get conflicts in pnpm-lock.yaml
                // and repo-state.json and have Rush update both.
                for (let nextNewlineIndex = 0; nextNewlineIndex > -1; nextNewlineIndex = fileContents.indexOf('\n', nextNewlineIndex + 1)) {
                    if (fileContents.substr(nextNewlineIndex + 1, 7) === '<<<<<<<') {
                        foundMergeConflictMarker = true;
                        repoStateJson = {
                            preferredVersionsHash: 'INVALID',
                            pnpmShrinkwrapHash: 'INVALID'
                        };
                        break;
                    }
                }
            }
            if (repoStateJson) {
                this._jsonSchema.validateObject(repoStateJson, jsonFilename);
            }
        }
        return new RepoStateFile(repoStateJson, !foundMergeConflictMarker, jsonFilename);
    }
    /**
     * Refresh the data contained in repo-state.json using the current state
     * of the Rush repo, and save the file if changes were made.
     *
     * @param rushConfiguration - The Rush configuration for the repo.
     * @param subspace - The subspace that repo-state.json was loaded from,
     * or `undefined` for the default subspace.
     *
     * @returns true if the file was modified, otherwise false.
     */
    refreshState(rushConfiguration, subspace, variant) {
        if (subspace === undefined) {
            subspace = rushConfiguration.defaultSubspace;
        }
        // Only support saving the pnpm shrinkwrap hash if it was enabled
        const preventShrinkwrapChanges = rushConfiguration.isPnpm &&
            rushConfiguration.pnpmOptions &&
            rushConfiguration.pnpmOptions.preventManualShrinkwrapChanges;
        if (preventShrinkwrapChanges) {
            const pnpmShrinkwrapFile = PnpmShrinkwrapFile.loadFromFile(subspace.getCommittedShrinkwrapFilePath(variant), { subspaceHasNoProjects: subspace.getProjects().length === 0 });
            if (pnpmShrinkwrapFile) {
                const shrinkwrapFileHash = pnpmShrinkwrapFile.getShrinkwrapHash(rushConfiguration.experimentsConfiguration.configuration);
                if (this._pnpmShrinkwrapHash !== shrinkwrapFileHash) {
                    this._pnpmShrinkwrapHash = shrinkwrapFileHash;
                    this._modified = true;
                }
            }
        }
        else if (this._pnpmShrinkwrapHash !== undefined) {
            this._pnpmShrinkwrapHash = undefined;
            this._modified = true;
        }
        // Currently, only support saving the preferred versions hash if using workspaces
        const useWorkspaces = rushConfiguration.pnpmOptions && rushConfiguration.pnpmOptions.useWorkspaces;
        if (useWorkspaces) {
            const commonVersions = subspace.getCommonVersions(variant);
            const preferredVersionsHash = commonVersions.getPreferredVersionsHash();
            if (this._preferredVersionsHash !== preferredVersionsHash) {
                this._preferredVersionsHash = preferredVersionsHash;
                this._modified = true;
            }
        }
        else if (this._preferredVersionsHash !== undefined) {
            this._preferredVersionsHash = undefined;
            this._modified = true;
        }
        if (rushConfiguration.isPnpm) {
            const packageJsonInjectedDependenciesHash = subspace.getPackageJsonInjectedDependenciesHash(variant);
            // packageJsonInjectedDependenciesHash is undefined, means there is no injected dependencies for that subspace
            // so we don't need to track the hash value for that subspace
            if (packageJsonInjectedDependenciesHash &&
                packageJsonInjectedDependenciesHash !== this._packageJsonInjectedDependenciesHash) {
                this._packageJsonInjectedDependenciesHash = packageJsonInjectedDependenciesHash;
                this._modified = true;
            }
            else if (!packageJsonInjectedDependenciesHash && this._packageJsonInjectedDependenciesHash) {
                // if packageJsonInjectedDependenciesHash is undefined, but this._packageJsonInjectedDependenciesHash is not
                // means users may turn off the injected installation
                // so we will need to remove unused fields in repo-state.json as well
                this._packageJsonInjectedDependenciesHash = undefined;
                this._modified = true;
            }
            // Track catalog hash to detect when catalog definitions change
            const pnpmCatalogsHash = subspace.getPnpmCatalogsHash();
            if (pnpmCatalogsHash && pnpmCatalogsHash !== this._pnpmCatalogsHash) {
                this._pnpmCatalogsHash = pnpmCatalogsHash;
                this._modified = true;
            }
            else if (!pnpmCatalogsHash && this._pnpmCatalogsHash) {
                this._pnpmCatalogsHash = undefined;
                this._modified = true;
            }
        }
        // Now that the file has been refreshed, we know its contents are valid
        this._isValid = true;
        return this._saveIfModified();
    }
    /**
     * Writes the "repo-state.json" file to disk, using the filename that was passed to loadFromFile().
     */
    _saveIfModified() {
        if (this._modified) {
            const content = '// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.' +
                `${NewlineKind.Lf}${this._serialize()}`;
            FileSystem.writeFile(this.filePath, content);
            this._modified = false;
            return true;
        }
        return false;
    }
    _serialize() {
        // We need to set these one-by-one, since JsonFile.stringify does not like undefined values
        const repoStateJson = {};
        if (this._pnpmShrinkwrapHash) {
            repoStateJson.pnpmShrinkwrapHash = this._pnpmShrinkwrapHash;
        }
        if (this._preferredVersionsHash) {
            repoStateJson.preferredVersionsHash = this._preferredVersionsHash;
        }
        if (this._packageJsonInjectedDependenciesHash) {
            repoStateJson.packageJsonInjectedDependenciesHash = this._packageJsonInjectedDependenciesHash;
        }
        if (this._pnpmCatalogsHash) {
            repoStateJson.pnpmCatalogsHash = this._pnpmCatalogsHash;
        }
        return JsonFile.stringify(repoStateJson, { newlineConversion: NewlineKind.Lf });
    }
}
RepoStateFile._jsonSchema = JsonSchema.fromLoadedObject(schemaJson);
//# sourceMappingURL=RepoStateFile.js.map