local UEHelpers = require("UEHelpers")

local MOD_NAME = "CheatToggles"
local MOD_VERSION = "1.1.0"
local MOD_NEXUS_ID = "64"
local LOG_FILE = "./ue4ss/Mods/" .. MOD_NAME .. "/CheatToggles.log"

local function initLogFile()
    local dir = LOG_FILE:match("(.*[/\\])")
    os.execute('mkdir "' .. dir:gsub("/", "\\") .. '" 2>nul')
    local f = io.open(LOG_FILE, "w")
    if f then
        f:write(string.format("=== %s v%s Log Started: %s ===\n", MOD_NAME, MOD_VERSION, os.date("%Y-%m-%d %H:%M:%S")))
        f:close()
    end
end

initLogFile()

local function log(msg)
    local timestamp = os.date("%H:%M:%S")
    local line = string.format("[%s] [%s] %s", timestamp, MOD_NAME, msg)
    local f = io.open(LOG_FILE, "a")
    if f then
        f:write(line .. "\n")
        f:close()
    end
    print(line .. "\n")
end

local function logError(msg)
    log("ERROR: " .. msg)
end

local function logWarning(msg)
    log("WARNING: " .. msg)
end

local function isValid(obj)
    if not obj then return false end
    local ok, v = pcall(function() return obj:IsValid() end)
    return ok and v
end

local function safeCall(label, fn)
    local ok, err = pcall(fn)
    if not ok then
        logError(string.format("%s: %s", label, tostring(err)))
    end
    return ok
end

local CheatDefs = {
    { key = "ThirdPerson", tag = "ThirdPerson", title = "Third Person", description = "Turn third person mode on/off", mode = "command" },
    { key = "GodMode", tag = "Cheat.GodMode", title = "God Mode", description = "Makes you invincible to all damage", mode = "tag" },
    { key = "Oxygen", tag = "Cheat.NoSuffocate", title = "Unlimited Oxygen", description = "Never run out of oxygen", mode = "tag" },
    { key = "Hunger", tag = "Cheat.NoStarve", title = "Never Hungry", description = "Pause hunger meter - Does not drain", mode = "tag" },
    { key = "Thirst", tag = "Cheat.NoDehydrate", title = "Never Thirsty", description = "Pause thirst meter - Does not drain", mode = "tag" },
    { key = "NoClip", tag = "Cheat.NoClip", title = "No Clip", description = "Walk and swim through walls and terrain", mode = "tag" },
    { key = "Fly", tag = "Cheat.Fly", title = "Fly", description = "Fly freely with collision still active (Can be used with NoClip)", mode = "tag" },
    { key = "Invisible", tag = "Cheat.Invisible", title = "Invisible", description = "Creatures cannot see or target you", mode = "tag" },
    { key = "HideMask", tag = "Cheat.HideMask", title = "Hide Mask", description = "Hides the scuba mask from view", mode = "tag" },
    { key = "NoCost", tag = "NoCost", title = "No Cost", description = "Building and crafting require no materials", mode = "command" },
    { key = "FastBuild", tag = "FastBuild", title = "Fast Build", description = "Structures build much faster", mode = "value", onValue = "20", offValue = "0" },
    { key = "UnlockAllRecipes", tag = "UnlockAllRecipes", title = "Unlock All Recipes", description = "Unlocks all recipes", mode = "command" },
    { key = "UnlimitedBattery", tag = "UnlimitedBattery", title = "Unlimited Battery", description = "Tools and vehicles never run out of battery power", mode = "command" },
    { key = "UnlimitedPower", tag = "UnlimitedPower", title = "Unlimited Power", description = "Bases have infinite power", mode = "command" },
    { key = "FreezeTime", tag = "FreezeTime", title = "Freeze Time of Day", description = "Stops the day/night cycle", mode = "command", offTag = "UnFreezeTime", resetOnStartup = true },
}

local Config = {}
local previousConfig = {}
local tagMap = {}
local modeMap = {}
local defByKey = {}

for _, def in ipairs(CheatDefs) do
    Config[def.key] = false
    previousConfig[def.key] = false
    tagMap[def.key] = def.tag
    modeMap[def.key] = def.mode
    defByKey[def.key] = def
end

local isApplying = false
local isGameReady = false
local hasRestoredOnce = false
local gameReadyCheckCount = 0
local _cheatManagerConfirmed = false
local _kismetLibConfirmed = false
local skyboxFixApplied = false
local skyboxFixAttempts = 0
local _sharedVarAvailable = false

local MANIFEST_PATH = "./ue4ss/Mods/SN2ModSettings/registrations/" .. MOD_NAME .. ".lua"
local SAVED_PATH    = "./ue4ss/Mods/SN2ModSettings/saved/" .. MOD_NAME .. ".lua"

local function GetPlayerControllerFresh()
    local pc = nil
    safeCall("GetPC", function()
        pc = UEHelpers.GetPlayerController()
        if not isValid(pc) then
            pc = nil
        end
        if pc then
            if not pc.CheatManager then
                logError("PlayerController.CheatManager is nil - CheatManagerEnabler may not be loaded!")
                pc = nil
            elseif not isValid(pc.CheatManager) then
                logWarning("PlayerController.CheatManager exists but is not valid yet (waiting for spawn)")
                pc = nil
            else
                if not _cheatManagerConfirmed then
                    log("CheatManager confirmed valid")
                    _cheatManagerConfirmed = true
                end
            end
        end
    end)

    if not pc then
        safeCall("GetPC:FindFirst", function()
            pc = FindFirstOf("SN2PlayerController")
            if not isValid(pc) then
                pc = nil
            end
            if pc then
                if not pc.CheatManager then
                    logError("PlayerController.CheatManager is nil - CheatManagerEnabler may not be loaded!")
                    pc = nil
                elseif not isValid(pc.CheatManager) then
                    logWarning("PlayerController.CheatManager exists but is not valid yet (waiting for spawn)")
                    pc = nil
                else
                    if not _cheatManagerConfirmed then
                        log("CheatManager confirmed valid (via FindFirstOf)")
                        _cheatManagerConfirmed = true
                    end
                end
            end
        end)
    end

    return pc
end

local function GetKismetLibFresh()
    local lib = nil
    safeCall("GetKismetLib", function()
        lib = StaticFindObject("/Script/Engine.Default__KismetSystemLibrary")
        if not isValid(lib) then
            logError("KismetSystemLibrary found but is invalid")
            lib = nil
        end
        if lib and not _kismetLibConfirmed then
            log("KismetSystemLibrary confirmed valid")
            _kismetLibConfirmed = true
        end
    end)
    return lib
end

local function isPlayerInWorld()
    local inWorld = false
    safeCall("isPlayerInWorld", function()
        local pc = FindFirstOf("SN2PlayerController")
        if isValid(pc) then
            inWorld = true
        end
    end)
    return inWorld
end

local function sendConsoleCommand(command)
    local executed = false
    safeCall("sendConsoleCommand", function()
        local lib = GetKismetLibFresh()
        if not lib then
            logWarning(string.format("SKIPPED (no KismetLib): %s", command))
            return
        end
        local pc = GetPlayerControllerFresh()
        if not pc then
            logWarning(string.format("SKIPPED (no PC): %s", command))
            return
        end
        lib:ExecuteConsoleCommand(pc, command, nil)
        log(string.format("Executed: %s", command))
        executed = true
    end)
    if not executed then
        logError(string.format("FAILED: %s", command))
    end
    return executed
end

local function fixBlackSkybox()
    log("Sending UnFreezeTime to fix potential black skybox issue")
    sendConsoleCommand("UnFreezeTime")
end

local function executeCheatCommand(key, tag, enable)
    local mode = modeMap[key]
    local def = defByKey[key]
    local success = false

    if mode == "tag" then
        if enable then
            success = sendConsoleCommand(string.format("AddTag %s", tag))
        else
            success = sendConsoleCommand(string.format("RemoveTag %s", tag))
        end
    elseif mode == "command" then
        if not enable and def and def.offTag then
            success = sendConsoleCommand(def.offTag)
        else
            success = sendConsoleCommand(tag)
        end
    elseif mode == "value" then
        if enable then
            success = sendConsoleCommand(string.format("%s %s", tag, def and def.onValue or "1"))
        else
            success = sendConsoleCommand(string.format("%s %s", tag, def and def.offValue or "0"))
        end
    end

    return success
end

local function applyChanges(initialSync)
    if isApplying then
        log("applyChanges already running, skipping")
        return
    end
    isApplying = true

    local pc = GetPlayerControllerFresh()
    if not pc then
        log("applyChanges: No player controller, deferring")
        isApplying = false
        return
    end

    for key, value in pairs(Config) do
        if previousConfig[key] ~= value then
            if initialSync and not value then
                previousConfig[key] = false
            else
                local tag = tagMap[key]
                if tag then
                    local success = executeCheatCommand(key, tag, value)
                    if success then
                        previousConfig[key] = value
                    else
                        log(string.format("Command failed for %s, will retry next poll", key))
                    end
                end
            end
        end
    end

    isApplying = false
end

local function write_manifest()
    local dir = MANIFEST_PATH:match("(.*[/\\])")
    os.execute('mkdir "' .. dir:gsub("/", "\\") .. '" 2>nul')

    local f = io.open(MANIFEST_PATH, "w")
    if not f then
        logError("FAILED to write manifest file")
        return
    end

    local lines = {}
    for _, def in ipairs(CheatDefs) do
        table.insert(lines, string.format(
            '        { key=%q, title=%q, type="toggle", default=false, description=%q }',
            def.key, def.title, def.description
        ))
    end

    local optionalFields = ""
    if MOD_NEXUS_ID ~= "" then
        optionalFields = string.format('    nexus_id = %q,\n', MOD_NEXUS_ID)
    end

    f:write(string.format(
        'return {\n'
     .. '    name    = %q,\n'
     .. '    display = "Cheat Toggles",\n'
     .. '    version = %q,\n'
     .. '%s'
     .. '    settings = {\n%s,\n    },\n}\n',
        MOD_NAME,
        MOD_VERSION,
        optionalFields,
        table.concat(lines, ",\n")
    ))
    f:close()
    log("Manifest written")
end

write_manifest()

local function LoadFromSharedVariable()
    if not ModRef then
        if not _sharedVarAvailable then
            logWarning("ModRef not available - SharedVariable not accessible")
        end
        return false
    end

    if not _sharedVarAvailable then
        _sharedVarAvailable = true
        log("SharedVariable system available via ModRef")
    end

    local changed = false
    for _, def in ipairs(CheatDefs) do
        local key = def.key
        local sharedKey = "SN2ModSettings/" .. MOD_NAME .. "/" .. key
        local v = nil
        safeCall("SharedVar:" .. key, function()
            v = ModRef:GetSharedVariable(sharedKey)
        end)
        if v ~= nil and type(v) == "boolean" then
            if Config[key] ~= v then
                log(string.format("SharedVariable changed: %s = %s", key, tostring(v)))
                Config[key] = v
                changed = true
            end
        end
    end

    return changed
end

local function readSavedConfig()
    local f = io.open(SAVED_PATH, "r")
    if not f then return nil end
    local content = f:read("*all")
    f:close()
    local loader = load(content)
    if not loader then return nil end
    local ok, data = pcall(loader)
    if not ok or type(data) ~= "table" then return nil end
    return data
end

local function writeSavedConfig(data)
    local dir = SAVED_PATH:match("(.*[/\\])")
    os.execute('mkdir "' .. dir:gsub("/", "\\") .. '" 2>nul')
    local f = io.open(SAVED_PATH, "w")
    if not f then
        logError("FAILED to write saved config file")
        return false
    end
    f:write("return {\n")
    for _, def in ipairs(CheatDefs) do
        local value = data[def.key]
        if value == nil then value = false end
        f:write(string.format("    %s = %s,\n", def.key, tostring(value)))
    end
    f:write("}\n")
    f:close()
    return true
end

local function resetCheatsOnStartup()
    local data = readSavedConfig()
    if not data then
        log("No saved config to reset")
        return
    end
    
    local resetCount = 0
    
    for _, def in ipairs(CheatDefs) do
        local shouldReset = def.mode == "tag" or def.resetOnStartup == true
        if shouldReset and data[def.key] == true then
            data[def.key] = false
            resetCount = resetCount + 1
            log(string.format("Resetting cheat: %s", def.title))
            
            if ModRef then
                local sharedKey = "SN2ModSettings/" .. MOD_NAME .. "/" .. def.key
                safeCall("ResetSharedVar:" .. def.key, function()
                    ModRef:SetSharedVariable(sharedKey, false)
                    log(string.format("  → SharedVariable reset: %s", def.key))
                end)
            end
        end
    end
    
    if resetCount > 0 then
        log(string.format("=== Resetting %d cheat(s) to OFF ===", resetCount))
        if writeSavedConfig(data) then
            log("Saved config updated successfully")
        end
    else
        log("No cheats needed resetting")
    end
end

LoopAsync(1000, function()
    ExecuteInGameThread(function()
        if not isGameReady then
            gameReadyCheckCount = gameReadyCheckCount + 1
            local pc = GetPlayerControllerFresh()
            if pc then
                isGameReady = true
                log("Game ready - player controller found")
            elseif gameReadyCheckCount > 60 then
                isGameReady = true
                log("Game ready (timeout failsafe)")
            end
            return
        end

        if not skyboxFixApplied then
            skyboxFixAttempts = skyboxFixAttempts + 1
            if isPlayerInWorld() then
                log("Player is in world - applying skybox fix")
                fixBlackSkybox()
                skyboxFixApplied = true
            elseif skyboxFixAttempts > 40 then
                log("Skybox fix timeout - applying anyway")
                fixBlackSkybox()
                skyboxFixApplied = true
            else
                if skyboxFixAttempts % 10 == 1 then
                    log("Waiting for player to load into world...")
                end
                return
            end
        end

        if not hasRestoredOnce then
            hasRestoredOnce = true
            safeCall("restoreState", function()
                local loaded = LoadFromSharedVariable()
                if not loaded then
                    local data = readSavedConfig()
                    if not data then
                        log("No saved config found, will retry next tick")
                        hasRestoredOnce = false
                        return
                    end
                    for _, def in ipairs(CheatDefs) do
                        local v = data[def.key]
                        if v ~= nil then
                            if type(v) ~= "boolean" then
                                v = (v and v ~= 0) and true or false
                            end
                            Config[def.key] = v
                        end
                    end
                end

                local count = 0
                for _, def in ipairs(CheatDefs) do
                    local shouldSkip = def.mode == "tag" or def.resetOnStartup == true
                    if shouldSkip then
                        Config[def.key] = false
                        previousConfig[def.key] = false
                    else
                        previousConfig[def.key] = false
                        if Config[def.key] then
                            count = count + 1
                        end
                    end
                end

                if count > 0 then
                    log(string.format("=== Restoring %d enabled cheat(s) from saved config ===", count))
                    for _, def in ipairs(CheatDefs) do
                        local shouldSkip = def.mode == "tag" or def.resetOnStartup == true
                        if Config[def.key] and not shouldSkip then
                            log(string.format("  - %s: ON", def.title))
                        end
                    end
                    applyChanges(true)
                    log("=== Restoration complete ===")
                else
                    log("No cheats to restore")
                    for k, v in pairs(Config) do
                        previousConfig[k] = v
                    end
                end
            end)
            return
        end

        if LoadFromSharedVariable() then
            applyChanges(false)
        end
    end)
end)

resetCheatsOnStartup()

local function checkDependencies()
    log("=== Dependency Check ===")
    log("Checking for CheatManagerEnabler mod...")
    log("If commands don't work, ensure CheatManagerEnabler is installed and enabled in mods.txt")
    local settingsExists = io.open(SAVED_PATH, "r")
    if settingsExists then
        settingsExists:close()
        log("SN2ModSettings config file found")
    else
        log("SN2ModSettings config not found yet - will be created on first settings access")
    end
    if ModRef then
        log("ModRef available - SharedVariable system active")
    else
        logWarning("ModRef not available - falling back to file-based config polling")
    end
    log(string.format("Loaded! %d cheat toggles registered.", #CheatDefs))
    log("Log file location: " .. LOG_FILE)
    log("========================")
end

checkDependencies()