require "InspectSystem/Core/Inspect_Stats"
local InspectPlugins   = require "InspectSystem/Core/Inspect_Plugins"
local CategoryRules    = require "InspectSystem/Core/Inspect_CategoryRules"
local CategoryTree     = require "InspectSystem/Core/Inspect_CategoryTree"

local DescFirearm     = require "InspectSystem/Modules/Desc_Firearm"
local DescMelee       = require "InspectSystem/Modules/Desc_Melee"
local DescAmmo        = require "InspectSystem/Modules/Desc_Ammo"
local DescFood        = require "InspectSystem/Modules/Desc_Food"
local DescClothing    = require "InspectSystem/Modules/Desc_Clothing"
local DescLiterature  = require "InspectSystem/Modules/Desc_Literature"
local DescElectronics = require "InspectSystem/Modules/Desc_Electronics"
local DescEquipment   = require "InspectSystem/Modules/Desc_Equipment"
local DescMagazine    = require "InspectSystem/Modules/Desc_Magazine"
local DescWeaponUtil  = require "InspectSystem/Modules/Desc_WeaponUtility"
local DescMaterials   = require "InspectSystem/Modules/Desc_Materials"
local DescMedical     = require "InspectSystem/Modules/Desc_Medical"
local DescFluid       = require "InspectSystem/Modules/Desc_Fluid"
local DescMoveable    = require "InspectSystem/Modules/Desc_Moveable"
local DescJunk        = require "InspectSystem/Modules/Desc_Junk"
local DescFallback    = require "InspectSystem/Modules/Desc_Fallback"
local DescGardening   = require "InspectSystem/Modules/Desc_Gardening"
local DescTool        = require "InspectSystem/Modules/Desc_Tool"
local DescCorpse      = require "InspectSystem/Modules/Desc_Corpse"

InspectDescription = InspectDescription or {}

--========================================================
-- Helpers de construcción de descripcion (builder)
--========================================================
function InspectDescription.newBuilder()
    return {
        lines = {},
    }
end

function InspectDescription.addLine(builder, text)
    if not builder or not builder.lines then return end
    if not text or text == "" then return end

    text = tostring(text)
    text = text:gsub("^%s+", ""):gsub("%s+$", "")

    if text ~= "" then
        table.insert(builder.lines, text)
    end
end

local function safeFormatDescription(fmt, ...)
    local args = { ... }
    local argIndex = 1
    local out = {}
    local len = #fmt
    local i = 1
    local specs = "cdiouxXeEfgGqs"

    while i <= len do
        local c = fmt:sub(i, i)
        if c ~= "%" then
            out[#out + 1] = c
            i = i + 1
        else
            local nextChar = fmt:sub(i + 1, i + 1)
            if nextChar == "" then
                out[#out + 1] = "%"
                i = i + 1
            elseif nextChar == "%" then
                out[#out + 1] = "%"
                i = i + 2
            else
                local j = i + 1
                while j <= len and fmt:sub(j, j):match("[-+ #0%*%.0-9]") do
                    j = j + 1
                end

                local spec = fmt:sub(j, j)
                if spec ~= "" and specs:find(spec, 1, true) then
                    local token = fmt:sub(i, j)
                    local value = args[argIndex]
                    argIndex = argIndex + 1

                    if value == nil then value = "" end
                    if fmt:sub(j + 1, j + 2) == "%%" and type(value) == "string" and value:sub(-1) == "%" then
                        value = value:sub(1, -2)
                    end

                    local ok, formatted = pcall(string.format, token, value)
                    out[#out + 1] = (ok and formatted) or tostring(value)
                    i = j + 1
                else
                    out[#out + 1] = "%"
                    i = i + 1
                end
            end
        end
    end

    return table.concat(out)
end

function InspectDescription.addFormatted(builder, key, fallback, ...)
    local fmt = InspectUI_tr(key, fallback or "")
    if fmt and fmt ~= "" then
        local line = safeFormatDescription(tostring(fmt), ...)
        if line and line ~= "" then
            InspectDescription.addLine(builder, line)
        end
    end
end

function InspectDescription.finish(builder)
    if not builder or not builder.lines then
        return ""
    end

    local text = table.concat(builder.lines, "\n")
    text = text:gsub("\r\n", "\n")
    text = text:gsub("\n\n+", "\n\n")

    return text
end

local function normalizeDescription(text)
    if not text or text == "" then return text end
    text = tostring(text)
    text = text:gsub("\r\n", "\n")
    text = text:gsub("^%s+", ""):gsub("%s+$", "")
    return text
end

-- Quick guard to keep fluid containers out of generic container branches
local function DetectFluidContainer(item, statMap)
    if not item then return nil, nil end

    local function safeLower(str)
        if not str then return "" end
        return string.lower(tostring(str))
    end

    local dc   = safeLower(item.getDisplayCategory and item:getDisplayCategory())
    local cat  = safeLower(item.getCategory and item:getCategory())
    local name = safeLower(item.getDisplayName and item:getDisplayName())

    local hasStat = statMap and (statMap["fluidCapacity"] ~= nil or statMap["rainFactor"] ~= nil)
    local isFluid = hasStat
        or (InspectUI_isFluidContainer and InspectUI_isFluidContainer(item))
        or dc:find("watercontainer", 1, true) ~= nil
        or dc:find("water", 1, true) ~= nil
        or dc:find("fuel", 1, true) ~= nil
        or cat:find("water", 1, true) ~= nil
        or cat:find("fuel", 1, true) ~= nil

    if not isFluid then return nil, nil end
    if instanceof and instanceof(item, "Food") then return nil, nil end

    local child = nil
    if name:find("water", 1, true) then
        child = "Water"
    elseif name:find("gas", 1, true) or name:find("petrol", 1, true) or name:find("propane", 1, true) then
        child = "Fuel"
    end

    return "FluidContainer", child
end

local function parseCategoryPath(path)
    if not path or path == "" then return nil, nil end
    local slash = string.find(path, "/", 1, true)
    if not slash then
        return path, nil
    end

    local parent = string.sub(path, 1, slash - 1)
    local child = string.sub(path, slash + 1)
    if child == "" then child = nil end
    return parent, child
end

--------------------------------------------------------
-- Resolver categoría: PADRE -> HIJOS -> FALLBACK
--------------------------------------------------------
local function childExtends(base, candidate)
    if not candidate then return false end
    if not base then return true end
    if candidate == base then return false end
    if #candidate <= #base then return false end
    return candidate:sub(1, #base) == base and candidate:sub(#base + 1, #base + 1) == "/"
end

local function resolveCategory(item, statMap)
    local quickParent, quickChild = DetectFluidContainer(item, statMap)
    if quickParent then
        return quickParent, quickChild
    end

    local matchedParent, matchedChild = nil, nil

    for _, rule in ipairs(CategoryTree) do
        if rule and type(rule.match) == "function" then
            local ok, match = pcall(rule.match, item, statMap)
            if ok and match then
                local parent, child = parseCategoryPath(rule.category)
                if parent then
                    if not matchedParent then
                        matchedParent, matchedChild = parent, child
                    elseif parent == matchedParent and childExtends(matchedChild, child) then
                        matchedChild = child
                    end
                end
            end
        end
    end

    if matchedParent then
        return matchedParent, matchedChild
    end

    return "Fallback", nil
end

--========================================================
-- Descripción automática principal
--========================================================
function InspectDescription.build(item, stats)
    if not item then
        return getText("IGUI_Inspect_NoDescription") or "No detailed information."
    end

    local statMap = InspectUI_statsById(stats or {})

    --------------------------------------------------------
    -- Categorías personalizadas definidas por plugins
    --------------------------------------------------------
    local customParent, customChild = CategoryRules.tryMatch(item, statMap)
    if customParent ~= nil then
        -- Override de plugins para Custom/*
        local overrideCustom = InspectPlugins.tryOverride(item, statMap, customParent, customChild)
        if overrideCustom and overrideCustom ~= "" then
            return normalizeDescription(overrideCustom)
        end

        -- Sin módulo dedicado: fallback + postProcess
        local baseDesc = DescFallback.describe(item, statMap, customChild, customParent)
        baseDesc = normalizeDescription(baseDesc or "")
        return InspectPlugins.postProcess(item, statMap, customParent, customChild, baseDesc)
    end

    local parent, child = resolveCategory(item, statMap)

    --------------------------------------------------------
    -- HOOK 1: Plugins pueden sobrescribir la descripción
    --------------------------------------------------------
    local overrideDesc = InspectPlugins.tryOverride(item, statMap, parent, child)
    if overrideDesc and overrideDesc ~= "" then
        return normalizeDescription(overrideDesc)
    end

    local result

    local function childStartsWith(prefix)
        return child and child:find(prefix, 1, true) == 1
    end

    if parent == "Weapons" then
        if childStartsWith("Firearm") then
            result = DescFirearm.describe(item, statMap, child, parent)
        elseif childStartsWith("Melee") then
            result = DescMelee.describe(item, statMap, child, parent)
        elseif child == "Explosive" or child == "WeaponPart" or child == "GardeningWeapon"
            or child == "Household/Writing" or child == "InstrumentWeapon" or child == "Cooking"
            or child == "Household" then
            result = DescWeaponUtil.describe(item, statMap, child, parent)
        end

    elseif parent == "Ammunition" then
        if child == "Magazine" or child == "AmmoBox" or child == "Carton" or child == "LooseRound" or child == "AmmoCarton" then
            result = DescMagazine.describe(item, statMap, child, parent)
        else
            result = DescAmmo.describe(item, statMap, child, parent)
        end
    elseif parent == "Food" then
        result = DescFood.describe(item, statMap, child, parent)
    elseif parent == "Clothing" then
        result = DescClothing.describe(item, statMap, child, parent)
    elseif parent == "Literature" then
        result = DescLiterature.describe(item, statMap, child, parent)
    elseif parent == "Electronics" then
        result = DescElectronics.describe(item, statMap, child, parent)
    elseif parent == "Equipment" then
        result = DescEquipment.describe(item, statMap, child, parent)
    elseif parent == "MaterialsAndMisc" then
        result = DescMaterials.describe(item, statMap, child, parent)
    elseif parent == "Medical" then
        result = DescMedical.describe(item, statMap, child, parent)
    elseif parent == "FluidContainer" then
        result = DescFluid.describe(item, statMap, child, parent)
    elseif parent == "Gardening" then
        result = DescGardening.describe(item, statMap, child, parent)
    elseif parent == "Tool" then
        if child == "Crafted" then
            result = DescMelee.describe(item, statMap, child, parent)
        else
            result = DescTool.describe(item, statMap, child, parent)
        end
    elseif parent == "Moveable" then
        result = DescMoveable.describe(item, statMap, child, parent)
    elseif parent == "Junk" then
        result = DescJunk.describe(item, statMap, child, parent)
    elseif parent == "Corpse" then
        result = DescCorpse.describe(item, statMap, child, parent)
    end

    --------------------------------------------------------
    -- v3.5 Dynamic ScriptItem Stats
    --------------------------------------------------------
    local baseDesc
    if not result or result == "" then
        local dynStats = ItemInspection and ItemInspection.BuildDynamicStats and ItemInspection.BuildDynamicStats(item) or {}
        if dynStats and #dynStats > 0 then
            baseDesc = table.concat(dynStats, " ")
        end

        if not baseDesc or baseDesc == "" then
            baseDesc = DescFallback.describe(item, statMap, child, parent)
        end

        result = baseDesc
    end

    result = normalizeDescription(result or "")

    --------------------------------------------------------
    -- HOOK 2: Post-procesado de plugins (si existe)
    --------------------------------------------------------
    return InspectPlugins.postProcess(item, statMap, parent, child, result)
end

return InspectDescription
