--[[
    READABLE ITEM DATABASE

    This module turns RSDWTools' items.json into a small lookup table:

        ITEM_ID -> Unreal asset path

    It does not touch the running game. The difficult Unreal interaction lives
    separately in item_reference_resolver.lua.

    RSDWTools records where every extracted item came from. Two examples:

        .../RSDragonwilds/Content/Gameplay/.../ITEM_Resources_Diamond.json
        .../RSDragonwilds/Plugins/GameFeatures/UmbralSands/Content/
            Gameplay/.../ITEM_Armour_T7_Body_Adamant.json

    Unreal mounts those packages as:

        /Game/Gameplay/.../ITEM_Resources_Diamond
        /UmbralSands/Gameplay/.../ITEM_Armour_T7_Body_Adamant
]]

local Json = require("json_decoder")
local Logger = require("logger")

local ItemDatabase = {}


local function readFile(path)
    local file = io.open(path, "r")

    if not file then
        return nil, "Could not open file: " .. tostring(path)
    end

    local read, contentOrError = pcall(function()
        return file:read("*a")
    end)
    pcall(function()
        file:close()
    end)

    if not read or type(contentOrError) ~= "string" then
        return nil, "Could not read file: " .. tostring(path) .. " ("
            .. tostring(contentOrError) .. ")"
    end

    local content = contentOrError
    return content
end


local function itemIdFromSourcePath(sourcePath)
    if type(sourcePath) ~= "string" then
        return nil
    end

    return sourcePath:match("([^/\\]+)%.json$")
end


function ItemDatabase.AssetPathFromSourcePath(sourcePath)
    if type(sourcePath) ~= "string" then
        return nil
    end

    local path = sourcePath:gsub("\\", "/")

    if path:find("%c") or path:find("/../", 1, true) then
        return nil
    end

    -- Base-game content is mounted below /Game/.
    local baseGamePath = path:match("/RSDragonwilds/Content/(.+)%.json$")
    if baseGamePath then
        return "/Game/" .. baseGamePath
    end

    -- Game-feature content is mounted below its plugin name, for example
    -- /UmbralSands/, /DowdunReach/, or /Fishing/.
    local pluginName, pluginPath = path:match(
        "/RSDragonwilds/Plugins/GameFeatures/([^/]+)/Content/(.+)%.json$"
    )

    if pluginName and pluginPath then
        return "/" .. pluginName .. "/" .. pluginPath
    end

    return nil
end


local function buildLookup(data)
    local itemsById = {}
    local invalidCount = 0
    local duplicateIds = {}
    local detailCount = 0
    local tabs = data and data.tabs

    if type(tabs) ~= "table" then
        return nil, "items.json does not contain a tabs table"
    end

    local function reject(message)
        invalidCount = invalidCount + 1
        detailCount = detailCount + 1

        if detailCount <= 10 then
            Logger.Warning("ITEM DATABASE", message)
        end
    end

    for tabName, tab in pairs(tabs) do
        if type(tab) == "table" and type(tab.items) == "table" then
            for _, item in ipairs(tab.items) do
                local sourcePath = type(item) == "table" and item.sourcePath
                    or nil
                local itemId = itemIdFromSourcePath(sourcePath)
                local assetPath = ItemDatabase.AssetPathFromSourcePath(sourcePath)

                if itemId and assetPath and not duplicateIds[itemId] then
                    if itemsById[itemId] then
                        itemsById[itemId] = nil
                        duplicateIds[itemId] = true
                        reject("Duplicate ITEM_ID ignored: " .. itemId)
                    else
                        itemsById[itemId] = {
                            ItemId = itemId,
                            AssetPath = assetPath,
                            SourcePath = sourcePath,
                            Name = item.name,
                        }
                    end
                else
                    if not duplicateIds[itemId] then
                        reject("Could not derive an asset path from: "
                            .. tostring(sourcePath))
                    end
                end
            end
        else
            reject("Tab " .. tostring(tabName)
                .. " has no usable items list")
        end
    end

    if invalidCount > 0 then
        if detailCount > 10 then
            Logger.Warning("ITEM DATABASE", tostring(detailCount - 10)
                .. " additional invalid record(s) omitted from the log")
        end

        Logger.Warning("ITEM DATABASE", "Skipped " .. tostring(invalidCount)
            .. " invalid or ambiguous record(s)")
    end

    if ItemDatabase.Count(itemsById) == 0 then
        return nil, "items.json contains no usable ITEM_ID records"
    end

    return itemsById, nil
end


function ItemDatabase.Load(path)
    local jsonText, readError = readFile(path)
    if not jsonText then
        Logger.Error("ITEM DATABASE", readError)
        return nil
    end

    local parsed, dataOrError = pcall(Json.Decode, jsonText)
    if not parsed then
        Logger.Error("ITEM DATABASE", "Could not parse items.json")
        Logger.Error("ITEM DATABASE", dataOrError)
        return nil
    end

    local itemsById, buildError = buildLookup(dataOrError)
    if not itemsById then
        Logger.Error("ITEM DATABASE", buildError)
        return nil
    end

    Logger.Info("Item database loaded: "
        .. tostring(ItemDatabase.Count(itemsById)) .. " ITEM_IDs")

    return itemsById
end


function ItemDatabase.Count(itemsById)
    local count = 0

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

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

    return count
end


return ItemDatabase
