global function apiVersion(): ApiVersion
    return {
        kind = "compiler_hint",
        major = 0,
        minor = 2,
    }
end

local gnuArchMap: {string:string} = {
    i386 = "i686",
    x86_64 = "x86_64",
    arm64 = "aarch64",
}

local profileNameMap: {string:{string:string}} = {
    release = {
        en_US = "release",
        pt_BR = "lançamento",
        zh_CN = "发布",
        zh_TW = "發佈",
    },
    debug = {
        en_US = "debug",
        pt_BR = "depuração",
        zh_CN = "调试",
        zh_TW = "偵錯",
    },
    debugWithAsan = {
        en_US = "debug with ASan",
        pt_BR = "depuração com ASan",
        zh_CN = "ASan 调试",
        zh_TW = "ASan 偵錯",
    },
}

local function nameGeneratorClang(lang: string, arch: string, profile: string, isMingw: boolean): string
    local template: {string:string} = {
        en_US = "%1 Clang %2, %3",
        pt_BR = "Clang %2 %1, %3",
        zh_CN = "%1 Clang %2,%3",
        zh_TW = "%1 Clang %2,%3",
    }
    local msvcCompatible: {string:string} = {
        en_US = "MSVC-compatible",
        pt_BR = "compatível com MSVC",
        zh_CN = "兼容 MSVC 的",
        zh_TW = "相容 MSVC 的",
    }
    return C_Util.format(
        template[lang] or template.en_US,
        isMingw and "LLVM-MinGW" or msvcCompatible[lang] or msvcCompatible.en_US,
        gnuArchMap[arch],
        profileNameMap[profile][lang] or profileNameMap[profile].en_US
    )
end

local function mergeCompilerSet(compilerSet: CompilerHint.CompilerSet, other: CompilerHint.CompilerSet)
    local c = compilerSet as {string:any}
    local o = other as {string:any}
    for k, v in pairs(o) do
        c[k] = v
    end
end

local record Programs
    cCompiler: string
    cxxCompiler: string
    make: string
    debugger: string
    debugServer: string
    resourceCompiler: string
    binDirs: {string}
    libDirs: {string} | nil
end

local record Config
    arch: string
    isAnsi: boolean | nil
    isClang: boolean | nil
    customCompileParams: {string} | nil
    customLinkParams: {string} | nil
end

local function generateConfig(
    nameGen: (function (arch: string, profile: string): string),
    programs: Programs,
    config: Config
): CompilerHint.CompilerSet, CompilerHint.CompilerSet, CompilerHint.CompilerSet
    local commonOptions : CompilerHint.CompilerSet = {
        cCompiler = programs.cCompiler,
        cxxCompiler = programs.cxxCompiler,
        debugger = programs.debugger,
        debugServer = programs.debugServer,
        make = programs.make,
        resourceCompiler = programs.resourceCompiler,
        binDirs = programs.binDirs,
        compilerType = config.isClang and "Clang" or "GCC_UTF8",
        preprocessingSuffix = ".i",
        compilationProperSuffix = ".s",
        assemblingSuffix = ".o",
        executableSuffix = ".exe",
        compilationStage = 3,
        ccCmdOptUsePipe = "on",
        ccCmdOptWarningAll = "on",
        ccCmdOptWarningExtra = "on",
        ccCmdOptCheckIsoConformance = "on",
    }
    if programs.libDirs then
        commonOptions.libDirs = programs.libDirs
    end
    if config.isAnsi then
        commonOptions.execCharset = "SYSTEM"
    end
    if config.customCompileParams then
        commonOptions.customCompileParams = config.customCompileParams
    end
    if config.customLinkParams then
        commonOptions.customLinkParams = config.customLinkParams
    end
    local release: CompilerHint.CompilerSet = {
        name = nameGen(config.arch, "release"),
        staticLink = true,
        linkCmdOptStripExe = "on",
        ccCmdOptOptimize = "2",
    }
    local debug_: CompilerHint.CompilerSet = {
        name = nameGen(config.arch, "debug"),
        ccCmdOptDebugInfo = "on",
    }
    local debugWithAsan: CompilerHint.CompilerSet = {
        name = nameGen(config.arch, "debugWithAsan"),
        ccCmdOptDebugInfo = "on",
        ccCmdOptAddressSanitizer = "address",
    }
    mergeCompilerSet(release, commonOptions)
    mergeCompilerSet(debug_, commonOptions)
    mergeCompilerSet(debugWithAsan, commonOptions)
    return release, debug_, debugWithAsan
end

global function main(): CompilerHint
    local appArch = C_System.appArch()
    local libexecDir = C_System.appLibexecDir()
    local lang = C_Desktop.language()
    local supportedAppArches = C_System.supportedAppArchList()

    local compilerList = {}
    local noSearch = {}
    local preferCompiler = 0

    local function checkAndAddClang()
        if not C_FileSystem.isExecutable(libexecDir .. "/llvm-mingw/bin/clang.exe") then
            return
        end

        local binDir = libexecDir .. "/llvm-mingw/bin"
        local appTriplet = gnuArchMap[appArch] .. "-w64-mingw32"
        local appDllDir = libexecDir .. "/llvm-mingw/" .. appTriplet .. "/bin"
        do
            local libDir = libexecDir .. "/llvm-mingw/" .. appTriplet .. "/lib"
            -- appArch is always debuggable
            local programs: Programs = {
                cCompiler = binDir .. "/" .. appTriplet .. "-clang.exe",
                cxxCompiler = binDir .. "/" .. appTriplet .. "-clang++.exe",
                make = binDir .. "/mingw32-make.exe",
                debugger = binDir .. "/lldb-mi.exe",
                debugServer = binDir .. "/lldb-server.exe",
                resourceCompiler = binDir .. "/" .. appTriplet .. "-windres.exe",
                binDirs = {binDir, appDllDir},
            }
            local customLinkParams: {string} = nil
            if C_FileSystem.exists(libDir .. "/utf8init.o") then
                customLinkParams = {"-Wl,utf8init.o", "-Wl,utf8manifest.o"}
            end
            local release, debug_, debugWithAsan = generateConfig(
                function (arch_: string, profile: string): string
                    return nameGeneratorClang(lang, arch_, profile, true)
                end,
                programs,
                {
                    arch = appArch,
                    customLinkParams = customLinkParams,
                    isClang = true,
                }
            )
            table.insert(compilerList, release)
            table.insert(compilerList, debug_)
            if appArch ~= "arm64" then
                table.insert(compilerList, debugWithAsan)
                if preferCompiler == 0 then
                    preferCompiler = 3
                end
            else
                if preferCompiler == 0 then
                    preferCompiler = 2
                end
            end
        end

        for _, foreignArch in ipairs(supportedAppArches) do
            local gnuArch = gnuArchMap[foreignArch]
            if foreignArch ~= appArch and gnuArch ~= nil then
                local foreignTriplet = gnuArch .. "-w64-mingw32"
                local foreignDllDir = libexecDir .. "/llvm-mingw/" .. foreignTriplet .. "/bin"
                local libDir = libexecDir .. "/llvm-mingw/" .. foreignTriplet .. "/lib"
                local programs: Programs = {
                    cCompiler = binDir .. "/" .. foreignTriplet .. "-clang.exe",
                    cxxCompiler = binDir .. "/" .. foreignTriplet .. "-clang++.exe",
                    make = binDir .. "/mingw32-make.exe",
                    debugger = binDir .. "/lldb-mi.exe",
                    debugServer = binDir .. "/lldb-server.exe",
                    resourceCompiler = binDir .. "/" .. foreignTriplet .. "-windres.exe",
                    binDirs = {binDir, foreignDllDir},
                }
                local customLinkParams: {string} = nil
                if C_FileSystem.exists(libDir .. "/utf8init.o") then
                    customLinkParams = {"-Wl,utf8init.o", "-Wl,utf8manifest.o"}
                end
                local release, _, _ = generateConfig(
                    function (arch_: string, profile: string): string
                        return nameGeneratorClang(lang, arch_, profile, true)
                    end,
                    programs,
                    {
                        arch = foreignArch,
                        customLinkParams = customLinkParams,
                        isClang = true,
                    }
                )
                table.insert(compilerList, release)
            end
        end

        table.insert(noSearch, binDir)

        local llvmOrgPath = C_System.readRegistry([[Software\LLVM\LLVM]], "") or C_System.readRegistry([[Software\Wow6432Node\LLVM\LLVM]], "")
        if not llvmOrgPath then
            return
        end
        local llvmOrgBinDir = llvmOrgPath .. "/bin"

        do
            local msvcTriplet = gnuArchMap[appArch] .. "-pc-windows-msvc"
            local libDir = libexecDir .. "/llvm-mingw/" .. msvcTriplet .. "/lib"
            local programs: Programs = {
                cCompiler = llvmOrgBinDir .. "/clang.exe",
                cxxCompiler = llvmOrgBinDir .. "/clang++.exe",
                make = binDir .. "/mingw32-make.exe",
                debugger = binDir .. "/lldb-mi.exe",
                debugServer = binDir .. "/lldb-server.exe",
                resourceCompiler = binDir .. "/" .. appTriplet .. "-windres.exe",
                binDirs = {llvmOrgBinDir},
                libDirs = {libDir},
            }
            local customLinkParams = {"-target", msvcTriplet}
            if C_FileSystem.exists(libDir .. "/utf8init.o") then
                table.insert(customLinkParams, "-Wl,utf8init.o")
                table.insert(customLinkParams, "-Wl,utf8manifest.o")
            end
            local release, debug_, _ = generateConfig(
                function (arch: string, profile: string): string
                    return nameGeneratorClang(lang, arch, profile, false)
                end,
                programs,
                {
                    arch = appArch,
                    customCompileParams = {
                        "-target", msvcTriplet,
                        "-fms-extensions",
                        "-fms-compatibility",
                        "-fdelayed-template-parsing",
                    },
                    customLinkParams = customLinkParams;
                    isClang = true,
                }
            )
            table.insert(compilerList, release)
            table.insert(compilerList, debug_)
        end

        for _, foreignArch in ipairs(supportedAppArches) do
            local gnuArch = gnuArchMap[foreignArch]
            if foreignArch ~= appArch and gnuArch ~= nil then
                local foreignTriplet = gnuArch .. "-w64-mingw32"
                local msvcTriplet = gnuArchMap[foreignArch] .. "-pc-windows-msvc"
                local libDir = libexecDir .. "/llvm-mingw/" .. msvcTriplet .. "/lib"
                local programs: Programs = {
                    cCompiler = llvmOrgBinDir .. "/clang.exe",
                    cxxCompiler = llvmOrgBinDir .. "/clang++.exe",
                    make = binDir .. "/mingw32-make.exe",
                    debugger = binDir .. "/lldb-mi.exe",
                    debugServer = binDir .. "/lldb-server.exe",
                    resourceCompiler = binDir .. "/" .. foreignTriplet .. "-windres.exe",
                    binDirs = {llvmOrgBinDir},
                    libDirs = {libDir},
                }
                local customLinkParams = {"-target", msvcTriplet}
                if C_FileSystem.exists(libDir .. "/utf8init.o") then
                    table.insert(customLinkParams, "-Wl,utf8init.o")
                    table.insert(customLinkParams, "-Wl,utf8manifest.o")
                end
                local release, _, _ = generateConfig(
                    function (arch: string, profile: string): string
                        return nameGeneratorClang(lang, arch, profile, false)
                    end,
                    programs,
                    {
                        arch = foreignArch,
                        customCompileParams = {
                            "-target", msvcTriplet,
                            "-fms-extensions",
                            "-fms-compatibility",
                            "-fdelayed-template-parsing",
                        },
                        customLinkParams = customLinkParams,
                        isClang = true,
                    }
                )
                table.insert(compilerList, release)
            end
        end
        table.insert(noSearch, llvmOrgBinDir)
    end

    checkAndAddClang()

    local result = {
        compilerList = compilerList,
        noSearch = noSearch,
        preferCompiler = preferCompiler,
    }

    return result
end