"use strict";
/**
 * @fileoverview Common utility functions for compilation, file operations, and error handling.
 * Provides cross-language compilation, test filtering, config reading, and directory management.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.API_KEY_LOCATION = exports.SECRET_KEY_LOCATION = exports.ENV = exports.DEFAULT_MEMORY_LIMIT = exports.DEFAULT_TIMEOUT = void 0;
exports.compileCPP = compileCPP;
exports.compileJava = compileJava;
exports.filterTestsByRange = filterTestsByRange;
exports.getTestFiles = getTestFiles;
exports.readConfigFile = readConfigFile;
exports.isNumeric = isNumeric;
exports.logError = logError;
exports.logErrorAndExit = logErrorAndExit;
exports.logErrorAndThrow = logErrorAndThrow;
exports.throwError = throwError;
exports.ensureDirectoryExists = ensureDirectoryExists;
exports.removeDirectoryRecursively = removeDirectoryRecursively;
exports.readFirstLine = readFirstLine;
exports.getCompiledCommandToRun = getCompiledCommandToRun;
const path_1 = __importDefault(require("path"));
const executor_1 = require("../executor");
const fs_1 = __importDefault(require("fs"));
const formatter_1 = require("../formatter");
/** Default compilation timeout in milliseconds */
exports.DEFAULT_TIMEOUT = 10000;
/** Default memory limit in megabytes */
exports.DEFAULT_MEMORY_LIMIT = 1024;
// ENV is win or unix
exports.ENV = process.platform === 'win32' ? 'win' : 'unix';
exports.SECRET_KEY_LOCATION = exports.ENV === 'win'
    ? '%USERPROFILE%\\.polyman\\secret_key'
    : '~/.polyman/secret_key';
exports.API_KEY_LOCATION = exports.ENV === 'win' ? '%USERPROFILE%\\.polyman\\api_key' : '~/.polyman/api_key';
/**
 * Compiles a C++ source file using g++.
 * Uses -O2 optimization and C++23 standard.
 *
 * @param {string} sourcePath - Path to the .cpp source file
 * @returns {Promise<string>} Path to the compiled executable
 *
 * @throws {Error} If file is not .cpp or compilation fails
 *
 * @example
 * const executablePath = await compileCPP('solutions/main.cpp');
 * // Returns: '/path/to/solutions/main'
 */
async function compileCPP(sourcePath) {
    const absolutePath = path_1.default.resolve(process.cwd(), sourcePath);
    if (path_1.default.extname(absolutePath) !== '.cpp') {
        throw new Error(`Expected .cpp file, got: ${absolutePath}`);
    }
    const outputPath = absolutePath.replace('.cpp', '');
    const compileCommand = `g++ -o ${outputPath} ${absolutePath}`;
    await executor_1.executor.execute(compileCommand, {
        timeout: exports.DEFAULT_TIMEOUT,
        silent: true,
    });
}
/**
 * Compiles a Java source file using javac.
 *
 * @param {string} sourcePath - Path to the .java source file
 * @returns {Promise<string>} Java execution command (e.g., "java -cp /path ClassName")
 *
 * @throws {Error} If compilation fails
 *
 * @example
 * const javaCommand = await compileJava('solutions/Solution.java');
 * // Returns: 'java -cp /path/to/solutions Solution'
 */
async function compileJava(sourcePath) {
    const absolutePath = path_1.default.resolve(sourcePath);
    await executor_1.executor.execute(`javac ${absolutePath}`, {
        timeout: exports.DEFAULT_TIMEOUT,
        silent: true,
    });
}
/**
 * Filters test files by a numerical range.
 * Looks for files named like "test1.txt", "test2.txt", etc.
 *
 * @param {string[]} testFiles - Array of test file names
 * @param {number} [testBegin] - Start of range (inclusive), optional
 * @param {number} [testEnd] - End of range (inclusive), optional
 * @returns {string[]} Filtered array of test files
 *
 * @example
 * const allTests = ['test1.txt', 'test2.txt', 'test3.txt'];
 * const filtered = filterTestsByRange(allTests, 1, 2);
 * // Returns: ['test1.txt', 'test2.txt']
 */
function filterTestsByRange(testFiles, testBegin, testEnd) {
    if (testBegin === undefined || testEnd === undefined) {
        return testFiles;
    }
    return testFiles.filter(file => {
        if (!file.startsWith('test'))
            return false;
        const numberPart = file.slice(4, file.lastIndexOf('.'));
        const testNumber = parseInt(numberPart, 10);
        return (!isNaN(testNumber) && testNumber >= testBegin && testNumber <= testEnd);
    });
}
/**
 * Gets all test files from tests directory.
 * Returns only files starting with 'test' prefix in sorted order by test number.
 *
 * @private
 * @param {string} testsDir - Path to tests directory
 * @returns {string[]} Array of test filenames
 *
 * @example
 * getTestFiles('/path/to/tests')
 * // Returns: ['test1.txt', 'test2.txt', 'test3.txt', ...]
 */
function getTestFiles(testsDir) {
    return fs_1.default
        .readdirSync(testsDir)
        .filter(file => file.startsWith('test'))
        .sort((a, b) => {
        const numA = parseInt(a.replace('test', '').replace('.txt', ''));
        const numB = parseInt(b.replace('test', '').replace('.txt', ''));
        return numA - numB;
    });
}
/**
 * Reads and parses the Config.json file from the current working directory.
 *
 * @returns {ConfigFile} Parsed configuration object
 *
 * @throws {Error} If Config.json doesn't exist or is invalid JSON
 *
 * @example
 * const config = readConfigFile();
 * console.log(config.solutions); // Array of solutions
 */
function readConfigFile() {
    try {
        const configFilePath = path_1.default.resolve(process.cwd(), 'Config.json');
        const configData = fs_1.default.readFileSync(configFilePath, 'utf-8');
        return JSON.parse(configData);
    }
    catch (error) {
        throw error instanceof Error
            ? error
            : new Error('Failed to read or parse Config.json file.');
    }
}
/**
 * Checks if a string represents a numeric value.
 *
 * @param {string} value - String to check
 * @returns {boolean} True if value is numeric
 *
 * @example
 * isNumeric('42');  // true
 * isNumeric('all'); // false
 */
function isNumeric(value) {
    return !isNaN(parseInt(value, 10));
}
/**
 * Logs an error message with a cross icon.
 * Formats the error using the formatter.
 *
 * @param {unknown} error - Error to log
 *
 * @example
 * try {
 *   await compileCPP('invalid.cpp');
 * } catch (error) {
 *   logError(error);
 * }
 */
function logError(error, indent = 1) {
    const message = error instanceof Error ? error.message : String(error);
    formatter_1.fmt.error(`${' '.repeat(indent)} ${formatter_1.fmt.cross()} ${message}`);
}
/**
 * Logs an error and exits the process with code 1.
 *
 * @param {unknown} error - Error to log
 *
 * @example
 * if (!fs.existsSync('Config.json')) {
 *   logErrorAndExit(new Error('Config.json not found'));
 * }
 */
function logErrorAndExit(error) {
    logError(error);
    process.exit(1);
}
/**
 * Logs an error and re-throws it.
 *
 * @param {unknown} error - Error to log and throw
 * @param {string} [message=''] - Additional context message
 *
 * @throws {Error} Always throws after logging
 *
 * @example
 * try {
 *   dangerousOperation();
 * } catch (error) {
 *   logErrorAndThrow(error, 'Operation failed');
 * }
 */
function logErrorAndThrow(error, message = '') {
    logError(error);
    throwError(error, message);
}
/**
 * Throws an error, ensuring it's an Error instance.
 * If the input is not an Error, wraps it in one.
 *
 * @param {unknown} error - Error to throw
 * @param {string} [message=''] - Additional context message
 *
 * @throws {Error} Always throws
 *
 * @example
 * throwError(new Error('Something went wrong'));
 */
function throwError(error, message = '') {
    throw error instanceof Error
        ? error
        : new Error(`${message}: ${String(error)}`);
}
/**
 * Ensures a directory exists, creating it if necessary.
 * Creates parent directories recursively.
 *
 * @param {string} dirName - Directory name/path relative to current working directory
 *
 * @example
 * ensureDirectoryExists('testsets');
 * ensureDirectoryExists('nested/path/to/dir');
 */
function ensureDirectoryExists(dirName) {
    const dirPath = path_1.default.resolve(process.cwd(), dirName);
    if (!fs_1.default.existsSync(dirPath)) {
        fs_1.default.mkdirSync(dirPath, { recursive: true });
    }
}
/**
 * Removes a directory and all its contents.
 * Does nothing if directory doesn't exist.
 *
 * @param {string} dirName - Directory name/path relative to current working directory
 *
 * @example
 * removeDirectoryRecursively('temp');
 */
function removeDirectoryRecursively(dirName) {
    const dirPath = path_1.default.resolve(process.cwd(), dirName);
    if (fs_1.default.existsSync(dirPath)) {
        fs_1.default.rmSync(dirPath, { recursive: true, force: true });
    }
}
/**
 * Reads the first line of a file efficiently using streaming.
 * Useful for reading verdict from validator/checker output.
 *
 * @param {string} filePath - Path to the file
 * @returns {Promise<string>} First line of the file
 *
 * @throws {Error} If file cannot be read
 *
 * @example
 * const verdict = await readFirstLine('/tmp/validator_output.txt');
 * // Returns: 'VALID' or 'INVALID'
 */
function readFirstLine(filePath) {
    // use file stream
    const fileStream = fs_1.default.createReadStream(filePath, { encoding: 'utf-8' });
    return new Promise((resolve, reject) => {
        let data = '';
        fileStream.on('data', chunk => {
            data += String(chunk);
            const lines = data.split(/\r?\n/);
            if (lines.length > 1) {
                fileStream.close();
                resolve(lines[0]);
            }
        });
        fileStream.on('end', () => {
            const lines = data.split(/\r?\n/);
            resolve(lines[0] || '');
        });
        fileStream.on('error', err => {
            reject(err);
        });
    });
}
function getCompiledCommandToRun(object) {
    if ('isStandard' in object && object.isStandard) {
        const checkerName = object.source.replace('.cpp', '');
        return path_1.default.resolve(__dirname, '../..', 'assets', 'checkers', `${checkerName}`);
    }
    const source = path_1.default.resolve(process.cwd(), object.source);
    const extention = path_1.default.extname(source);
    switch (extention) {
        case '.cpp':
            return source.replace('.cpp', '');
        case '.java': {
            const dir = path_1.default.dirname(source);
            const fileName = path_1.default.basename(source);
            const className = fileName.replace('.java', '');
            return `java -cp ${dir} ${className}`;
        }
        case '.py':
            return `python ${source}`;
        case '.js':
            return `node ${source}`;
        default:
            throw new Error(`Unsupported source file extension: ${extention}`);
    }
}
//# sourceMappingURL=utils.js.map