-- OnBootPatch.lua
-- Inventory Tetris - Build 42 Community Patch
-- Author: Malkiot (credits to Notloc)
-- Version: 1.1.1

local Logger = require("Utils/Logger")

-- Version and configuration
local ITP_VERSION = "v1.1.1"
if not _G.InventoryTetrisPatchConfig then
    _G.InventoryTetrisPatchConfig = {}
end
_G.InventoryTetrisPatchConfig.Version = ITP_VERSION
_G.InventoryTetrisPatchConfig.DebugLog = false

-- Global table definition
if not InventoryTetrisB42Patch then
    InventoryTetrisB42Patch = {}
end

Logger.log("Inventory Tetris B42 Patch loaded. Waiting for game start...")

-- Configuration
InventoryTetrisB42Patch.Config = {
    MAX_RETRY_ATTEMPTS = 10,
    RETRY_DELAY_FRAMES = 10,
    REQUIRED_CLASSES = {"ISInventoryPage", "ISInventoryTransferAction", "ISInventoryPane"}
}

-- Check if required game classes are available
local function areRequiredClassesAvailable()
    for _, className in ipairs(InventoryTetrisB42Patch.Config.REQUIRED_CLASSES) do
        if not _G[className] then
            return false, className
        end
    end
    return true
end

-- Safe module loading with error handling
local function safeRequire(modulePath)
    local success, result = pcall(require, modulePath)
    if success then
        return true, result
    else
        Logger.error("Failed to load module: " .. modulePath .. " - " .. tostring(result))
        return false, result
    end
end

-- Retry logic for patches that fail initially
local function delayedInit(unappliedFunctions)
    Logger.log("Initial patch attempt failed. Setting up retry loop...")
    
    local attempts = 0
    local maxAttempts = InventoryTetrisB42Patch.Config.MAX_RETRY_ATTEMPTS
    local retryDelay = InventoryTetrisB42Patch.Config.RETRY_DELAY_FRAMES
    
    local function tryInit()
        attempts = attempts + 1
        
        if attempts % retryDelay ~= 0 then
            return
        end
        
        local effectiveAttempt = math.ceil(attempts / retryDelay)
        local effectiveMaxAttempts = math.ceil(maxAttempts / retryDelay)
        
        Logger.log("Retry attempt #" .. effectiveAttempt .. "/" .. effectiveMaxAttempts, "RETRY")
        
        local classesAvailable, missingClass = areRequiredClassesAvailable()
        if classesAvailable then
            local stillUnapplied = InventoryTetrisB42Patch.applySpecificPatches(unappliedFunctions)
            
            if #stillUnapplied == 0 then
                Events.OnTick.Remove(tryInit)
                Logger.success("All patches successfully applied after " .. effectiveAttempt .. " attempts!")
            else
                unappliedFunctions = stillUnapplied
                Logger.log(string.format("Progress: %d patches remaining", #stillUnapplied), "RETRY")
            end
        elseif effectiveAttempt >= effectiveMaxAttempts then
            Events.OnTick.Remove(tryInit)
            Logger.error("Failed to apply " .. #unappliedFunctions .. " patches - missing class '" .. 
                        (missingClass or "unknown") .. "' after " .. effectiveMaxAttempts .. " attempts")
        else
            Logger.log("Waiting for class: " .. (missingClass or "unknown"), "RETRY")
        end
    end
    
    Events.OnTick.Add(tryInit)
    return true
end

-- Load patch modules
function InventoryTetrisB42Patch.loadModules()
    local patchFiles = {
        "Patches/InventoryPagePatch",
        "Patches/KeyringFix",
        "Patches/ResizeFix",
        "Patches/InventoryPanePrerenderPatch",
		"Patches/WalletFixFix",
    }
    
    Logger.log("Loading patch modules...")
    
    local loadedCount = 0
    for i, path in ipairs(patchFiles) do
        local success, module = safeRequire(path)
        if success then
            loadedCount = loadedCount + 1
            Logger.debug("Module loaded: " .. path)
            
            -- Register the module's apply function
            local fileName = path:match("([^/]+)$")
            local funcName = "apply" .. fileName
            
            if module and type(module.apply) == "function" then
                InventoryTetrisB42Patch[funcName] = module.apply
            else
                Logger.error("Module " .. fileName .. " doesn't have an apply function")
            end
        end
    end
    
    Logger.log(string.format("Finished loading modules. Success: %d/%d", loadedCount, #patchFiles))
    return patchFiles
end

-- Apply patches to game
function InventoryTetrisB42Patch.applySpecificPatches(patchFileList)
    local unappliedFunctions = {}
    local patchesApplied = 0
    local totalToApply = #patchFileList

    local classesAvailable, missingClass = areRequiredClassesAvailable()
    if not classesAvailable then
        Logger.log("Required classes not available (missing: " .. (missingClass or "unknown") .. ")", "RETRY")
        return patchFileList
    end

    for i, path in ipairs(patchFileList) do
        local fileName = path:match("([^/]+)$")
        local funcName = "apply" .. fileName
        local patchFn = InventoryTetrisB42Patch[funcName]

        if patchFn and type(patchFn) == "function" then
            local success, result = pcall(patchFn)
            
            if success and result then
                patchesApplied = patchesApplied + 1
                Logger.success("Patch: " .. fileName)
            else
                unappliedFunctions[#unappliedFunctions + 1] = path
                if not success then
                    Logger.error("Patch '" .. funcName .. "' error: " .. tostring(result))
                else
                    Logger.log("Patch: " .. fileName, "FAILURE")
                end
            end
        else
            unappliedFunctions[#unappliedFunctions + 1] = path
            Logger.error("Patch function '" .. funcName .. "' not found")
        end
    end

    Logger.log(string.format("Batch Summary: %d/%d applied. Remaining: %d", 
        patchesApplied, totalToApply, #unappliedFunctions))
    
    return unappliedFunctions
end

-- Main initialization
function InventoryTetrisB42Patch.init()
    if InventoryTetrisB42Patch._initialized then
        Logger.log("Mod already initialized, skipping")
        return true
    end
    
    Logger.log("Starting initialization. Version: " .. ITP_VERSION)
    
    -- Setup ModScope stub (required by KeyringFix)
    if not _G.ModScope then
        _G.ModScope = {}
        Logger.log("Created global ModScope stub", "INFO")
    end
    if not _G.ModScope.withInstanceofExclusion then
        _G.ModScope.withInstanceofExclusion = function(fn, className)
            return fn()
        end
        Logger.log("Created global ModScope.withInstanceofExclusion stub", "INFO")
    end
    
    local allPatchFiles = InventoryTetrisB42Patch.loadModules()
    local unapplied = InventoryTetrisB42Patch.applySpecificPatches(allPatchFiles)
    
    if #unapplied > 0 then
        Logger.log(string.format("Initial attempt: %d/%d patches applied", 
                   #allPatchFiles - #unapplied, #allPatchFiles))
        delayedInit(unapplied)
        InventoryTetrisB42Patch._initialized = true
        return false
    else
        Logger.success("All patches applied successfully on first attempt!")
        InventoryTetrisB42Patch._initialized = true
        return true
    end
end

Events.OnGameStart.Add(InventoryTetrisB42Patch.init)