// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import * as fs from 'node:fs';
import { Async, FileSystem } from '@rushstack/node-core-library';
import { TerminalChunkKind, TerminalProviderSeverity } from '@rushstack/terminal';
import { OperationStateFile } from './OperationStateFile';
import { RushConstants } from '../RushConstants';
import { Stopwatch } from '../../utilities/Stopwatch';
/**
 * A helper class for managing the meta files of a operation.
 *
 * @internal
 */
export class OperationMetadataManager {
    constructor(options) {
        this.wasCobuilt = false;
        const { operation: { logFilenameIdentifier, associatedProject } } = options;
        const { projectFolder } = associatedProject;
        this.logFilenameIdentifier = logFilenameIdentifier;
        const metadataFolderPath = `${RushConstants.projectRushFolderName}/${RushConstants.rushTempFolderName}/operation/${logFilenameIdentifier}`;
        this.stateFile = new 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 Async.forEachAsync(copyFileOptions, async (options) => {
            try {
                await FileSystem.copyFileAsync(options);
            }
            catch (e) {
                if (!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 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 === TerminalChunkKind.Stderr) {
                    terminalProvider.write(text, TerminalProviderSeverity.error);
                }
                else {
                    terminalProvider.write(text, TerminalProviderSeverity.log);
                }
            }
        }
        catch (e) {
            if (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 FileSystem.copyFileAsync({
                sourcePath: this._errorLogPath,
                destinationPath: errorLogPath
            });
        }
        catch (e) {
            if (!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.fromState({
                startTime,
                endTime
            });
        }
        return originalStopwatch;
    }
}
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 (!FileSystem.isNotExistError(logReadStreamError)) {
            throw logReadStreamError;
        }
    }
    finally {
        // Close the read stream
        logReadStream === null || logReadStream === void 0 ? void 0 : logReadStream.close();
    }
}
//# sourceMappingURL=OperationMetadataManager.js.map