// SPDX-License-Identifier: Apache-2.0
import { spawn } from 'node:child_process';
import { HelmExecutionException } from '../helm-execution-exception.js';
import { HelmParserException } from '../helm-parser-exception.js';
/**
 * Represents the execution of a helm command and is responsible for parsing the response.
 */
export class HelmExecution {
    /**
     * The logger for this class which should be used for all logging.
     */
    static MSG_TIMEOUT_ERROR = 'Timed out waiting for the process to complete';
    /**
     * The message for a timeout error.
     */
    static MSG_DESERIALIZATION_ERROR = 'Failed to deserialize the output into the specified class: %s';
    /**
     * The message for a deserialization error.
     */
    static MSG_LIST_DESERIALIZATION_ERROR = 'Failed to deserialize the output into a list of the specified class: %s';
    process;
    commandLine;
    logger;
    output = [];
    errOutput = [];
    exitCodeValue = null;
    /**
     * Creates a new HelmExecution instance.
     * @param command The command array to execute
     * @param workingDirectory The working directory for the process
     * @param environmentVariables The environment variables to set
     * @param logger Optional logger for command output
     */
    constructor(command, workingDirectory, environmentVariables, logger) {
        this.logger = logger;
        this.commandLine = command.join(' ');
        if (this.logger) {
            this.logger.info(`Executing helm command: ${this.commandLine}`);
        }
        this.process = spawn(command.join(' '), {
            shell: true,
            cwd: workingDirectory,
            env: { ...process.env, ...environmentVariables },
        });
    }
    /**
     * Waits for the process to complete.
     * @returns A promise that resolves when the process completes
     */
    async waitFor() {
        return new Promise((resolve, reject) => {
            // const output: string[] = [];
            this.process.stdout.on('data', (d) => {
                const items = d.toString().split(/\r?\n/);
                for (const item of items) {
                    if (item) {
                        this.output.push(item);
                    }
                }
            });
            this.process.stderr.on('data', (d) => {
                const items = d.toString().split(/\r?\n/);
                for (const item of items) {
                    if (item) {
                        this.errOutput.push(item.trim());
                    }
                }
            });
            this.process.on('close', (code) => {
                this.exitCodeValue = code;
                if (code === 0) {
                    resolve();
                }
                else {
                    reject(new HelmExecutionException(code || 1, `Helm command failed with exit code ${code}. Command: '${this.commandLine}'. Error: ${this.standardError()}`, this.standardOutput(), this.standardError()));
                }
            });
        });
    }
    /**
     * Waits for the process to complete with a timeout.
     * @param timeout The maximum time to wait, or null to wait indefinitely
     * @returns A promise that resolves with true if the process completed, or false if it timed out
     */
    async waitForTimeout(timeout) {
        const timeoutPromise = new Promise((resolve) => {
            setTimeout(() => resolve(false), timeout.toMillis());
        });
        const successPromise = new Promise((resolve) => {
            this.process.on('close', (code) => {
                resolve(code === 0);
            });
        });
        return Promise.race([successPromise, timeoutPromise]);
    }
    /**
     * Gets the exit code of the process.
     * @returns The exit code or null if the process hasn't completed
     */
    exitCode() {
        return this.exitCodeValue;
    }
    /**
     * Gets the standard output of the process.
     * @returns concatenated standard output as a string
     */
    standardOutput() {
        return this.output.join('');
    }
    /**
     * Gets the standard error of the process.
     * @returns concatenated standard error as a string
     */
    standardError() {
        return this.errOutput.join('');
    }
    /**
     * Gets the response as a parsed object.
     * @param responseClass The class to parse the response into
     * @returns A promise that resolves with the parsed response
     */
    async responseAs(responseClass) {
        return this.responseAsTimeout(responseClass, null);
    }
    /**
     * Gets the response as a parsed object with a timeout.
     * @param responseClass The class to parse the response into
     * @param timeout The maximum time to wait, or null to wait indefinitely
     * @returns A promise that resolves with the parsed response or rejects on timeout
     */
    async responseAsTimeout(responseClass, timeout) {
        if (timeout === null) {
            await this.waitFor();
        }
        else {
            const success = await this.waitForTimeout(timeout);
            if (!success) {
                throw new HelmParserException(HelmExecution.MSG_TIMEOUT_ERROR);
            }
        }
        const exitCode = this.exitCode();
        if (exitCode !== 0) {
            const stdOut = this.standardOutput();
            const stdError = this.standardError();
            throw new HelmExecutionException(exitCode, `Helm command failed with exit code ${exitCode}. Command: '${this.commandLine}'. Error: ${stdError}`, stdOut, stdError);
        }
        if (responseClass === undefined) {
            return null;
        }
        const output = this.standardOutput();
        try {
            const parsed = JSON.parse(output);
            const result = new responseClass();
            Object.assign(result, parsed);
            return result;
        }
        catch {
            throw new HelmParserException(HelmExecution.MSG_DESERIALIZATION_ERROR.replace('%s', responseClass.name));
        }
    }
    /**
     * Gets the response as a list of parsed objects.
     * @param responseClass The class to parse each item in the response into
     * @returns A promise that resolves with the parsed response list
     */
    async responseAsList(responseClass) {
        return this.responseAsListTimeout(responseClass, null);
    }
    /**
     * Gets the response as a list of parsed objects with a timeout.
     * @param responseClass The class to parse each item in the response into
     * @param timeout The maximum time to wait, or null to wait indefinitely
     * @returns A promise that resolves with the parsed response list or rejects on timeout
     */
    async responseAsListTimeout(responseClass, timeout) {
        if (timeout === null) {
            await this.waitFor();
        }
        else {
            const success = await this.waitForTimeout(timeout);
            if (!success) {
                throw new HelmParserException(HelmExecution.MSG_TIMEOUT_ERROR);
            }
        }
        const exitCode = this.exitCode();
        if (exitCode !== 0) {
            const stdOut = this.standardOutput();
            const stdError = this.standardError();
            throw new HelmExecutionException(exitCode, `Helm command failed with exit code ${exitCode}. Command: '${this.commandLine}'. Error: ${stdError}`, stdOut, stdError);
        }
        const output = this.standardOutput();
        try {
            return JSON.parse(output);
        }
        catch {
            throw new HelmParserException(HelmExecution.MSG_LIST_DESERIALIZATION_ERROR.replace('%s', responseClass.name));
        }
    }
    /**
     * Executes the command and waits for completion.
     * @returns A promise that resolves when the command completes
     */
    async call() {
        await this.callTimeout(null);
    }
    /**
     * Executes the command and waits for completion with a timeout.
     * @param timeout The maximum time to wait, or null to wait indefinitely
     * @returns A promise that resolves when the command completes or rejects on timeout
     */
    async callTimeout(timeout) {
        if (timeout === null) {
            await this.waitFor();
        }
        else {
            const success = await this.waitForTimeout(timeout);
            if (!success) {
                throw new HelmParserException(HelmExecution.MSG_TIMEOUT_ERROR);
            }
        }
        const exitCode = this.exitCode();
        if (exitCode !== 0) {
            const stdOut = this.standardOutput();
            const stdError = this.standardError();
            throw new HelmExecutionException(exitCode, `Helm command failed with exit code ${exitCode}. Command: '${this.commandLine}'. Error: ${stdError}`, stdOut, stdError);
        }
    }
}
//# sourceMappingURL=helm-execution.js.map