"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeGradleReportToCache = writeGradleReportToCache;
exports.getCurrentGradleReport = getCurrentGradleReport;
exports.populateGradleReport = populateGradleReport;
exports.processProjectReports = processProjectReports;
exports.processGradleDependencies = processGradleDependencies;
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const devkit_1 = require("@nx/devkit");
const workspace_context_1 = require("nx/src/utils/workspace-context");
const path_1 = require("path");
const split_config_files_1 = require("../../utils/split-config-files");
const get_project_report_lines_1 = require("./get-project-report-lines");
const cache_directory_1 = require("nx/src/utils/cache-directory");
const exec_gradle_1 = require("../../utils/exec-gradle");
function readGradleReportCache(cachePath, hash) {
    const gradleReportJson = (0, node_fs_1.existsSync)(cachePath)
        ? (0, devkit_1.readJsonFile)(cachePath)
        : undefined;
    if (!gradleReportJson || gradleReportJson.hash !== hash) {
        return;
    }
    let results = {
        gradleFileToGradleProjectMap: new Map(Object.entries(gradleReportJson['gradleFileToGradleProjectMap'])),
        gradleProjectToDepsMap: new Map(Object.entries(gradleReportJson['gradleProjectToDepsMap']).map(([key, value]) => [key, new Set(value)])),
        gradleFileToOutputDirsMap: new Map(Object.entries(gradleReportJson['gradleFileToOutputDirsMap']).map(([key, value]) => [key, new Map(Object.entries(value))])),
        gradleProjectToTasksTypeMap: new Map(Object.entries(gradleReportJson['gradleProjectToTasksTypeMap']).map(([key, value]) => [key, new Map(Object.entries(value))])),
        gradleProjectToTasksMap: new Map(Object.entries(gradleReportJson['gradleProjectToTasksMap']).map(([key, value]) => [key, new Set(value)])),
        gradleProjectToProjectName: new Map(Object.entries(gradleReportJson['gradleProjectToProjectName'])),
        gradleProjectNameToProjectRootMap: new Map(Object.entries(gradleReportJson['gradleProjectNameToProjectRootMap'])),
        gradleProjectToChildProjects: new Map(Object.entries(gradleReportJson['gradleProjectToChildProjects'])),
    };
    return results;
}
function writeGradleReportToCache(cachePath, results, hash) {
    let gradleReportJson = {
        hash,
        gradleFileToGradleProjectMap: Object.fromEntries(results.gradleFileToGradleProjectMap),
        gradleProjectToDepsMap: Object.fromEntries(Array.from(results.gradleProjectToDepsMap).map(([key, value]) => [
            key,
            Array.from(value),
        ])),
        gradleFileToOutputDirsMap: Object.fromEntries(Array.from(results.gradleFileToOutputDirsMap).map(([key, value]) => [
            key,
            Object.fromEntries(value),
        ])),
        gradleProjectToTasksTypeMap: Object.fromEntries(Array.from(results.gradleProjectToTasksTypeMap).map(([key, value]) => [
            key,
            Object.fromEntries(value),
        ])),
        gradleProjectToTasksMap: Object.fromEntries(Array.from(results.gradleProjectToTasksMap).map(([key, value]) => [
            key,
            Array.from(value),
        ])),
        gradleProjectToProjectName: Object.fromEntries(results.gradleProjectToProjectName),
        gradleProjectNameToProjectRootMap: Object.fromEntries(results.gradleProjectNameToProjectRootMap),
        gradleProjectToChildProjects: Object.fromEntries(results.gradleProjectToChildProjects),
    };
    try {
        (0, devkit_1.writeJsonFile)(cachePath, gradleReportJson);
    }
    catch (e) {
        devkit_1.logger.warn(`Failed to write Gradle report cache to ${cachePath}: ${e instanceof Error ? e.message : 'unknown error'}`);
    }
}
let gradleReportCache;
let gradleReportCachePath = (0, node_path_1.join)(cache_directory_1.workspaceDataDirectory, 'gradle-report-v1.hash');
function getCurrentGradleReport() {
    if (!gradleReportCache) {
        throw new devkit_1.AggregateCreateNodesError([
            [
                null,
                new Error(`Expected cached gradle report. Please open an issue at https://github.com/nrwl/nx/issues/new/choose`),
            ],
        ], []);
    }
    return gradleReportCache;
}
/**
 * This function populates the gradle report cache.
 * For each gradlew file, it runs the `projectReportAll` task and processes the output.
 * If `projectReportAll` fails, it runs the `projectReport` task instead.
 * It will throw an error if both tasks fail.
 * It will accumulate the output of all gradlew files.
 * @param workspaceRoot
 * @param gradlewFiles absolute paths to all gradlew files in the workspace
 * @returns Promise<void>
 */
async function populateGradleReport(workspaceRoot, gradlewFiles) {
    const gradleConfigHash = await (0, workspace_context_1.hashWithWorkspaceContext)(workspaceRoot, [
        split_config_files_1.gradleConfigAndTestGlob,
    ]);
    const cached = readGradleReportCache(gradleReportCachePath, gradleConfigHash);
    if (cached) {
        gradleReportCache = cached;
        return;
    }
    const gradleProjectReportStart = performance.mark('gradleProjectReport:start');
    const projectReportLines = await gradlewFiles.reduce(async (projectReportLines, gradlewFile) => {
        const allLines = await projectReportLines;
        const currentLines = await (0, get_project_report_lines_1.getProjectReportLines)(gradlewFile);
        return [...allLines, ...currentLines];
    }, Promise.resolve([]));
    const gradleProjectReportEnd = performance.mark('gradleProjectReport:end');
    performance.measure('gradleProjectReport', gradleProjectReportStart.name, gradleProjectReportEnd.name);
    gradleReportCache = processProjectReports(projectReportLines);
    writeGradleReportToCache(gradleReportCachePath, gradleReportCache, gradleConfigHash);
}
function processProjectReports(projectReportLines) {
    /**
     * Map of Gradle File path to Gradle Project Name
     */
    const gradleFileToGradleProjectMap = new Map();
    const gradleProjectToDepsMap = new Map();
    /**
     * Map of Gradle Build File to tasks type map
     */
    const gradleProjectToTasksTypeMap = new Map();
    const gradleProjectToTasksMap = new Map();
    const gradleProjectToProjectName = new Map();
    const gradleProjectNameToProjectRootMap = new Map();
    /**
     * Map fo possible output files of each gradle file
     * e.g. {build.gradle.kts: { projectReportDir: '' testReportDir: '' }}
     */
    const gradleFileToOutputDirsMap = new Map();
    /**
     * Map of Gradle Project to its child projects
     */
    const gradleProjectToChildProjects = new Map();
    let index = 0;
    while (index < projectReportLines.length) {
        const line = projectReportLines[index].trim();
        if (line.startsWith('> Task ')) {
            if (line.endsWith(':dependencyReport')) {
                const gradleProject = line.substring('> Task '.length, line.length - ':dependencyReport'.length);
                while (index < projectReportLines.length &&
                    !projectReportLines[index].includes(exec_gradle_1.fileSeparator)) {
                    index++;
                }
                const [_, file] = projectReportLines[index].split(exec_gradle_1.fileSeparator);
                gradleProjectToDepsMap.set(gradleProject, processGradleDependencies(file));
            }
            if (line.endsWith('propertyReport')) {
                const gradleProject = line.substring('> Task '.length, line.length - ':propertyReport'.length);
                while (index < projectReportLines.length &&
                    !projectReportLines[index].includes(exec_gradle_1.fileSeparator)) {
                    index++;
                }
                const [_, file] = projectReportLines[index].split(exec_gradle_1.fileSeparator);
                const propertyReportLines = (0, node_fs_1.existsSync)(file)
                    ? (0, node_fs_1.readFileSync)(file).toString().split(exec_gradle_1.newLineSeparator)
                    : [];
                let projectName, absBuildFilePath, absBuildDirPath;
                const outputDirMap = new Map();
                const tasks = new Set();
                for (const line of propertyReportLines) {
                    if (line.startsWith('name: ')) {
                        projectName = line.substring('name: '.length);
                    }
                    if (line.startsWith('buildFile: ')) {
                        absBuildFilePath = line.substring('buildFile: '.length);
                    }
                    if (line.startsWith('buildDir: ')) {
                        absBuildDirPath = line.substring('buildDir: '.length);
                    }
                    if (line.startsWith('childProjects: ')) {
                        const childProjects = line.substring('childProjects: {'.length, line.length - 1); // remove curly braces {} around childProjects
                        gradleProjectToChildProjects.set(gradleProject, childProjects
                            .split(',')
                            .map((c) => c.trim().split('=')[0])
                            .filter(Boolean) // e.g. get project name from text like "app=project ':app', mylibrary=project ':mylibrary'"
                        );
                    }
                    if (line.includes('Dir: ')) {
                        const [dirName, dirPath] = line.split(': ');
                        const taskName = dirName.replace('Dir', '');
                        outputDirMap.set(taskName, `{workspaceRoot}/${(0, node_path_1.relative)(devkit_1.workspaceRoot, dirPath)}`);
                    }
                    if (line.includes(': task ')) {
                        const [task] = line.split(': task ');
                        tasks.add(task);
                    }
                }
                if (!projectName || !absBuildFilePath || !absBuildDirPath) {
                    continue;
                }
                const buildFile = (0, devkit_1.normalizePath)((0, node_path_1.relative)(devkit_1.workspaceRoot, absBuildFilePath));
                const buildDir = (0, node_path_1.relative)(devkit_1.workspaceRoot, absBuildDirPath);
                outputDirMap.set('build', `{workspaceRoot}/${buildDir}`);
                outputDirMap.set('classes', `{workspaceRoot}/${(0, node_path_1.join)(buildDir, 'classes')}`);
                gradleFileToOutputDirsMap.set(buildFile, outputDirMap);
                gradleFileToGradleProjectMap.set(buildFile, gradleProject);
                gradleProjectToProjectName.set(gradleProject, projectName);
                gradleProjectNameToProjectRootMap.set(gradleProject, (0, path_1.dirname)(buildFile));
                gradleProjectToTasksMap.set(gradleProject, tasks);
            }
            if (line.endsWith('taskReport')) {
                const gradleProject = line.substring('> Task '.length, line.length - ':taskReport'.length);
                while (index < projectReportLines.length &&
                    !projectReportLines[index].includes(exec_gradle_1.fileSeparator)) {
                    index++;
                }
                const [_, file] = projectReportLines[index].split(exec_gradle_1.fileSeparator);
                const taskTypeMap = new Map();
                const tasksFileLines = (0, node_fs_1.existsSync)(file)
                    ? (0, node_fs_1.readFileSync)(file).toString().split(exec_gradle_1.newLineSeparator)
                    : [];
                let i = 0;
                while (i < tasksFileLines.length) {
                    const line = tasksFileLines[i];
                    if (line.endsWith('tasks')) {
                        const dashes = new Array(line.length + 1).join('-');
                        if (tasksFileLines[i + 1] === dashes) {
                            const type = line.substring(0, line.length - ' tasks'.length);
                            i++;
                            while (tasksFileLines[++i] !== '' &&
                                i < tasksFileLines.length &&
                                tasksFileLines[i]?.includes(' - ')) {
                                const [taskName] = tasksFileLines[i].split(' - ');
                                taskTypeMap.set(taskName, type);
                            }
                        }
                    }
                    i++;
                }
                gradleProjectToTasksTypeMap.set(gradleProject, taskTypeMap);
            }
        }
        index++;
    }
    return {
        gradleFileToGradleProjectMap,
        gradleFileToOutputDirsMap,
        gradleProjectToTasksTypeMap,
        gradleProjectToDepsMap,
        gradleProjectToTasksMap,
        gradleProjectToProjectName,
        gradleProjectNameToProjectRootMap,
        gradleProjectToChildProjects,
    };
}
function processGradleDependencies(depsFile) {
    const dependedProjects = new Set();
    const lines = (0, node_fs_1.readFileSync)(depsFile).toString().split(exec_gradle_1.newLineSeparator);
    let inDeps = false;
    for (const line of lines) {
        if (line.startsWith('implementationDependenciesMetadata') ||
            line.startsWith('compileClasspath')) {
            inDeps = true;
            continue;
        }
        if (inDeps) {
            if (line === '') {
                inDeps = false;
                continue;
            }
            const [indents, dep] = line.split('--- ');
            if (indents === '\\' || indents === '+') {
                let targetProjectName;
                if (dep.startsWith('project ')) {
                    targetProjectName = dep
                        .substring('project '.length)
                        .replace(/ \(n\)$/, '')
                        .trim()
                        .split(' ')?.[0];
                }
                else if (dep.includes('-> project')) {
                    const [_, projectName] = dep.split('-> project');
                    targetProjectName = projectName.trim().split(' ')?.[0];
                }
                if (targetProjectName) {
                    dependedProjects.add(targetProjectName);
                }
            }
        }
    }
    return dependedProjects;
}
