/**
 * Fixes invalid paths in the compile command's response files
 */

import { existsSync } from 'fs';
import * as path from "path";

import * as consts from '../../consts';
import * as shared from '../../shared';
import type { ProjectUE4 } from '../../project/projectUE4';

import * as console from "../../console";


export async function fixResponse(project: ProjectUE4) {
    console.log("Fixing invalid paths in response files.");

    const mainCompileCommands = project.getMainWorkspaceCompileCommands();

    if (!mainCompileCommands) {
        console.log("No compile commands found!");
        return;
    }

    for (const [index, compileCommand] of mainCompileCommands) {
        
        const responsePaths: string[] | undefined = compileCommand.getAllUsedResponsePaths();

        if (!responsePaths?.length) {
            console.error("Couldn't find any response file paths.");
            return;
        }

        for (const filePath of responsePaths) {

            const originalResponseString = await shared.readStringFromFile(filePath);
            if (!originalResponseString || originalResponseString.startsWith('undefined')) {
                console.error("Couldn't read response file");

                if (originalResponseString?.startsWith('undefined')) {
                    console.error("A project reset should fix this.");
                }
                continue;
            }

            const fixedFileString = fixKnownInvalidPathsInFile(filePath, originalResponseString);

            if (!fixedFileString) {
                console.log("No invalid path fixes found.");
                continue;
            }

            try {
                await shared.writeJsonOrStringToFile(filePath, fixedFileString);  // This isn't json but should work since 
            } catch (error) {

                if (error instanceof Error) {
                    console.error(`Problem writing fixed response file: ${error.message}.`);
                }
                
                continue;
            }
        }
    }

}


/**
 * @param value 
 * @returns undefined if no fixes were made
 * 
 * @logs all
 */
export function fixKnownInvalidPathsInFile(responsePath: string, originalResponseString: string): string | undefined {

    const preincludePaths = originalResponseString.match(consts.RE_PREINCLUDE_SHAREDPCH_PATH);

    if (preincludePaths?.length) {
        warnInvalidPreIncludePaths(preincludePaths);
    }

    const matches = originalResponseString.match(consts.RE_COMPILE_COMMAND_INCLUDE_PATHS);

    if (!matches) {
        console.log("No includes found in response file.");
        return;
    }

    const invalidPaths = getInvalidWithValidPaths(matches);

    if (!invalidPaths.fixable.length) {
        console.log("No invalid paths returned. No fixes needed.");
        return originalResponseString;  // need to return orginal for additional fixes
    }

    let replacementString = originalResponseString;
    for (const invalidPath of invalidPaths.fixable) {  // now we fix and replace

        replacementString = replacementString.replace(invalidPath.invalid, invalidPath.valid);
        continue;
    }

    const parsedPath = path.parse(responsePath);
    console.log(`${parsedPath.name}: Fixed paths count(${invalidPaths.fixable.length}), Unfixed paths count(${invalidPaths.unfixable})`);

    return replacementString;
}


/**
 * 
 * @param outPaths 
 * @returns array of invalid/valid paths
 */
function getInvalidWithValidPaths(outPaths: string[]): { unfixable: number, fixable: { invalid: string, valid: string }[] } {

    const reBadIncPath = new RegExp(consts.RE_COMPILE_COMMAND_INC_BAD_PATH);
    const reBadReliabilityPath = new RegExp(consts.RE_COMPILE_COMMAND_RELIABILITY_BAD_PATH);

    const paths = outPaths;

    const invalidStringsObject: { unfixable: number, fixable: { invalid: string, valid: string }[] } = { unfixable: 0, fixable: [] };

    for (const key in paths) {
        if (existsSync(paths[key])) {
            continue;  // Path exist so continue.
        }

        const currentPath = paths[key];

        // Bad Inc path fix
        if (checkAndReplacePathSubstring(paths, key, { reMatch: reBadIncPath, replace: consts.REPLACEMENT_NAME_INC_TO_DEVELOPEMENT })) {
            invalidStringsObject.fixable.push({ invalid: currentPath, valid: paths[key] });
            continue;
        }

        // Bad ReliabilityHandleComponent path fix
        if (checkAndReplacePathSubstring(paths, key,
            { reMatch: reBadIncPath, replace: consts.REPLACEMENT_NAME_INC_TO_DEVELOPEMENT },
            { reMatch: reBadReliabilityPath, replace: consts.REPLACEMENT_NAME_RELIABILITY_TO_RELIABLE })) {
            invalidStringsObject.fixable.push({ invalid: currentPath, valid: paths[key] });
            continue;
        }

        invalidStringsObject.unfixable++;
        console.error(`Couldn't fix ${outPaths[key]} from compile commands.`);
        console.error("You may have to Build the version specified in the path before the path is fixed (e.g. The path contains Development and/or Win64)\n");

    }

    return invalidStringsObject;
}


/**
 *  * 
 * @param outPaths 
 * @param key of outPaths that we're trying to replace, using 'any'. shame...
 * @param fromTos reMatch: Regex for matching and replacement of the match, replace: string that replaces the match
 * 
 * @returns true if outPaths modified
 */
function checkAndReplacePathSubstring(outPaths: string[], key: any, ...fromTos: { reMatch: RegExp, replace: string }[]): boolean {

    const paths = outPaths;
    let currentPath: string = paths[key];

    for (const fromTo of fromTos) {
        currentPath = currentPath.replace(fromTo.reMatch, fromTo.replace);
    }

    if (!existsSync(currentPath)) {
        return false;
    }
    else {
        paths[key] = currentPath;
        return true;
    }

}

function warnInvalidPreIncludePaths(paths: RegExpMatchArray) {

    for (const path of paths) {

        if (!existsSync(path)) {
            console.log("WARNING: Intellisense preinclude path doesn't exist. Building the project should fix this.");
            console.log("WARNING: If this message persists, then do a project reset.");
        }
    }
}