--[[
    OPTIONAL ITEM-CATALOGUE GENERATOR

    This development tool reads the RSDWTools export at Scripts/items.json and
    rewrites item_catalogue.md in the mod root. It does not scan the live game.

    It is dormant during normal play. To run it once, uncomment the marked
    `require("scanner").Scan()` line near the top of main.lua, start the game,
    wait for the success message in UE4SS.log, and comment that line again.

    File responsibilities:
      scanner.lua          Reads JSON and formats Markdown (this file)
      scanner_sorting.lua  Decides which catalogue heading each item belongs to
      json_decoder.lua     Shared JSON plumbing; normally leave it alone

    Important distinction: the runtime now reads items.json to find configured
    asset paths. The optional catalogue classification and Markdown generation
    in this file still remain completely dormant unless Scan() is called.
]]

local Scanner = {}
local Paths = require("paths")
local Json = require("json_decoder")
local Sorting = require("scanner_sorting")

local itemsJsonPath = Paths.Script("items.json")
local cataloguePath = Paths.Root("item_catalogue.md")


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

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

    local content = file:read("*a")
    file:close()
    return content
end


local function writeFile(path, content)
    local file = io.open(path, "w")

    if not file then
        return false, "Could not write file: " .. tostring(path)
    end

    file:write(content)
    file:close()
    return true
end


local function lower(value)
    return type(value) == "string" and value:lower() or ""
end


local function clean(value)
    if type(value) ~= "string" then
        return ""
    end

    value = value:gsub("[\r\n]+", " "):gsub("%s+", " ")
    return value:gsub("^%s+", ""):gsub("%s+$", "")
end


local function prettyLabel(value)
    if type(value) ~= "string" then
        return ""
    end

    value = value:gsub("_", " ")
    value = value:gsub("(%l)(%u)", "%1 %2")
    value = value:gsub("(%u)(%u%l)", "%1 %2")
    value = value:gsub("%s+", " ")
    return value:gsub("^%s+", ""):gsub("%s+$", "")
end


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

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


local function collectSections(data)
    local sections = {}
    local tabs = data and data.tabs

    if type(tabs) ~= "table" then
        return sections
    end

    -- Stable tab order makes two catalogue generations easy to compare.
    local tabOrder = { bag = 1, rune = 2, ammo = 3, quest = 4 }
    local tabNames = {}

    for tabName in pairs(tabs) do
        tabNames[#tabNames + 1] = tabName
    end

    table.sort(tabNames, function(left, right)
        local leftOrder = tabOrder[lower(left)] or 999
        local rightOrder = tabOrder[lower(right)] or 999

        if leftOrder ~= rightOrder then
            return leftOrder < rightOrder
        end

        return lower(left) < lower(right)
    end)

    local seenIds = {}

    for _, tabName in ipairs(tabNames) do
        local sourceTab = tabs[tabName]
        local items = {}

        if type(sourceTab) == "table" and type(sourceTab.items) == "table" then
            for _, rawItem in ipairs(sourceTab.items) do
                local id = type(rawItem) == "table"
                    and itemId(rawItem.sourcePath)
                    or nil

                -- Some RSDWTools tabs overlap. The catalogue should list every
                -- ITEM_ID exactly once, under the first authoritative tab.
                if id and not seenIds[id] then
                    seenIds[id] = true
                    items[#items + 1] = {
                        id = id,
                        name = rawItem.name,
                        powerLevel = tonumber(rawItem.powerLevel),
                        sourcePath = rawItem.sourcePath,
                        category = rawItem.category,
                        equipment = rawItem.equipment,
                    }
                end
            end
        end

        sections[#sections + 1] = {
            key = tabName,
            items = items,
        }
    end

    return sections
end


local function countItems(sections)
    local count = 0

    for _, section in ipairs(sections) do
        count = count + #section.items
    end

    return count
end


local function powerText(item)
    if Sorting.is_power_level(item) then
        return tostring(item.powerLevel)
    end

    return "-"
end


local function buildCatalogue(items, discoveredCount)
    local visibleCount = #items
    local output = {
        "# BagLootFramework Item Catalogue",
        "",
        "Source: `items.json`  ",
        string.format("Total items: %d  ", visibleCount),
        string.format("Hidden items filtered: %d", discoveredCount - visibleCount),
        "",
        "Use this catalogue to find the ITEM_ID to paste into `loot_config.lua`.",
        "",
    }

    local longestLeftColumn = 0

    for _, item in ipairs(items) do
        local left = string.format(
            "[%s] %s",
            powerText(item),
            clean(item.name or item.id)
        )
        longestLeftColumn = math.max(longestLeftColumn, #left)
    end

    local lineFormat = string.format("%%-%ds  %%s", longestLeftColumn)
    local codeBlockIsOpen = false

    local function closeCodeBlock()
        if codeBlockIsOpen then
            output[#output + 1] = "```"
            output[#output + 1] = ""
            codeBlockIsOpen = false
        end
    end

    local function openCodeBlock()
        if not codeBlockIsOpen then
            output[#output + 1] = "```text"
            codeBlockIsOpen = true
        end
    end

    local currentPrimary
    local currentFamily
    local currentSubgroup
    local currentDetail

    for _, item in ipairs(items) do
        local class = item._class

        if class.primary ~= currentPrimary then
            closeCodeBlock()
            output[#output + 1] = "---"
            output[#output + 1] = ""
            output[#output + 1] = "## " .. prettyLabel(class.primary)
            output[#output + 1] = ""
            currentPrimary = class.primary
            currentFamily = nil
            currentSubgroup = nil
            currentDetail = nil
        end

        if class.family ~= currentFamily then
            closeCodeBlock()
            output[#output + 1] = "### " .. prettyLabel(class.family)
            output[#output + 1] = ""
            currentFamily = class.family
            currentSubgroup = nil
            currentDetail = nil
        end

        if class.subgroup ~= "" and class.subgroup ~= currentSubgroup then
            closeCodeBlock()
            output[#output + 1] = "#### " .. prettyLabel(class.subgroup)
            output[#output + 1] = ""
            currentSubgroup = class.subgroup
            currentDetail = nil
        end

        if class.detail ~= "" and class.detail ~= currentDetail then
            closeCodeBlock()
            output[#output + 1] = "##### " .. prettyLabel(class.detail)
            output[#output + 1] = ""
            currentDetail = class.detail
        end

        openCodeBlock()

        local left = string.format(
            "[%s] %s",
            powerText(item),
            clean(item.name or item.id)
        )
        output[#output + 1] = string.format(lineFormat, left, item.id)
    end

    closeCodeBlock()
    return table.concat(output, "\n")
end


function Scanner.Scan()
    print("[BagLootFramework] Item catalogue generation started")
    print("[BagLootFramework] Reading: " .. itemsJsonPath)

    local jsonText, readError = readFile(itemsJsonPath)
    if not jsonText then
        print("[BagLootFramework] " .. tostring(readError))
        return false
    end

    local parsed, dataOrError = pcall(Json.Decode, jsonText)
    if not parsed then
        print("[BagLootFramework] Failed to parse items.json")
        print("[BagLootFramework] " .. tostring(dataOrError))
        return false
    end

    local sections = collectSections(dataOrError)
    local discoveredCount = countItems(sections)
    local sortedItems = Sorting.sort_sections(sections)
    local markdown = buildCatalogue(sortedItems, discoveredCount)
    local written, writeError = writeFile(cataloguePath, markdown)

    if not written then
        print("[BagLootFramework] " .. tostring(writeError))
        return false
    end

    print("[BagLootFramework] Catalogue generated successfully")
    print("[BagLootFramework] Wrote: " .. cataloguePath)
    print("[BagLootFramework] Total items: " .. tostring(#sortedItems))
    print("[BagLootFramework] Hidden items filtered: "
        .. tostring(discoveredCount - #sortedItems))

    return true
end


return Scanner
