"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.OperationMetadataManager = void 0;
const fs = __importStar(require("node:fs"));
const node_core_library_1 = require("@rushstack/node-core-library");
const terminal_1 = require("@rushstack/terminal");
const OperationStateFile_1 = require("./OperationStateFile");
const RushConstants_1 = require("../RushConstants");
const Stopwatch_1 = require("../../utilities/Stopwatch");
/**
 * A helper class for managing the meta files of a operation.
 *
 * @internal
 */
class OperationMetadataManager {
    constructor(options) {
        this.wasCobuilt = false;
        const { operation: { logFilenameIdentifier, associatedProject } } = options;
        const { projectFolder } = associatedProject;
        this.logFilenameIdentifier = logFilenameIdentifier;
        const metadataFolderPath = `${RushConstants_1.RushConstants.projectRushFolderName}/${RushConstants_1.RushConstants.rushTempFolderName}/operation/${logFilenameIdentifier}`;
        this.stateFile = new OperationStateFile_1.OperationStateFile({
            projectFolder: projectFolder,
            metadataFolder: metadataFolderPath
        });
        this._metadataFolderPath = metadataFolderPath;
        this._logPath = `${projectFolder}/${metadataFolderPath}/all.log`;
        this._errorLogPath = `${projectFolder}/${metadataFolderPath}/error.log`;
        this._logChunksPath = `${projectFolder}/${metadataFolderPath}/log-chunks.jsonl`;
    }
    /**
     * Returns the relative paths of the metadata files to project folder.
     *
     * Example: `.rush/temp/operation/_phase_build/state.json`
     * Example: `.rush/temp/operation/_phase_build/all.log`
     * Example: `.rush/temp/operation/_phase_build/error.log`
     */
    get metadataFolderPath() {
        return this._metadataFolderPath;
    }
    async saveAsync({ durationInSeconds, cobuildContextId, cobuildRunnerId, logPath, errorLogPath, logChunksPath }) {
        const state = {
            nonCachedDurationMs: durationInSeconds * 1000,
            cobuildContextId,
            cobuildRunnerId
        };
        await this.stateFile.writeAsync(state);
        const copyFileOptions = [
            {
                sourcePath: logPath,
                destinationPath: this._logPath
            },
            {
                sourcePath: errorLogPath,
                destinationPath: this._errorLogPath
            },
            {
                sourcePath: logChunksPath,
                destinationPath: this._logChunksPath
            }
        ];
        // Try to copy log files
        await node_core_library_1.Async.forEachAsync(copyFileOptions, async (options) => {
            try {
                await node_core_library_1.FileSystem.copyFileAsync(options);
            }
            catch (e) {
                if (!node_core_library_1.FileSystem.isNotExistError(e)) {
                    throw e;
                }
            }
        });
    }
    async tryRestoreAsync({ terminal, terminalProvider, errorLogPath, cobuildContextId, cobuildRunnerId }) {
        var _a, _b, _c;
        await this.stateFile.tryRestoreAsync();
        this.wasCobuilt =
            ((_a = this.stateFile.state) === null || _a === void 0 ? void 0 : _a.cobuildContextId) !== undefined &&
                cobuildContextId !== undefined &&
                ((_b = this.stateFile.state) === null || _b === void 0 ? void 0 : _b.cobuildContextId) === cobuildContextId &&
                ((_c = this.stateFile.state) === null || _c === void 0 ? void 0 : _c.cobuildRunnerId) !== cobuildRunnerId;
        try {
            const rawLogChunks = await node_core_library_1.FileSystem.readFileAsync(this._logChunksPath);
            const chunks = [];
            for (const chunk of rawLogChunks.split('\n')) {
                if (chunk) {
                    chunks.push(JSON.parse(chunk));
                }
            }
            for (const { kind, text } of chunks) {
                if (kind === terminal_1.TerminalChunkKind.Stderr) {
                    terminalProvider.write(text, terminal_1.TerminalProviderSeverity.error);
                }
                else {
                    terminalProvider.write(text, terminal_1.TerminalProviderSeverity.log);
                }
            }
        }
        catch (e) {
            if (node_core_library_1.FileSystem.isNotExistError(e)) {
                // Log chunks file doesn't exist, try to restore log file
                await restoreFromLogFile(terminal, this._logPath);
            }
            else {
                throw e;
            }
        }
        // Try to restore cached error log as error log file
        try {
            await node_core_library_1.FileSystem.copyFileAsync({
                sourcePath: this._errorLogPath,
                destinationPath: errorLogPath
            });
        }
        catch (e) {
            if (!node_core_library_1.FileSystem.isNotExistError(e)) {
                throw e;
            }
        }
    }
    tryRestoreStopwatch(originalStopwatch) {
        var _a;
        if (this.wasCobuilt && this.stateFile.state && originalStopwatch.endTime !== undefined) {
            const endTime = originalStopwatch.endTime;
            const startTime = Math.max(0, endTime - ((_a = this.stateFile.state.nonCachedDurationMs) !== null && _a !== void 0 ? _a : 0));
            return Stopwatch_1.Stopwatch.fromState({
                startTime,
                endTime
            });
        }
        return originalStopwatch;
    }
}
exports.OperationMetadataManager = OperationMetadataManager;
async function restoreFromLogFile(terminal, path) {
    let logReadStream;
    try {
        logReadStream = fs.createReadStream(path, {
            encoding: 'utf-8'
        });
        for await (const data of logReadStream) {
            terminal.write(data);
        }
    }
    catch (logReadStreamError) {
        if (!node_core_library_1.FileSystem.isNotExistError(logReadStreamError)) {
            throw logReadStreamError;
        }
    }
    finally {
        // Close the read stream
        logReadStream === null || logReadStream === void 0 ? void 0 : logReadStream.close();
    }
}
//# sourceMappingURL=OperationMetadataManager.js.map