
import { getAssembliesDir } from "@fable-org/fable-metadata";
import { getVersion as getVersion_1, getFableLibDir as getFableLibDir_1 } from "../src/util.js";
import { coreAssemblies } from "./Fable.Transforms/Global/Metadata.js";
import { join, substring, concat, printf, interpolate, toConsole, replace, trimEnd } from "./fable_modules/fable-library-js.5.0.0-rc.1/String.js";
import { stringHash, uncurry2, defaultOf, comparePrimitives, Exception, Lazy, int32ToString } from "./fable_modules/fable-library-js.5.0.0-rc.1/Util.js";
import { runCmdAndExitIfFails, CmdLineOptions, Path_GetFileNameWithoutExtension, serializeToJson, ensureDirExists, writeAllText, readAllBytes as readAllBytes_1, measureTime, getHomePath, readAllText, Path_ChangeExtension, normalizeFullPath, Path_GetFileName, getRelativePath, Path_Combine, Path_GetDirectoryName, normalizePath } from "./Platform.js";
import { ofSeq as ofSeq_1, FSharpSet__Contains } from "./fable_modules/fable-library-js.5.0.0-rc.1/Set.js";
import { singleton } from "./fable_modules/fable-library-js.5.0.0-rc.1/AsyncBuilder.js";
import { StringBuilder_$ctor, StringBuilder__Append_Z721C83C5 } from "./fable_modules/fable-library-js.5.0.0-rc.1/System.Text.js";
import { MappingIndex } from "./fable_modules/source-map-sharp.1.0.9/src/Util.fs.js";
import { SourceMapGenerator__toJSON, SourceMapGenerator_$ctor_Z257FAB7, SourceMapGenerator__AddMapping_Z1908099D } from "./fable_modules/source-map-sharp.1.0.9/src/SourceMapGenerator.fs.js";
import { toArray as toArray_1, orElse, map as map_1, defaultArg, unwrap } from "./fable_modules/fable-library-js.5.0.0-rc.1/Option.js";
import { class_type } from "./fable_modules/fable-library-js.5.0.0-rc.1/Reflection.js";
import { FSharpRef, toString } from "./fable_modules/fable-library-js.5.0.0-rc.1/Types.js";
import { item, equalsWith, splitAt, tryFindIndex, contains as contains_1, pairwise, tryFindBack, append, reverse, map, partition } from "./fable_modules/fable-library-js.5.0.0-rc.1/Array.js";
import { startImmediate } from "./fable_modules/fable-library-js.5.0.0-rc.1/Async.js";
import { parseProject } from "./ProjectParser.js";
import { tryFind, toArray, ofSeq } from "./fable_modules/fable-library-js.5.0.0-rc.1/Map.js";
import { addToDict, tryGetValue } from "./fable_modules/fable-library-js.5.0.0-rc.1/MapUtil.js";
import { ofArray, singleton as singleton_1, contains } from "./fable_modules/fable-library-js.5.0.0-rc.1/List.js";
import { tryParse } from "./fable_modules/fable-library-js.5.0.0-rc.1/Boolean.js";
import { iterate } from "./fable_modules/fable-library-js.5.0.0-rc.1/Seq.js";
import "@fable-org/fable-standalone";

export const getMetadataDir = getAssembliesDir;

export const getFableLibDir = getFableLibDir_1;

export const getVersion = getVersion_1;


export function initFable() {
    return __FABLE_STANDALONE__.init();
}

export const references = coreAssemblies;

export const metadataPath = trimEnd(getMetadataDir(), "\\", "/") + "/";

export function Imports_trimPath(path) {
    return replace(replace(replace(path, "../", ""), "./", ""), ":", "");
}

export function Imports_isRelativePath(path) {
    if (path.startsWith("./")) {
        return true;
    }
    else {
        return path.startsWith("../");
    }
}

export function Imports_isAbsolutePath(path) {
    if (path.startsWith("/")) {
        return true;
    }
    else {
        return path.indexOf(":") === 1;
    }
}

export function Imports_preventConflicts(conflicts, originalName) {
    const check = (originalName_1_mut, n_mut) => {
        check:
        while (true) {
            const originalName_1 = originalName_1_mut, n = n_mut;
            const name = (n > 0) ? ((originalName_1 + "_") + int32ToString(n)) : originalName_1;
            if (!conflicts(name)) {
                return name;
            }
            else {
                originalName_1_mut = originalName_1;
                n_mut = (n + 1);
                continue check;
            }
            break;
        }
    };
    return check(originalName, 0);
}

export function Imports_getTargetAbsolutePath(getOrAddDeduplicateTargetDir, importPath, projDir, outDir) {
    const importPath_1 = normalizePath(importPath);
    const outDir_1 = normalizePath(outDir);
    if (importPath_1.startsWith(outDir_1 + "/")) {
        return importPath_1;
    }
    else {
        const importDir = Path_GetDirectoryName(importPath_1);
        return Path_Combine(getOrAddDeduplicateTargetDir(importDir, (currentTargetDirs) => Imports_preventConflicts((value) => FSharpSet__Contains(currentTargetDirs, value), Path_Combine(outDir_1, Imports_trimPath(getRelativePath(projDir, importDir))))), Path_GetFileName(importPath_1));
    }
}

export function Imports_getTargetRelativePath(getOrAddDeduplicateTargetDir, importPath, targetDir, projDir, outDir) {
    const relPath = getRelativePath(targetDir, Imports_getTargetAbsolutePath(getOrAddDeduplicateTargetDir, importPath, projDir, outDir));
    if (Imports_isRelativePath(relPath)) {
        return relPath;
    }
    else {
        return "./" + relPath;
    }
}

export function Imports_getImportPath(getOrAddDeduplicateTargetDir, sourcePath, targetPath, projDir, outDir, importPath) {
    if (outDir != null) {
        const outDir_1 = outDir;
        const importPath_1 = importPath.startsWith("${outDir}") ? normalizeFullPath(Path_Combine(outDir_1, replace(importPath, "${outDir}", ""))) : importPath;
        const sourceDir = Path_GetDirectoryName(sourcePath);
        const targetDir = Path_GetDirectoryName(targetPath);
        const importPath_2 = Imports_isRelativePath(importPath_1) ? normalizeFullPath(Path_Combine(sourceDir, importPath_1)) : importPath_1;
        if (Imports_isAbsolutePath(importPath_2)) {
            if (importPath_2.endsWith(".fs")) {
                return Imports_getTargetRelativePath(getOrAddDeduplicateTargetDir, importPath_2, targetDir, projDir, outDir_1);
            }
            else {
                return getRelativePath(targetDir, importPath_2);
            }
        }
        else {
            return importPath_2;
        }
    }
    else {
        return replace(importPath, "${outDir}", ".");
    }
}

export class SourceWriter {
    constructor(sourcePath, targetPath, projDir, options, fileExt, dedupTargetDir) {
        this.sourcePath = sourcePath;
        this.targetPath = targetPath;
        this.projDir = projDir;
        this.options = options;
        this.fileExt = fileExt;
        this.dedupTargetDir = dedupTargetDir;
        this.sb = StringBuilder_$ctor();
        this.mapGenerator = (new Lazy(SourceMapGenerator_$ctor_Z257FAB7));
    }
    Write(str) {
        const _ = this;
        return singleton.Delay(() => singleton.Return(void StringBuilder__Append_Z721C83C5(_.sb, str)));
    }
    MakeImportPath(path) {
        const _ = this;
        const path_1 = Imports_getImportPath(_.dedupTargetDir, _.sourcePath, _.targetPath, _.projDir, _.options.outDir, path);
        return path_1.endsWith(".fs") ? Path_ChangeExtension(path_1, _.fileExt) : path_1;
    }
    AddSourceMapping(_arg) {
        const _ = this;
        const srcLine = _arg[0] | 0;
        const srcCol = _arg[1] | 0;
        if (_.options.sourceMaps) {
            const generated = new MappingIndex(_arg[2], _arg[3]);
            const original = new MappingIndex(srcLine, srcCol);
            if ((srcLine !== 0) && (srcCol !== 0)) {
                SourceMapGenerator__AddMapping_Z1908099D(_.mapGenerator.Value, generated, original, _.sourcePath, unwrap(_arg[4]));
            }
        }
    }
    Dispose() {
    }
}

export function SourceWriter_$reflection() {
    return class_type("Fable.Compiler.App.SourceWriter", undefined, SourceWriter);
}

export function SourceWriter_$ctor_71349DF3(sourcePath, targetPath, projDir, options, fileExt, dedupTargetDir) {
    return new SourceWriter(sourcePath, targetPath, projDir, options, fileExt, dedupTargetDir);
}

export function SourceWriter__get_SourceMap(_) {
    return SourceMapGenerator__toJSON(_.mapGenerator.Value);
}

export function SourceWriter__get_Result(_) {
    return toString(_.sb);
}

export function printErrors(showWarnings, errors) {
    const printError = (e) => {
        toConsole(interpolate("%s%P() (%d%P(),%d%P()): %s%P(): %s%P()", [e.FileName, e.StartLine, e.StartColumn, e.IsWarning ? "Warning" : "Error", e.Message]));
    };
    const patternInput = partition((e_1) => e_1.IsWarning, errors);
    const errors_1 = patternInput[1];
    const hasErrors = !(errors_1.length === 0);
    if (showWarnings) {
        patternInput[0].forEach(printError);
    }
    if (hasErrors) {
        errors_1.forEach(printError);
        throw new Exception("Too many errors.");
    }
}

export function runAsync(computation) {
    startImmediate(singleton.Delay(() => singleton.TryWith(singleton.Delay(() => singleton.Bind(computation, () => singleton.Return(undefined))), (_arg_1) => {
        const ex = _arg_1;
        toConsole(`ERROR: ${ex.message}`);
        toConsole(`${ex.stack}`);
        return singleton.Zero();
    })));
}

export function parseFiles(projectFileName, options) {
    const patternInput = parseProject(projectFileName);
    const fileNames = patternInput[1];
    const sources = map(readAllText, fileNames);
    const nugetPath = Path_Combine(replace(getHomePath(), "\\", "/"), ".nuget");
    const fileNames_1 = map((x) => replace(x, nugetPath, ""), fileNames);
    const dllRefMap = ofSeq(map((x_1) => [Path_GetFileName(x_1), x_1], reverse(patternInput[0])), {
        Compare: (x_2, y) => (comparePrimitives(x_2, y) | 0),
    });
    const references_1 = append(references, map((tuple) => tuple[0], toArray(dllRefMap)));
    const fable = initFable();
    const otherOptions_1 = append([options.optimize ? "--optimize+" : "--optimize-"], patternInput[2]);
    const patternInput_1 = measureTime(() => fable.CreateChecker(references_1, (dllName_1) => {
        let dllName, value;
        return readAllBytes_1((dllName = dllName_1, (value = (metadataPath + dllName), defaultArg(tryFind(dllName, dllRefMap), value))));
    }, otherOptions_1), undefined);
    const checker = patternInput_1[0];
    toConsole(`fable-compiler-js v${getVersion()}`);
    toConsole(printf("--------------------------------------------"));
    toConsole(interpolate("InteractiveChecker created in %d%P() ms", [patternInput_1[1]]));
    const patternInput_2 = measureTime(() => fable.ParseAndCheckProject(checker, projectFileName, fileNames_1, sources), undefined);
    const parseRes = patternInput_2[0];
    toConsole(interpolate("Project: %s%P(), FCS time: %d%P() ms", [projectFileName, patternInput_2[1]]));
    toConsole(printf("--------------------------------------------"));
    const showWarnings = !options.benchmark;
    printErrors(showWarnings, parseRes.Errors);
    if (options.benchmark) {
    }
    else {
        fable.ClearCache(checker);
        const fileNames_2 = fileNames_1.filter((x_3) => !x_3.endsWith(".fsi"));
        const projDir = Path_GetDirectoryName(normalizeFullPath(projectFileName));
        const libDir = normalizeFullPath(defaultArg(options.libDir, getFableLibDir()));
        let fileExt;
        const matchValue = options.language.toLowerCase();
        switch (matchValue) {
            case "js":
            case "javascript": {
                fileExt = ".js";
                break;
            }
            case "ts":
            case "typescript": {
                fileExt = ".ts";
                break;
            }
            case "py":
            case "python": {
                fileExt = ".py";
                break;
            }
            case "php": {
                fileExt = ".php";
                break;
            }
            case "dart": {
                fileExt = ".dart";
                break;
            }
            case "rs":
            case "rust": {
                fileExt = ".rs";
                break;
            }
            default:
                throw new Exception(concat("Unsupported language: ", ...options.language));
        }
        const fileExt_1 = (options.outDir == null) ? (".fs" + fileExt) : fileExt;
        let getOrAddDeduplicateTargetDir;
        const dedupDic = new Map([]);
        getOrAddDeduplicateTargetDir = ((importDir) => ((addTargetDir) => {
            const importDir_1 = normalizeFullPath(importDir).toLocaleLowerCase();
            let matchValue_1;
            let outArg = defaultOf();
            matchValue_1 = [tryGetValue(dedupDic, importDir_1, new FSharpRef(() => outArg, (v) => {
                outArg = v;
            })), outArg];
            if (matchValue_1[0]) {
                return matchValue_1[1];
            }
            else {
                const v_2 = addTargetDir(ofSeq_1(dedupDic.values(), {
                    Compare: (x_4, y_1) => (comparePrimitives(x_4, y_1) | 0),
                }));
                addToDict(dedupDic, importDir_1, v_2);
                return v_2;
            }
        }));
        runAsync(singleton.Delay(() => singleton.For(fileNames_2, (_arg) => {
            let fsAstStr;
            const fileName_1 = _arg;
            const patternInput_3 = measureTime((tupledArg) => fable.CompileToTargetAst(libDir, tupledArg[0], tupledArg[1], options.typedArrays, options.language), [parseRes, fileName_1]);
            const res_1 = patternInput_3[0];
            toConsole(interpolate("File: %s%P(), Fable time: %d%P() ms", [fileName_1, patternInput_3[1]]));
            printErrors(showWarnings, res_1.FableErrors);
            let outPath;
            const matchValue_2 = options.outDir;
            outPath = ((matchValue_2 != null) ? Path_ChangeExtension(Imports_getTargetAbsolutePath(uncurry2(getOrAddDeduplicateTargetDir), fileName_1, projDir, matchValue_2), fileExt_1) : Path_ChangeExtension(fileName_1, fileExt_1));
            return singleton.Combine(options.printAst ? ((fsAstStr = fable.FSharpAstToString(parseRes, fileName_1), (writeAllText(substring(outPath, 0, outPath.lastIndexOf(fileExt_1)) + ".fs.ast", fsAstStr), singleton.Zero()))) : singleton.Zero(), singleton.Delay(() => {
                const writer = SourceWriter_$ctor_71349DF3(fileName_1, outPath, projDir, options, fileExt_1, uncurry2(getOrAddDeduplicateTargetDir));
                return singleton.Bind(fable.PrintTargetAst(res_1, writer), () => {
                    let mapPath, sourceMapUrl;
                    ensureDirExists(Path_GetDirectoryName(outPath));
                    return singleton.Combine(options.sourceMaps ? ((mapPath = (outPath + ".map"), (sourceMapUrl = ("//# sourceMappingURL=" + Path_GetFileName(mapPath)), singleton.Bind(writer.Write(sourceMapUrl), () => {
                        writeAllText(mapPath, serializeToJson(SourceWriter__get_SourceMap(writer)));
                        return singleton.Zero();
                    })))) : singleton.Zero(), singleton.Delay(() => {
                        writeAllText(outPath, SourceWriter__get_Result(writer));
                        return singleton.Zero();
                    }));
                });
            }));
        })));
    }
}

export function argValue(keys, args) {
    return map_1((tuple) => tuple[1], tryFindBack((tupledArg) => {
        if (!tupledArg[1].startsWith("-")) {
            return contains(tupledArg[0], keys, {
                Equals: (x, y) => (x === y),
                GetHashCode: (x) => (stringHash(x) | 0),
            });
        }
        else {
            return false;
        }
    }, pairwise(args)));
}

export function tryFlag(flag, args) {
    const matchValue = argValue(singleton_1(flag), args);
    if (matchValue == null) {
        if (contains_1(flag, args, {
            Equals: (x, y) => (x === y),
            GetHashCode: (x) => (stringHash(x) | 0),
        })) {
            return true;
        }
        else {
            return undefined;
        }
    }
    else {
        let matchValue_1;
        let outArg = false;
        matchValue_1 = [tryParse(matchValue, new FSharpRef(() => outArg, (v) => {
            outArg = v;
        })), outArg];
        if (matchValue_1[0]) {
            return matchValue_1[1];
        }
        else {
            return undefined;
        }
    }
}

export function hasFlag(flag, args) {
    return defaultArg(tryFlag(flag, args), false);
}

export function run(opts, projectFileName, outDir) {
    const commandToRun = map_1((i) => concat("node ", Path_Combine(defaultArg(outDir, "."), Path_GetFileNameWithoutExtension(projectFileName) + ".js"), " ", ...join(" ", opts.slice(i + 1, opts.length))), tryFindIndex((y) => ("--run" === y), opts));
    parseFiles(projectFileName, new CmdLineOptions(orElse(argValue(ofArray(["--outDir", "-o"]), opts), outDir), argValue(singleton_1("--fableLib"), opts), hasFlag("--benchmark", opts), hasFlag("--optimize", opts), hasFlag("--sourceMaps", opts) ? true : hasFlag("-s", opts), tryFlag("--typedArrays", opts), defaultArg(argValue(ofArray(["--language", "--lang"]), opts), "JavaScript"), hasFlag("--printAst", opts)));
    iterate((cmd) => {
        runCmdAndExitIfFails(cmd);
    }, toArray_1(commandToRun));
}

export function parseArguments(argv) {
    let testExpr, testExpr_1;
    const usage = "Usage: fable <F#_PROJECT_PATH> [OUT_DIR] [--options]\n\nOptions:\n  --help            Show help\n  --version         Print version\n  -o|--outDir       Redirect compilation output to a directory\n  -s|--sourceMaps   Enable source maps\n  --fableLib        Specify Fable library path\n  --typedArrays     Compile numeric arrays as JS typed arrays (default is true for JS, false for TS)\n  --run             Execute the script after compilation\n\n  --optimize        Compile with optimized F# AST (experimental)\n";
    let patternInput;
    const matchValue = tryFindIndex((s) => s.startsWith("-"), argv);
    patternInput = ((matchValue != null) ? splitAt(matchValue, argv) : [argv, []]);
    const opts = patternInput[1];
    const args = patternInput[0];
    if (hasFlag("--help", argv)) {
        toConsole(`${usage}`);
    }
    else if (hasFlag("--version", argv)) {
        toConsole(`v${getVersion()}`);
    }
    else if ((testExpr = args, !equalsWith((x, y) => (x === y), testExpr, defaultOf()) && (testExpr.length === 1))) {
        run(opts, item(0, args), undefined);
    }
    else if ((testExpr_1 = args, !equalsWith((x_1, y_1) => (x_1 === y_1), testExpr_1, defaultOf()) && (testExpr_1.length === 2))) {
        run(opts, item(0, args), item(1, args));
    }
    else {
        toConsole(`${usage}`);
    }
}

(function (argv) {
    try {
        parseArguments(argv);
    }
    catch (ex) {
        toConsole(`ERROR: ${ex.message}`);
        toConsole(`${ex.stack}`);
    }
    return 0;
})(typeof process === 'object' ? process.argv.slice(2) : []);

