
import { Union } from "./fable_modules/fable-library-js.5.0.0-rc.1/Types.js";
import { union_type, string_type } from "./fable_modules/fable-library-js.5.0.0-rc.1/Reflection.js";
import { matches, replace, create, match } from "./fable_modules/fable-library-js.5.0.0-rc.1/RegExp.js";
import { empty, singleton, collect, append, toArray, tryHead, map, delay, toList } from "./fable_modules/fable-library-js.5.0.0-rc.1/Seq.js";
import { map as map_1, defaultArg } from "./fable_modules/fable-library-js.5.0.0-rc.1/Option.js";
import { split, printf, toConsole, concat, replace as replace_1, trimStart } from "./fable_modules/fable-library-js.5.0.0-rc.1/String.js";
import { getGlobFiles, Path_GetFileName, Path_GetDirectoryName, normalizeFullPath, getDirFiles, Path_Combine, getHomePath, readAllText } from "./Platform.js";
import { collect as collect_1, map as map_2, append as append_1, fold, tryLast } from "./fable_modules/fable-library-js.5.0.0-rc.1/Array.js";
import { Array_distinct, distinct, except } from "./fable_modules/fable-library-js.5.0.0-rc.1/Seq2.js";
import { curry2, stringHash } from "./fable_modules/fable-library-js.5.0.0-rc.1/Util.js";
import { head, tail, isEmpty } from "./fable_modules/fable-library-js.5.0.0-rc.1/List.js";
import { HashSet } from "./fable_modules/fable-library-js.5.0.0-rc.1/MutableSet.js";
import { unionWith } from "./fable_modules/fable-library-js.5.0.0-rc.1/Set.js";

export class ReferenceType extends Union {
    constructor(tag, fields) {
        super();
        this.tag = tag;
        this.fields = fields;
    }
    cases() {
        return ["ProjectReference", "PackageReference"];
    }
}

export function ReferenceType_$reflection() {
    return union_type("Fable.Compiler.ProjectParser.ReferenceType", [], ReferenceType, () => [[["Item", string_type]], [["Item1", string_type], ["Item2", string_type]]]);
}

export function $007CRegex$007C_$007C(pattern, input) {
    const m = match(create(pattern), input);
    if (m != null) {
        return toList(delay(() => map((x) => (x || ""), m)));
    }
    else {
        return undefined;
    }
}

export function getXmlWithoutComments(xml) {
    return replace(xml, "<!--[\\s\\S]*?-->", "");
}

export function getXmlTagContents(tag, xml) {
    return map((m) => (m[1] || "").trim(), matches(create(`<${tag}[^>]*>([^<]*)<\\/${tag}[^>]*>`), xml));
}

export function getXmlTagContentsFirstOrDefault(tag, defaultValue, xml) {
    return defaultArg(tryHead(getXmlTagContents(tag, xml)), defaultValue);
}

export function getXmlTagAttributes1(tag, attr1, xml) {
    return map((m) => trimStart(trimStart(m[1] || "", "\""), "\'").trim(), matches(create(`<${tag}\\s+[^>]*${attr1}\\s*=\\s*("[^"]*|'[^']*)`), xml));
}

export function getXmlTagAttributes2(tag, attr1, attr2, xml) {
    return map((m) => [trimStart(trimStart(m[1] || "", "\""), "\'").trim(), trimStart(trimStart(m[2] || "", "\""), "\'").trim()], matches(create(`<${tag}\\s+[^>]*${attr1}\\s*=\\s*("[^"]*|'[^']*)[^>]*${attr2}\\s*=\\s*("[^"]*|'[^']*)`), xml));
}

export function isSystemPackage(pkgName) {
    if ((((pkgName.startsWith("System.") ? true : pkgName.startsWith("Microsoft.")) ? true : pkgName.startsWith("runtime.")) ? true : (pkgName === "NETStandard.Library")) ? true : (pkgName === "FSharp.Core")) {
        return true;
    }
    else {
        return pkgName === "Fable.Core";
    }
}

export function parsePackageSpec(nuspecPath) {
    return toArray(map((tupledArg) => (new ReferenceType(1, [tupledArg[0], tupledArg[1]])), getXmlTagAttributes2("dependency", "id", "version", getXmlWithoutComments(readAllText(nuspecPath)))));
}

export function resolvePackage(pkgName, pkgVersion) {
    let projRef, dependencies, dllRef;
    if (!isSystemPackage(pkgName)) {
        const homePath = replace_1(getHomePath(), "\\", "/");
        const nugetPath = concat(".nuget/packages/", pkgName, "/", ...pkgVersion);
        const pkgPath = Path_Combine(homePath, nugetPath.toLowerCase());
        const libPath = Path_Combine(pkgPath, "lib");
        const fablePath = Path_Combine(pkgPath, "fable");
        const binaryPaths = getDirFiles(libPath, ".dll");
        const nuspecPaths = getDirFiles(pkgPath, ".nuspec");
        const fsprojPaths = getDirFiles(fablePath, ".fsproj");
        if (nuspecPaths.length === 0) {
            toConsole(printf("ERROR: Cannot find package %s"))(pkgPath);
        }
        const binaryOpt = tryLast(binaryPaths);
        const dependOpt = map_1(parsePackageSpec, tryLast(nuspecPaths));
        const fsprojOpt = map_1((Item) => (new ReferenceType(0, [Item])), tryLast(fsprojPaths));
        const patternInput = (fsprojOpt != null) ? ((projRef = fsprojOpt, [[projRef], []])) : ((binaryOpt != null) ? ((dependOpt != null) ? ((dependencies = dependOpt, (dllRef = binaryOpt, [dependencies, [dllRef]]))) : [[], []]) : [[], []]);
        return [patternInput[0], patternInput[1]];
    }
    else {
        return [[], []];
    }
}

export function parseCompilerOptions(projectXml) {
    const target = getXmlTagContentsFirstOrDefault("OutputType", "", projectXml);
    const langVersion = getXmlTagContentsFirstOrDefault("LangVersion", "", projectXml);
    const warnLevel = getXmlTagContentsFirstOrDefault("WarningLevel", "", projectXml);
    const treatWarningsAsErrors = getXmlTagContentsFirstOrDefault("TreatWarningsAsErrors", "", projectXml);
    const defines = toArray(except(["$(DefineConstants)", ""], distinct(map((s_1) => s_1.trim(), append(["FABLE_COMPILER", "FABLE_COMPILER_5", "FABLE_COMPILER_JAVASCRIPT", "NPM_PACKAGE_FABLE_COMPILER_JAVASCRIPT"], collect((s) => split(s, [";"], undefined, 0), getXmlTagContents("DefineConstants", projectXml)))), {
        Equals: (x, y) => (x === y),
        GetHashCode: (x) => (stringHash(x) | 0),
    }), {
        Equals: (x_1, y_1) => (x_1 === y_1),
        GetHashCode: (x_1) => (stringHash(x_1) | 0),
    }));
    const nowarns = toArray(except(["$(NoWarn)", ""], distinct(map((s_3) => s_3.trim(), collect((s_2) => split(s_2, [";"], undefined, 0), getXmlTagContents("NoWarn", projectXml))), {
        Equals: (x_2, y_2) => (x_2 === y_2),
        GetHashCode: (x_2) => (stringHash(x_2) | 0),
    }), {
        Equals: (x_3, y_3) => (x_3 === y_3),
        GetHashCode: (x_3) => (stringHash(x_3) | 0),
    }));
    const warnAsErrors = toArray(except(["$(WarningsAsErrors)", ""], distinct(map((s_5) => s_5.trim(), collect((s_4) => split(s_4, [";"], undefined, 0), getXmlTagContents("WarningsAsErrors", projectXml))), {
        Equals: (x_4, y_4) => (x_4 === y_4),
        GetHashCode: (x_4) => (stringHash(x_4) | 0),
    }), {
        Equals: (x_5, y_5) => (x_5 === y_5),
        GetHashCode: (x_5) => (stringHash(x_5) | 0),
    }));
    const otherFlags = toArray(except(["$(OtherFlags)", ""], distinct(map((s_7) => s_7.trim(), collect((s_6) => split(s_6, [" "], undefined, 0), getXmlTagContents("OtherFlags", projectXml))), {
        Equals: (x_6, y_6) => (x_6 === y_6),
        GetHashCode: (x_6) => (stringHash(x_6) | 0),
    }), {
        Equals: (x_7, y_7) => (x_7 === y_7),
        GetHashCode: (x_7) => (stringHash(x_7) | 0),
    }));
    return toArray(delay(() => append((target.length > 0) ? singleton("--target:" + target) : empty(), delay(() => append((langVersion.length > 0) ? singleton("--langversion:" + langVersion) : empty(), delay(() => append((warnLevel.length > 0) ? singleton("--warn:" + warnLevel) : empty(), delay(() => append((treatWarningsAsErrors === "true") ? singleton("--warnaserror+") : empty(), delay(() => append(map((d) => ("-d:" + d), defines), delay(() => append(map((n) => ("--nowarn:" + n), nowarns), delay(() => append(map((e) => ("--warnaserror:" + e), warnAsErrors), delay(() => map((o) => o, otherFlags)))))))))))))))));
}

export function makeFullPath(projectFileDir, path) {
    let path_2;
    const path_1 = replace_1(path, "\\", "/");
    return normalizeFullPath(((path_2 = path_1, path_2.startsWith("/") ? true : (path_2.indexOf(":") === 1))) ? path_1 : Path_Combine(projectFileDir, path_1));
}

export function parseProjectScript(projectFilePath) {
    const projectXml = readAllText(projectFilePath);
    const projectDir = Path_GetDirectoryName(projectFilePath);
    const patternInput = fold((tupledArg, line) => {
        let path;
        const dllRefs = tupledArg[0];
        const srcFiles = tupledArg[1];
        const matchValue = line.trim();
        let matchResult, path_1;
        const activePatternResult = $007CRegex$007C_$007C("^#r\\s+\"(.*?)\"$", matchValue);
        if (activePatternResult != null) {
            if (!isEmpty(activePatternResult)) {
                if (!isEmpty(tail(activePatternResult))) {
                    if (isEmpty(tail(tail(activePatternResult)))) {
                        if ((path = head(tail(activePatternResult)), !path.endsWith("Fable.Core.dll"))) {
                            matchResult = 0;
                            path_1 = head(tail(activePatternResult));
                        }
                        else {
                            matchResult = 1;
                        }
                    }
                    else {
                        matchResult = 1;
                    }
                }
                else {
                    matchResult = 1;
                }
            }
            else {
                matchResult = 1;
            }
        }
        else {
            matchResult = 1;
        }
        switch (matchResult) {
            case 0:
                return [append_1([Path_Combine(projectDir, path_1)], dllRefs), srcFiles];
            default: {
                let matchResult_1, path_2;
                const activePatternResult_1 = $007CRegex$007C_$007C("^#load\\s+\"(.*?)\"$", matchValue);
                if (activePatternResult_1 != null) {
                    if (!isEmpty(activePatternResult_1)) {
                        if (!isEmpty(tail(activePatternResult_1))) {
                            if (isEmpty(tail(tail(activePatternResult_1)))) {
                                matchResult_1 = 0;
                                path_2 = head(tail(activePatternResult_1));
                            }
                            else {
                                matchResult_1 = 1;
                            }
                        }
                        else {
                            matchResult_1 = 1;
                        }
                    }
                    else {
                        matchResult_1 = 1;
                    }
                }
                else {
                    matchResult_1 = 1;
                }
                switch (matchResult_1) {
                    case 0:
                        return [dllRefs, append_1([Path_Combine(projectDir, path_2)], srcFiles)];
                    default:
                        return [dllRefs, srcFiles];
                }
            }
        }
    }, [[], []], split(projectXml, ["\n"], undefined, 0));
    return [[], patternInput[0], append_1(patternInput[1], [Path_GetFileName(projectFilePath)]), ["--define:FABLE_COMPILER", "--define:FABLE_COMPILER_5", "--define:FABLE_COMPILER_JAVASCRIPT", "--define:NPM_PACKAGE_FABLE_COMPILER_JAVASCRIPT"]];
}

export function parseProjectFile(projectFilePath) {
    const projectXml = getXmlWithoutComments(readAllText(projectFilePath));
    const projectDir = Path_GetDirectoryName(projectFilePath);
    const packageRefs = toArray(map((tupledArg) => (new ReferenceType(1, [tupledArg[0], tupledArg[1]])), getXmlTagAttributes2("PackageReference", "Include", "Version", projectXml)));
    const projectRefs = toArray(map((arg) => (new ReferenceType(0, [makeFullPath(projectDir, arg)])), getXmlTagAttributes1("ProjectReference", "Include", projectXml)));
    const projectXml_1 = replace_1(projectXml, "$(MSBuildProjectDirectory)", ".");
    const projectXml_2 = replace_1(projectXml_1, "$(FSharpSourcesRoot)", replace_1(getXmlTagContentsFirstOrDefault("FSharpSourcesRoot", "", projectXml_1), "\\", "/"));
    const projectXml_3 = replace_1(projectXml_2, "$(FsYaccOutputFolder)", replace_1(getXmlTagContentsFirstOrDefault("FsYaccOutputFolder", "", projectXml_2), "\\", "/"));
    const sourceFiles = toArray(collect(getGlobFiles, map((path_1) => makeFullPath(projectDir, path_1), getXmlTagAttributes1("Compile", "Include", projectXml_3))));
    return [append_1(projectRefs, packageRefs), [], sourceFiles, parseCompilerOptions(projectXml_3)];
}

export function makeHashSetIgnoreCase() {
    return new HashSet([], {
        Equals(x, y) {
            return x.toLowerCase() === y.toLowerCase();
        },
        GetHashCode(x_1) {
            return stringHash(x_1.toLowerCase()) | 0;
        },
    });
}

export function dedupReferences(refSet, references) {
    const refName = (_arg) => {
        if (_arg.tag === 1) {
            return (_arg.fields[0] + ",") + _arg.fields[1];
        }
        else {
            return _arg.fields[0];
        }
    };
    const newRefs = references.filter((arg_1) => {
        let item;
        return !((item = refName(arg_1), refSet.has(item)));
    });
    unionWith(refSet, map_2(refName, newRefs));
    return newRefs;
}

export function parseProject(projectFilePath) {
    const parseProject_1 = (refSet, projectRef) => {
        let patternInput_1;
        if (projectRef.tag === 1) {
            const patternInput = resolvePackage(projectRef.fields[0], projectRef.fields[1]);
            patternInput_1 = [patternInput[0], patternInput[1], [], []];
        }
        else {
            const path = projectRef.fields[0];
            patternInput_1 = (path.endsWith("Fable.Core.fsproj") ? [[], [], [], []] : (path.endsWith(".fsx") ? parseProjectScript(path) : parseProjectFile(path)));
        }
        let parseResult;
        const array = dedupReferences(refSet, patternInput_1[0]);
        parseResult = map_2(curry2(parseProject_1)(refSet), array);
        return [append_1(collect_1((tupledArg) => tupledArg[0], parseResult), patternInput_1[1]), append_1(collect_1((tupledArg_1) => tupledArg_1[1], parseResult), patternInput_1[2]), append_1(collect_1((tupledArg_2) => tupledArg_2[2], parseResult), patternInput_1[3])];
    };
    const patternInput_2 = parseProject_1(makeHashSetIgnoreCase(), new ReferenceType(0, [projectFilePath]));
    return [Array_distinct(patternInput_2[0], {
        Equals: (x_3, y) => (x_3 === y),
        GetHashCode: (x_3) => (stringHash(x_3) | 0),
    }), Array_distinct(patternInput_2[1], {
        Equals: (x_4, y_1) => (x_4 === y_1),
        GetHashCode: (x_4) => (stringHash(x_4) | 0),
    }), Array_distinct(patternInput_2[2], {
        Equals: (x_5, y_2) => (x_5 === y_2),
        GetHashCode: (x_5) => (stringHash(x_5) | 0),
    })];
}

