
local M = {}

local function log(msg)
    print(string.format("[SN2ModSettings] %s\n", msg))
end

local ROOT = "./ue4ss/Mods/SN2ModSettings/"
M.REGISTRATIONS_DIR = ROOT .. "registrations/"
M.SAVED_DIR         = ROOT .. "saved/"

M.UPDATE_CACHE_PATH  = M.SAVED_DIR .. "_update_cache.lua"
M.UPDATE_TTL_SECONDS = 3 * 60 * 60

local _ensured = {}
local function ensure_dir(path)
    if _ensured[path] then return end

    local sentinel = path .. ".ensure_probe"
    local f = io.open(sentinel, "w")
    if f then
        f:close()
        os.remove(sentinel)
        _ensured[path] = true
        return
    end

    os.execute('mkdir "' .. path:gsub("/", "\\") .. '" 2>nul')
    _ensured[path] = true
end

function M.ListFiles(dir, ext)
    local files = {}
    local cmd = 'dir /b "' .. dir:gsub("/", "\\") .. '*' .. (ext or "") .. '" 2>nul'
    local handle = io.popen(cmd)
    if not handle then return files end
    for line in handle:lines() do
        files[#files + 1] = line
    end
    handle:close()
    return files
end

function M.LoadLuaTable(path)
    local f = io.open(path, "r")
    if not f then return nil end
    local content = f:read("*all")
    f:close()
    if not content or content == "" then return nil end
    local loader, err = load(content)
    if not loader then
        log(string.format("Failed to parse %s: %s", path, tostring(err)))
        return nil
    end
    local ok, data = pcall(loader)
    if not ok or type(data) ~= "table" then
        log(string.format("Failed to eval %s: %s", path, tostring(data)))
        return nil
    end
    return data
end

function M.SerializeLuaTable(t)
    local parts = { "-- Generated by SN2ModSettings. Editable; reloaded automatically.\nreturn {\n" }
    local keys = {}
    for k, _ in pairs(t) do keys[#keys + 1] = k end
    table.sort(keys)
    for _, k in ipairs(keys) do
        local v = t[k]
        if type(v) == "number" or type(v) == "boolean" then
            parts[#parts + 1] = string.format("    %s = %s,\n", k, tostring(v))
        elseif type(v) == "string" then
            parts[#parts + 1] = string.format("    %s = %q,\n", k, v)
        end
    end
    parts[#parts + 1] = "}\n"
    return table.concat(parts)
end

function M.SaveLuaTable(path, t)
    ensure_dir(path:match("(.*[/\\])"))
    local serialized = M.SerializeLuaTable(t)
    local f = io.open(path, "w")
    if not f then return false end
    f:write(serialized)
    f:close()

    local fname = path:match("([^/\\]+)%.lua$")
    if fname and ModRef then
        for k, v in pairs(t) do
            local vt = type(v)
            if vt == "number" or vt == "boolean" or vt == "string" then
                local ok, err = pcall(function()
                    ModRef:SetSharedVariable("SN2ModSettings/" .. fname .. "/" .. k, v)
                end)
                if not ok then
                    log(string.format("SharedVariable write failed for %s/%s: %s",
                        fname, k, tostring(err)))
                end
            end
        end
    end
    return true
end

local function mod_is_installed(mod_id)
    if not mod_id or mod_id == "" then return false end
    local f = io.open("./ue4ss/Mods/" .. mod_id .. "/enabled.txt", "r")
    if f then f:close(); return true end
    return false
end

function M.LoadRegistrations()
    ensure_dir(M.REGISTRATIONS_DIR)
    local files = M.ListFiles(M.REGISTRATIONS_DIR, ".lua")
    local out = {}
    local stale = {}
    for _, fname in ipairs(files) do
        local manifest = M.LoadLuaTable(M.REGISTRATIONS_DIR .. fname)
        if manifest and manifest.name then

            local mod_id = fname:gsub("%.lua$", "")
            if mod_is_installed(mod_id) or mod_is_installed(manifest.name) then
                out[#out + 1] = manifest
            else
                stale[#stale + 1] = mod_id
            end
        end
    end
    if #stale > 0 then
        log(string.format("Skipped %d stale registration(s) (mod not installed): %s",
            #stale, table.concat(stale, ", ")))
    end
    return out
end

function M.GetValue(mod_id, key, default_value)
    local saved = M.LoadLuaTable(M.SAVED_DIR .. mod_id .. ".lua")
    if saved and saved[key] ~= nil then return saved[key] end
    return default_value
end

function M.SetValue(mod_id, key, value)
    ensure_dir(M.SAVED_DIR)
    local saved = M.LoadLuaTable(M.SAVED_DIR .. mod_id .. ".lua") or {}
    saved[key] = value
    return M.SaveLuaTable(M.SAVED_DIR .. mod_id .. ".lua", saved)
end

function M.ParseVersion(s)
    if type(s) ~= "string" then return nil end
    local parts = {}
    for n in s:gmatch("(%d+)") do
        parts[#parts + 1] = tonumber(n)
        if #parts >= 4 then break end
    end
    if #parts == 0 then return nil end
    return parts
end

function M.CompareVersions(a, b)
    local pa, pb = M.ParseVersion(a), M.ParseVersion(b)
    if not pa or not pb then return 0 end
    local n = math.max(#pa, #pb)
    for i = 1, n do
        local x = pa[i] or 0
        local y = pb[i] or 0
        if x < y then return -1 end
        if x > y then return 1 end
    end
    return 0
end

return M
