--[[
    UNREAL ENGINE BACK END - MOST USERS SHOULD NOT EDIT THIS FILE

    loot_config.lua contains readable ITEM_ID text. Dragonwilds' loot struct
    instead requires a TSoftObjectPtr. This module performs the validated
    bridge proven by the working live build:

        ITEM_ID -> loaded UObject -> TSoftObjectPtr

    It uses StaticFindObject() only. It never scans other loot tables and never
    calls LoadAsset(). A missing, obscure, or unusable item is reported and
    omitted from the returned lookup instead of being passed to the builder.
]]

local Logger = require("logger")

local ItemResolver = {}


local function mark(checkpoint, message)
    if type(checkpoint) == "function" then
        checkpoint("Resolver: " .. message)
    end
end


local function configuredItemIds(config)
    local uniqueIds = {}

    if type(config) ~= "table" then
        return {}
    end

    for _, bagSettings in pairs(config) do
        local lootList = type(bagSettings) == "table" and bagSettings.Loot

        if type(lootList) == "table" then
            for _, loot in ipairs(lootList) do
                if type(loot) == "table" and type(loot.Item) == "string" then
                    uniqueIds[loot.Item] = true
                end
            end
        end
    end

    local sortedIds = {}
    for itemId in pairs(uniqueIds) do
        sortedIds[#sortedIds + 1] = itemId
    end

    table.sort(sortedIds)
    return sortedIds
end


local function isValidUnrealObject(object)
    if object == nil then
        return false
    end

    local checked, valid = pcall(function()
        return object:IsValid()
    end)

    return checked and valid == true
end


local function findLoadedObject(objectPath)
    if type(StaticFindObject) ~= "function" then
        return nil, "UE4SS StaticFindObject() is unavailable"
    end

    local found, objectOrError = pcall(StaticFindObject, objectPath)

    if not found then
        return nil, "StaticFindObject failed for " .. objectPath .. ": "
            .. tostring(objectOrError)
    end

    if not isValidUnrealObject(objectOrError) then
        return nil, "StaticFindObject returned no valid object for " .. objectPath
    end

    return objectOrError
end


local function findItemObject(itemId, assetPath)
    -- Unreal's full object path repeats the asset name after a dot:
    -- /Game/.../ITEM_ID.ITEM_ID
    local fullObjectPath = assetPath .. "." .. itemId
    local reference, fullPathError = findLoadedObject(fullObjectPath)

    if reference then
        return reference
    end

    -- Some UE4SS lookups accept a package path. The full form is used by this
    -- game version; this safe fallback also produces a clearer failure report.
    local packageReference, packagePathError = findLoadedObject(assetPath)

    if packageReference then
        return packageReference
    end

    return nil, tostring(fullPathError) .. "; " .. tostring(packagePathError)
end


local function findSoftPointerConverter()
    local kismetClass, classError = findLoadedObject(
        "/Script/Engine.KismetSystemLibrary"
    )

    if not kismetClass then
        return nil, classError
    end

    local readCdo, cdoOrError = pcall(function()
        return kismetClass:GetCDO()
    end)

    if not readCdo then
        return nil, "Could not obtain the KismetSystemLibrary default object: "
            .. tostring(cdoOrError)
    end

    if not isValidUnrealObject(cdoOrError) then
        return nil, "KismetSystemLibrary returned no valid default object"
    end

    return cdoOrError
end


local function softPointerPath(pointer)
    if pointer == nil then
        return nil, "conversion returned nil"
    end

    local readPath, pathOrError = pcall(function()
        return pointer:GetObjectID():GetAssetPathName():ToString()
    end)

    if not readPath then
        return nil, tostring(pathOrError)
    end

    local path = tostring(pathOrError)
    if path == "" then
        return nil, "converted soft pointer has an empty asset path"
    end

    return path
end


local function itemIdFromObjectPath(objectPath)
    return objectPath:match("([^/%.]+)$")
end


local function makeSoftPointer(converter, itemObject, itemId)
    local converted, pointerOrError = pcall(function()
        return converter:Conv_ObjectToSoftObjectReference(itemObject)
    end)

    if not converted then
        return nil, tostring(pointerOrError)
    end

    local pointerPath, readError = softPointerPath(pointerOrError)

    if not pointerPath then
        return nil, "Could not read converted soft pointer: "
            .. tostring(readError)
    end

    local resolvedItemId = itemIdFromObjectPath(pointerPath)
    if resolvedItemId ~= itemId then
        return nil, "Converted soft pointer identified " .. pointerPath
            .. " instead of " .. itemId
    end

    return pointerOrError
end


function ItemResolver.ResolveConfiguredItems(config, itemsById, checkpoint)
    local references = {}
    local requestedIds = configuredItemIds(config)
    local stats = {
        Requested = #requestedIds,
        Resolved = 0,
        Failed = 0,
    }

    if #requestedIds == 0 then
        Logger.Info("Configured item soft pointers resolved: 0/0")
        return references, stats
    end

    mark(checkpoint, "BEFORE locating Kismet soft-pointer converter")
    local converter, converterError = findSoftPointerConverter()

    if not converter then
        stats.Failed = #requestedIds
        mark(checkpoint, "FAILED locating Kismet soft-pointer converter")
        Logger.Error("RESOLVER", "Soft-pointer converter unavailable: "
            .. tostring(converterError))
        Logger.Info("Configured item soft pointers resolved: 0/"
            .. tostring(#requestedIds))
        return references, stats
    end

    mark(checkpoint, "AFTER locating Kismet soft-pointer converter")

    for index, itemId in ipairs(requestedIds) do
        local item = type(itemsById) == "table" and itemsById[itemId] or nil
        mark(checkpoint, "ITEM " .. tostring(index) .. "/"
            .. tostring(#requestedIds) .. " BEFORE lookup: " .. itemId)

        if not item then
            stats.Failed = stats.Failed + 1
            mark(checkpoint, "ITEM lookup FAILED: " .. itemId)
            Logger.Warning("RESOLVER", "Unknown ITEM_ID ignored: " .. itemId)
        elseif type(item.AssetPath) ~= "string" or item.AssetPath == "" then
            stats.Failed = stats.Failed + 1
            mark(checkpoint, "ITEM asset path INVALID: " .. itemId)
            Logger.Warning("RESOLVER", "No usable asset path for " .. itemId
                .. "; drop ignored")
        else
            local itemObject, lookupError = findItemObject(itemId, item.AssetPath)

            if not itemObject then
                stats.Failed = stats.Failed + 1
                mark(checkpoint, "ITEM lookup FAILED: " .. itemId)
                Logger.Warning("RESOLVER", "Item object not found for " .. itemId
                    .. " at " .. item.AssetPath .. "; drop ignored ("
                    .. tostring(lookupError) .. ")")
            else
                mark(checkpoint, "ITEM AFTER lookup; BEFORE conversion: "
                    .. itemId)
                local softPointer, conversionError = makeSoftPointer(
                    converter,
                    itemObject,
                    itemId
                )

                if softPointer then
                    references[itemId] = softPointer
                    stats.Resolved = stats.Resolved + 1
                    mark(checkpoint, "ITEM AFTER conversion: " .. itemId)
                else
                    stats.Failed = stats.Failed + 1
                    mark(checkpoint, "ITEM conversion FAILED: " .. itemId)
                    Logger.Warning("RESOLVER", "Soft-pointer conversion failed for "
                        .. itemId .. "; drop ignored ("
                        .. tostring(conversionError) .. ")")
                end
            end
        end
    end

    Logger.Info("Configured item soft pointers resolved: "
        .. tostring(stats.Resolved) .. "/" .. tostring(stats.Requested))

    return references, stats
end


function ItemResolver.Count(references)
    local count = 0

    if type(references) ~= "table" then
        return count
    end

    for _ in pairs(references) do
        count = count + 1
    end

    return count
end


return ItemResolver
