"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.batchRunnerPath = void 0;
exports.default = gradleBatch;
exports.getGradlewTasksToRun = getGradlewTasksToRun;
const devkit_1 = require("@nx/devkit");
const exec_gradle_1 = require("../../utils/exec-gradle");
const path_1 = require("path");
const child_process_1 = require("child_process");
const readline_1 = require("readline");
const get_exclude_task_1 = require("./get-exclude-task");
exports.batchRunnerPath = (0, path_1.join)(__dirname, '../../../batch-runner/build/libs/gradle-batch-runner-all.jar');
async function* gradleBatch(taskGraph, inputs, overrides, context) {
    const projectName = taskGraph.tasks[taskGraph.roots[0]]?.target?.project;
    const projectRoot = context.projectGraph.nodes[projectName]?.data?.root ?? '';
    const customGradleExecutableDirectory = (0, exec_gradle_1.getCustomGradleExecutableDirectoryFromPlugin)(context.nxJsonConfiguration);
    let gradlewPath = (0, exec_gradle_1.findGradlewFile)((0, path_1.join)(projectRoot, 'project.json'), devkit_1.workspaceRoot, customGradleExecutableDirectory);
    gradlewPath = (0, path_1.join)(context.root, gradlewPath);
    const root = (0, path_1.dirname)(gradlewPath);
    // set args with passed in args and overrides in command line
    const input = inputs[taskGraph.roots[0]];
    const args = typeof input.args === 'string'
        ? input.args.trim().split(' ')
        : Array.isArray(input.args)
            ? input.args
            : [];
    if (overrides.__overrides_unparsed__.length) {
        args.push(...overrides.__overrides_unparsed__);
    }
    const taskIds = Object.keys(taskGraph.tasks);
    const { gradlewTasksToRun, excludeTasks, excludeTestTasks } = getGradlewTasksToRun(taskIds, taskGraph, inputs, context.projectGraph.nodes, context.taskGraph ?? taskGraph);
    const yielded = new Set();
    try {
        for await (const event of streamTasksInBatch(gradlewTasksToRun, excludeTasks, excludeTestTasks, args, root)) {
            yielded.add(event.task);
            yield event;
        }
    }
    catch (e) {
        devkit_1.output.error({
            title: `Gradlew batch failed`,
            bodyLines: [e.toString()],
        });
        for (const taskId of taskIds) {
            if (!yielded.has(taskId)) {
                yielded.add(taskId);
                yield {
                    task: taskId,
                    result: { success: false, terminalOutput: e.toString() },
                };
            }
        }
    }
}
function getGradlewTasksToRun(taskIds, taskGraph, inputs, nodes, fullTaskGraph = taskGraph) {
    const tasksWithExcludeIds = new Set();
    const testTasksWithExcludeIds = new Set();
    const tasksWithoutExcludeIds = new Set();
    const gradlewTasksToRun = {};
    const includeDependsOnTasks = new Set();
    for (const taskId of taskIds) {
        const input = inputs[taskId];
        gradlewTasksToRun[taskId] = input;
        if (input.includeDependsOnTasks) {
            for (const t of input.includeDependsOnTasks) {
                includeDependsOnTasks.add(t);
            }
        }
        if (input.excludeDependsOn) {
            if (input.testClassName) {
                testTasksWithExcludeIds.add(taskId);
            }
            else {
                tasksWithExcludeIds.add(taskId);
            }
        }
        else {
            tasksWithoutExcludeIds.add(taskId);
        }
    }
    const runningTaskIds = new Set(taskIds);
    for (const depId of (0, get_exclude_task_1.getAllDependsOnFromTaskGraph)(tasksWithoutExcludeIds, fullTaskGraph)) {
        runningTaskIds.add(depId);
    }
    const excludeTasks = (0, get_exclude_task_1.getExcludeTasksFromTaskGraph)(tasksWithExcludeIds, runningTaskIds, fullTaskGraph, nodes, includeDependsOnTasks);
    const excludeTestTasks = (0, get_exclude_task_1.getExcludeTasksFromTaskGraph)(testTasksWithExcludeIds, new Set(), fullTaskGraph, nodes);
    return {
        gradlewTasksToRun,
        excludeTasks,
        excludeTestTasks,
    };
}
async function* streamTasksInBatch(gradlewTasksToRun, excludeTasks, excludeTestTasks, args, root) {
    const gradlewBatchStart = performance.mark(`gradlew-batch:start`);
    const debugOptions = (process.env.NX_GRADLE_BATCH_DEBUG ?? '').trim();
    const spawnArgs = [
        ...(debugOptions ? debugOptions.split(/\s+/) : []),
        '-jar',
        exports.batchRunnerPath,
        `--tasks=${JSON.stringify(gradlewTasksToRun)}`,
        `--workspaceRoot=${root}`,
        `--args=${args.join(' ').replaceAll("'", '"')}`,
        `--excludeTasks=${Array.from(excludeTasks).join(',')}`,
        `--excludeTestTasks=${Array.from(excludeTestTasks).join(',')}`,
        ...(process.env.NX_VERBOSE_LOGGING === 'true' ? [] : ['--quiet']),
    ];
    // stderr is inherited so Gradle output (tee'd to System.err by TeeOutputStream)
    // and logger output flow to the terminal in real-time.
    // stdout is piped so we can read NX_RESULT lines emitted per task.
    const cp = (0, child_process_1.spawn)('java', spawnArgs, {
        cwd: devkit_1.workspaceRoot,
        windowsHide: true,
        env: process.env,
        stdio: ['pipe', 'pipe', 'inherit'],
    });
    const exit = new Promise((resolve, reject) => {
        cp.on('error', reject);
        cp.on('close', (code) => resolve(code ?? 0));
    });
    const rl = (0, readline_1.createInterface)({ input: cp.stdout, crlfDelay: Infinity });
    // Drain stdout into a queue eagerly. Yielding inside the readline loop creates
    // back-pressure: if the consumer is slow, `yield` blocks, readline pauses, the
    // OS pipe between Java and Node fills, and Java's NX_RESULT println blocks on
    // a full pipe — the JVM hangs even though all task work is done.
    const queue = [];
    let notifyAvailable = null;
    let readerClosed = false;
    rl.on('line', (line) => {
        if (!line.startsWith('NX_RESULT:'))
            return;
        try {
            const data = JSON.parse(line.slice('NX_RESULT:'.length));
            queue.push({
                task: data.task,
                result: {
                    success: data.result.success ?? false,
                    status: data.result.status,
                    terminalOutput: data.result.terminalOutput ?? '',
                    startTime: data.result.startTime,
                    endTime: data.result.endTime,
                },
            });
        }
        catch (e) {
            console.error('[Gradle Batch] Failed to parse result line:', line, e);
            return;
        }
        notifyAvailable?.();
        notifyAvailable = null;
    });
    rl.on('close', () => {
        readerClosed = true;
        notifyAvailable?.();
        notifyAvailable = null;
    });
    try {
        while (!readerClosed || queue.length > 0) {
            if (queue.length > 0) {
                yield queue.shift();
            }
            else {
                await new Promise((resolve) => {
                    notifyAvailable = resolve;
                });
            }
        }
    }
    finally {
        rl.close();
    }
    const code = await exit;
    const gradlewBatchEnd = performance.mark(`gradlew-batch:end`);
    performance.measure(`gradlew-batch`, gradlewBatchStart.name, gradlewBatchEnd.name);
    if (code !== 0) {
        throw new Error(`Gradle batch runner exited with code ${code}`);
    }
}
