require "API/DST_API"
require "API/DST_Helpers"

local DST = rawget(_G, "DST") or {}
local ST  = DST and DST.SkillTooltips
if not ST then return end

local KEY         = "Foraging"           -- UI skill display name
local FORAGE_PERK = "PlantScavenging"    -- internal vanilla perk id

local AMMO_CATEGORY_KEY = "Ammunition"

local greyedout = {
    gap = true,
    color = ST.COLORS.greyedout,
} 

-----------------------------------------------------------------
-- Mod options
-----------------------------------------------------------------
local function _isLootTableEnabled()
    -- Safe default: OFF (no spoilers)
    return DST and DST.Options and DST.Options.isForagingLootEnabled() == true
end
local function _isSpecialLootEnabled()
    -- Safe default: OFF (no spoilers)
    return DST and DST.Options and DST.Options.isForagingRareLootEnabled() == true
end

-----------------------------------------------------------------
-- Cache structure
-----------------------------------------------------------------
local _forageCache = {
    builtTick = 0,
    byLevel = {},           -- [lvl] = { core = {...}, focus = {...}, loot = {...} }
    _catsDoneGlobal = {},   -- tracks categories already advertised for "Can find more"
}

local function _ensureLevelBuckets(cacheTbl, lvl)
    if not cacheTbl[lvl] then
        cacheTbl[lvl] = { core = {}, focus = {}, loot = {}, rare = {} }
    else
        -- make sure all 3 exist even if partially built
        cacheTbl[lvl].core = cacheTbl[lvl].core or {}
        cacheTbl[lvl].focus = cacheTbl[lvl].focus or {}
        cacheTbl[lvl].loot = cacheTbl[lvl].loot or {}
        cacheTbl[lvl].rare = cacheTbl[lvl].rare or {}
    end
    return cacheTbl[lvl]
end

-- Append unique strings from source -> target (no duplicates)
local function _appendUnique(target, source)
    if not target or not source then return end

    -- Build a quick lookup of what target already has
    local seen = {}
    for i = 1, #target do
        seen[target[i]] = true
    end

    for i = 1, #source do
        local v = source[i]
        if v and v ~= "" and not seen[v] then
            table.insert(target, v)
            seen[v] = true
        end
    end
end

-- True if table has at least one key/value
local function _hasAny(t)
    if not t then return false end
    for _ in pairs(t) do
        return true
    end
    return false
end

-----------------------------------------------------------------
-- Build loot table sections (normal + rare)
--
-- Returns:
--   lootByLevel[lvl]      : cumulative non-hidden items
--   rareLootByLevel[lvl]  : cumulative hidden/special items
--
-- We only split by categoryHidden; both are strictly
-- PlantScavenging loot from forageSystem.itemDefs.
-----------------------------------------------------------------
local function _buildLootSection(fs)
    local lootByLevel       = {}
    local rareLootByLevel   = {}

    local perLevelPerCat        = {}   -- [lvl][cat] = { names... } (normal)
    local perLevelPerCatRare    = {}   -- [lvl][cat] = { names... } (hidden)
    local allLevelsNormal       = {}
    local allLevelsRare         = {}

    -------------------------------------------------
    -- 1) Classify items into normal vs rare buckets
    -------------------------------------------------
    for _, itemDef in pairs(fs.itemDefs or {}) do
        if itemDef
            and itemDef.skill and itemDef.skill > 0-- ~= nil
            and itemDef.perks and itemDef.perks[1]
            and itemDef.categories and itemDef.categories[1]
        then
            local perkKey = tostring(itemDef.perks[1])
            if perkKey == FORAGE_PERK then
                local lvl = tonumber(itemDef.skill) or 0
                local cat = tostring(itemDef.categories[1])

                if lvl < 1 then
                    lvl = 1 -- treat 0 as available from level 1
                end
                if lvl > 0 then
                    local catDef   = fs.catDefs and fs.catDefs[cat] or nil
                    local isHidden = (catDef and catDef.categoryHidden) and true or false

                    local prettyName = (getItemNameFromFullType and getItemNameFromFullType(itemDef.type))
                                     or itemDef.type
                    if prettyName and prettyName ~= "" then
                        if isHidden then
                            perLevelPerCatRare[lvl] = perLevelPerCatRare[lvl] or {}
                            perLevelPerCatRare[lvl][cat] = perLevelPerCatRare[lvl][cat] or {}
                            local list = perLevelPerCatRare[lvl][cat]

                            local exists = false
                            for i = 1, #list do
                                if list[i] == prettyName then
                                    exists = true
                                    break
                                end
                            end
                            if not exists then
                                table.insert(list, prettyName)
                            end
                            allLevelsRare[lvl] = true
                        else
                            perLevelPerCat[lvl] = perLevelPerCat[lvl] or {}
                            perLevelPerCat[lvl][cat] = perLevelPerCat[lvl][cat] or {}
                            local list = perLevelPerCat[lvl][cat]

                            local exists = false
                            for i = 1, #list do
                                if list[i] == prettyName then
                                    exists = true
                                    break
                                end
                            end
                            if not exists then
                                table.insert(list, prettyName)
                            end
                            allLevelsNormal[lvl] = true
                        end
                    end
                end
            end
        end
    end

    -------------------------------------------------
    -- 2) Build cumulative normal loot per level
    -------------------------------------------------
    local accumByCat = {} -- [cat] = { names... }

    for lvl = 1, 10 do
        if allLevelsNormal[lvl] or _hasAny(accumByCat) then
            local levelTbl = perLevelPerCat[lvl]
            if levelTbl then
                for cat, newItems in pairs(levelTbl) do
                    local acc = accumByCat[cat]
                    if not acc then
                        acc = {}
                        accumByCat[cat] = acc
                    end

                    local seen = {}
                    for i = 1, #acc do
                        seen[acc[i]] = true
                    end
                    for i = 1, #newItems do
                        local name = newItems[i]
                        if name and not seen[name] then
                            table.insert(acc, name)
                            seen[name] = true
                        end
                    end
                end
            end

            if _hasAny(accumByCat) then
                lootByLevel[lvl] = {}

                for cat, acc in pairs(accumByCat) do
                    if #acc > 0 then
                        local line = ""
                        for i = 1, #acc do
                            if i > 1 then
                                line = line .. ", "
                            end
                            line = line .. acc[i]
                        end
                        table.insert(lootByLevel[lvl], line)
                    end
                end
            end
        end
    end

    -------------------------------------------------
    -- 3) Build cumulative rare loot per level
    --    (only used if option enabled)
    -------------------------------------------------
    local accumRareByCat = {}

    for lvl = 1, 10 do
        if allLevelsRare[lvl] or _hasAny(accumRareByCat) then
            local levelTbl = perLevelPerCatRare[lvl]
            if levelTbl then
                for cat, newItems in pairs(levelTbl) do
                    local acc = accumRareByCat[cat]
                    if not acc then
                        acc = {}
                        accumRareByCat[cat] = acc
                    end

                    local seen = {}
                    for i = 1, #acc do
                        seen[acc[i]] = true
                    end
                    for i = 1, #newItems do
                        local name = newItems[i]
                        if name and not seen[name] then
                            table.insert(acc, name)
                            seen[name] = true
                        end
                    end
                end
            end

            if _hasAny(accumRareByCat) then
                rareLootByLevel[lvl] = {}

                for cat, acc in pairs(accumRareByCat) do
                    if #acc > 0 then
                        if cat == AMMO_CATEGORY_KEY then
                            -- Collapse ammo to a single line
                            local label = getTextOrNull("IGUI_SearchMode_Categories_" .. AMMO_CATEGORY_KEY)
                                         or "Ammunition"
                            table.insert(rareLootByLevel[lvl], label)
                        else
                            local line = ""
                            for i = 1, #acc do
                                if i > 1 then
                                    line = line .. ", "
                                end
                                line = line .. acc[i]
                            end
                            table.insert(rareLootByLevel[lvl], line)
                        end
                    end
                end
            end
        end
    end

    return lootByLevel, rareLootByLevel
end

-----------------------------------------------------------------
-- _rebuildForageCache(fs)
--
-- Populates _forageCache.byLevel[lvl].core / focus / loot
-----------------------------------------------------------------
local function _rebuildForageCache(fs)
    if not fs then return end

    local newByLevel = {}

    -------------------------------------------------
    -- Precompute focus unlocks: catDefs
    -------------------------------------------------
    local focusUnlocksByLevel = {}
    for _, catDef in pairs(fs.catDefs or {}) do
        if (not catDef.categoryHidden)
            and type(catDef.identifyCategoryLevel) == "number"
            and catDef.identifyCategoryLevel > 0
        then
            local unlockLvl = catDef.identifyCategoryLevel
            focusUnlocksByLevel[unlockLvl] = focusUnlocksByLevel[unlockLvl] or {}

            local prettyCat = getTextOrNull("IGUI_SearchMode_Categories_" .. tostring(catDef.name))
                            or tostring(catDef.name)
            if prettyCat and prettyCat ~= "" then
                table.insert(focusUnlocksByLevel[unlockLvl], prettyCat)
            end
        end
    end

    -------------------------------------------------
    -- Precompute rare / hidden loot pools by level
    --
    -- Hidden categories that:
    --  - belong to PlantScavenging
    --  - have an identifyCategoryLevel
    -- are treated as "rare pools".
    -------------------------------------------------
    local rarePoolsByLevel = {}
    for catName, catDef in pairs(fs.catDefs or {}) do
        if catDef
            and catDef.categoryHidden
            and catDef.identifyCategoryPerk == FORAGE_PERK
            and type(catDef.identifyCategoryLevel) == "number"
            and catDef.identifyCategoryLevel > 0
        then
            local lvl = catDef.identifyCategoryLevel
            rarePoolsByLevel[lvl] = rarePoolsByLevel[lvl] or {}

            local labelKey = "IGUI_SearchMode_Categories_" .. tostring(catDef.name or catName)
            local label = getTextOrNull(labelKey) or tostring(catDef.name or catName)

            if label and label ~= "" then
                table.insert(rarePoolsByLevel[lvl], label)
            end
        end
    end

    -------------------------------------------------
    -- Precompute loot pool lines
    -------------------------------------------------
    local lootByLevel, rareLootByLevel = _buildLootSection(fs)

    -------------------------------------------------
    -- Build each level’s sections (per-level gains)
    -------------------------------------------------
    for lvl = 1, 10 do
        local buckets = _ensureLevelBuckets(newByLevel, lvl)

        -------------------------------------------------
        -- CORE EFFECTS (non-accumulating: snapshot at lvl)
        -------------------------------------------------
        local radiusBonus = 0
        if fs.levelBonus and type(fs.levelBonus) == "number" then
            radiusBonus = lvl * fs.levelBonus
        end
        table.insert(buckets.core,
            "+" .. tostring(radiusBonus) .. " " .. getText("IGUI_SearchMode_Vision_Effect_Radius")
        )

        table.insert(buckets.core,
            getText("IGUI_DST_Better") .. " " .. getText("IGUI_SearchMode_Tip_SearchFocus_Title")
        )

        if lvl >= 3 and lvl <= 8 then
            table.insert(buckets.core,
                getText("IGUI_DST_Foraging_val_precision")
            )
        end

        if lvl >= 9 then
            table.insert(buckets.core,
                getText("IGUI_DST_Foraging_val_precision_max")
            )
        end

        -------------------------------------------------
        -- SEARCH FOCUS [UNLOCKS] (delta at this level)
        -------------------------------------------------
        local cats = focusUnlocksByLevel[lvl]
        if cats and #cats > 0 then
            for _, catName in ipairs(cats) do
                table.insert(buckets.focus, ST.getText(catName))
            end
        end

        -------------------------------------------------
        -- FORAGE POOL (delta at this level)
        -------------------------------------------------
        local lootLines = lootByLevel[lvl]
        if lootLines and #lootLines > 0 then
            for _, lootLine in ipairs(lootLines) do
                table.insert(buckets.loot, lootLine)
            end
        end

        -------------------------------------------------
        -- RARE LOOT [UNLOCKS] (delta at this level)
        -------------------------------------------------
        local rareLines = rareLootByLevel[lvl]
        if rareLines and #rareLines > 0 then
            for _, rareLine in ipairs(rareLines) do
                table.insert(buckets.rare, rareLine)
            end
        end
    end

    -------------------------------------------------
    -- Accumulate unlock-style sections:
    --  lvl N shows everything from 1..N
    --  (core stays level-specific)
    -------------------------------------------------
    for lvl = 2, 10 do
        local cur  = newByLevel[lvl]
        local prev = newByLevel[lvl - 1]
        if cur and prev then
            -- _appendUnique(cur.rare,  prev.rare)
            _appendUnique(cur.focus, prev.focus)
        end
    end

    _forageCache.byLevel         = newByLevel
    _forageCache.builtTick       = (getTimestampMs and getTimestampMs())
                                or (getTimeInMillis and getTimeInMillis())
                                or (_forageCache.builtTick + 1)
end

-----------------------------------------------------------------
-- Events to populate / refresh cache
-----------------------------------------------------------------
local function _onAddForageDefs(fs)
    _rebuildForageCache(fs)
end

local function _refreshForageCache()
    if rawget(_G, "forageSystem") then
        _rebuildForageCache(rawget(_G, "forageSystem"))
    end
end

if Events and Events.onAddForageDefs and Events.onAddForageDefs.Add then
    Events.onAddForageDefs.Add(_onAddForageDefs)
end
if Events and Events.EveryHours and Events.EveryHours.Add then
    Events.EveryHours.Add(_refreshForageCache)
end

-----------------------------------------------------------------
-- Custom event: allow external callers (ModOptions) to force
-- a rebuild of the foraging tooltip definitions.
-----------------------------------------------------------------
local function _onDSTForagingRebuild()
    local fs = rawget(_G, "forageSystem")
    if fs then
        _rebuildForageCache(fs)
    else
        -- If somehow called too early, clear so next access rebuilds.
        _forageCache.byLevel         = {}
        _forageCache.builtTick       = 0
    end
end
Events.DST_ForagingRebuild.Add(_onDSTForagingRebuild)

-----------------------------------------------------------------
-- DST contributor hook
--
-- Emits:
--   Core Effects:
--      ...
--   Search Focus [Unlocks]:
--      ...
--   Forage Pool:
--      ...
--
-- We assume ctx.addHeader is available (DST updated version).
-- If ctx.addHeader is missing, we just inline the lines without headers.
-----------------------------------------------------------------
ST.addContributor(KEY, function(ctx)
    local lvl = ctx.getLevel() or 0
    if lvl < 1 then lvl = 1 end
    if lvl > 10 then lvl = 10 end

    local bucket = _forageCache.byLevel[lvl]

    -- lazy rebuild safety net if cache is empty
    if (not bucket) and rawget(_G, "forageSystem") then
        _rebuildForageCache(rawget(_G, "forageSystem"))
        bucket = _forageCache.byLevel[lvl]
    end

    if not bucket then
        -- total fallback if literally nothing built yet
        local fs = rawget(_G, "forageSystem")
        local radiusBonus = (fs and fs.levelBonus and (lvl * fs.levelBonus)) or 0
        ctx.add("+" .. tostring(radiusBonus) .. " " .. getText("IGUI_SearchMode_Vision_Effect_Radius"))
        ctx.add(getText("IGUI_DST_Better") .. " " .. getText("IGUI_SearchMode_Tip_SearchFocus_Title"))
        return
    end

    -- CORE EFFECTS
    if bucket.core and #bucket.core > 0 then
        ctx.addHeader(ST.getText("IGUI_DST_Core_hdr"))
        ctx.addMany(bucket.core)
    end

    -- SEARCH FOCUS [UNLOCKS]
    if bucket.focus and #bucket.focus > 0 then
        ctx.addHeader(ST.getText("IGUI_DST_Foraging_hdr_SearchFocus"))
        ctx.addMany(bucket.focus)
    end

    local showLoot = _isLootTableEnabled()
    local showRare = _isSpecialLootEnabled()

    -- FORAGE POOL
    if bucket.loot and #bucket.loot > 0 then
        if showLoot then
            ctx.addHeader(ST.getText("IGUI_DST_Foraging_hdr_LootTable"))
            ctx.addMany(bucket.loot)
        elseif not showRare then
            ctx.add(ST.getText("IGUI_DST_Foraging_val_showloot"), greyedout)
        end
    end

    -- RARE LOOT
    if bucket.rare and #bucket.rare > 0 then
        if showRare then
            ctx.addHeader(ST.getText("IGUI_DST_Foraging_hdr_Rarities"))
            ctx.addMany(bucket.rare)
        elseif showLoot then
            ctx.add(ST.getText("IGUI_DST_Foraging_val_showrareloot"), greyedout)
        end
    end
end)

-----------------------------------------------------------------
-- Existing recipe / unlock helpers stay as-is
-----------------------------------------------------------------
ST.addAutoLearnRecipes(KEY)
ST.addUnlockCraftRecipes(KEY)
ST.addUnlockBuildRecipes(KEY)
