import("common")
-- TODO: optimize base.utils and xim.base.utils issues
import("base.utils")
import("config.xconfig")

local installer_base_dir = path.directory(os.scriptdir())

function load_installers(dir)
    local installers = {}

    local installers_dir = path.join(installer_base_dir, dir)

    for _, file in ipairs(os.files(path.join(installers_dir, "*.lua"))) do
        local name = path.basename(file)
        local installer = import("xim." .. dir .. "." .. name)
        installers[name] = installer
    end

    return installers
end

function deep_copy(orig)
    local copies = {}

    local function _copy(obj)
        if type(obj) ~= "table" then
            return obj
        end

        if copies[obj] then
            return copies[obj]
        end

        local new_table = {}
        copies[obj] = new_table

        for key, value in pairs(obj) do
            new_table[_copy(key)] = _copy(value)
        end

        return debug.setmetatable(new_table, debug.getmetatable(obj))
    end

    return _copy(orig)
end

function os_type()
    local os_type = os.host()
    
    if os_type == "linux" then
        os_type = linuxos.name()
        -- os_version = linuxos.version() -- TODO: get linux version
    end

    return os_type
end

local __os_info = nil
function os_info()
    if __os_info ~= nil then return __os_info end

    local os_type = os.host()
    local name, version, upstream = "", "unknown-todo", "unknown-todo"

    if os_type == "linux" then
        name = linuxos.name()
        try {
            function() version = linuxos.version() end,
            catch { 
                function(e)
                    -- TODO: fix xmake's linux version issue
                end 
            }
        }
    elseif os_type == "windows" then
        name = "windows" -- winos.name()
        version = winos.version()
    elseif os_type == "macosx" then
        name = "macosx" -- macosxos.name()
        version = macos.version()
    end

    __os_info = {
        name = name,
        version = version
    }

    return __os_info
end

function try_download_and_check(url, dir, sha256)
    local filename = path.join(dir, path.filename(url))
    if not os.isfile(filename) then
        if not common.xlings_download(url, filename) then
            os.tryrm(filename)
            return false, filename
        end
    end
    if sha256 then
        if hash.sha256(filename) ~= sha256 then
            os.tryrm(filename)
            return false, filename
        end
    end
    return true, filename
end

function is_compressed(filename)
    local ext = path.extension(filename)
    local exts = {
        ['.zip'] = true,
        ['.tar'] = true,
        ['.gz']  = true,
        ['.bz2'] = true,
        ['.xz']  = true,
        ['.7z']  = true
    }
    return exts[ext] or false
end

function local_package_manager()
    local osinfo = os_info()
    local pm = nil
    if osinfo.name == "windows" then
        pm = "winget"
    elseif osinfo.name == "ubuntu" then
        pm = "apt"
    elseif osinfo.name == "archlinux" or osinfo.name == "manjaro" then
        pm = "pacman"
    elseif is_host("macosx") then
        -- TODO
    end
    return pm
end

function deref(tlb, key)

    if tlb[key] == nil then return nil end

    while tlb[key].ref do
        key = tlb[key].ref
        if tlb[key] == nil then
            return nil
        end
    end

    return key, tlb[key] 
end

-- prompt(message) -> confirm
-- prompt(message, value) -> bool
-- prompt(message, value, cmp_func) -> bool
-- cmp_func: compare function, cmp_func(confirm, value)
--- @param message string
--- @param value? string
--- @param cmp_func? fun(string, string): boolean
--- @return string | boolean
--- @nodiscard
function prompt(message, value, cmp_func)
    cprintf("${cyan blink}-> ${clear}%s ", message)
    io.stdout:flush()
    local confirm = io.read()

    if cmp_func then  -- 3 argument
        return cmp_func(confirm, value)
    elseif value then -- 2 argument
        return confirm == value
    else              -- 1 argument
        return confirm
    end
end

function load_module(fullpath, rootdir)
    return utils.load_module(fullpath, rootdir)
end

function add_env_path(value)

    local old_value = os.getenv("PATH")

    if string.find(old_value, value, 1, true) then
        cprint("[xlings:xim]: env-path [%s] already exists", value)
        return
    end

    cprint("[xlings:xim]: add [%s] to env-path", value)
    os.addenv("PATH", value)

    if is_host("windows") then
        common.xlings_exec("setx PATH \"" .. value .. ";%PATH%\"")
    else
        local content = string.format("export PATH=%s:$PATH", value)
        append_bashrc(content)
    end
end

function append_bashrc(content)
    local bashrc = os.getenv("HOME") .. "/.bashrc"
    if not os.isfile(bashrc) then
        common.xlings_create_file_and_write(bashrc, content)
    else
        local bashrc_content = io.readfile(bashrc)
        if string.find(bashrc_content, content, 1, true) == nil then
            content = "\n" .. content
            common.xlings_file_append(bashrc, content)
        end
    end
end

local function automatch_closest_distro(distro, values)
    local seen = {}
    local distro_map = {
        manjaro = "archlinux",
        archlinux = "linux",
        ubuntu = "debian",
        debian = "linux",
        fedora = "linux",
        centos = "rhel",
        rhel = "linux",
    }
    while distro do
        if values[distro] then
            return values[distro], distro
        end

        -- 防止死循环
        if seen[distro] then
            cprint("[xlings:xim]: ${red dim}distro circle-ref detected: %s", distro)
            break
        end

        seen[distro] = true
        distro = distro_map[distro]
    end
    return nil, nil
end

-- TODO: optimize xpm os match
-- for support default match(linux) in xpackage's xpm
function xpm_target_os_helper(xpm)
    local os_name = os_info().name
    if xpm[os_name] then
        return os_name
    elseif is_host("linux") then
        local distro_value, distro = automatch_closest_distro(os_name, xpm)
        if distro_value then
            return distro
        --elseif xpm["linux"] then
        --    return "linux"
        end
    end
    return nil
end

function try_mirror_match_for_url(url, opt)

    opt = opt or { }

    if type(url) == "string" then
        return url
    elseif type(url) == "table" then
        local target_url = nil
        if opt.detect then
            local urls = {}
            for _, v in pairs(url) do
                if type(v) == "string" then
                    table.insert(urls, v)
                end
            end
            target_url = utils.low_latency_urls(urls)
        else
            local mirror = xconfig.load()["mirror"]
            target_url = url[mirror] or url["GLOBAL"] or url["DEFAULT"]
        end
        return target_url
    else
        -- TODO: add log level to control log output
        --cprint("[xlings:xim]: ${yellow dim}url is not a string or table")
        return nil
    end

end

function main()

end
