local _, ns = ...

------------------------------------------------------------------------
-- EJ API Compatibility Layer
-- Blizzard moves functions between global EJ_* and C_EncounterJournal.*
------------------------------------------------------------------------
local CEJ = C_EncounterJournal or {}

local function EJ_Call(cejName, globalName, ...)
    if CEJ[cejName] then
        return CEJ[cejName](...)
    elseif _G[globalName] then
        return _G[globalName](...)
    end
    return nil
end

local function EJ_GetCurrentTierCompat()
    return EJ_Call("GetCurrentTier", "EJ_GetCurrentTier")
end
local function EJ_SelectTierCompat(tier)
    return EJ_Call("SelectTier", "EJ_SelectTier", tier)
end
local function EJ_GetNumTiersCompat()
    return EJ_Call("GetNumTiers", "EJ_GetNumTiers") or 0
end
local function EJ_GetInstanceByIndexCompat(index, isRaid)
    return EJ_Call("GetInstanceByIndex", "EJ_GetInstanceByIndex", index, isRaid)
end
local function EJ_SelectInstanceCompat(instanceID)
    return EJ_Call("SelectInstance", "EJ_SelectInstance", instanceID)
end
local function EJ_GetEncounterInfoByIndexCompat(index)
    return EJ_Call("GetEncounterInfoByIndex", "EJ_GetEncounterInfoByIndex", index)
end
local function EJ_SelectEncounterCompat(encounterID)
    return EJ_Call("SelectEncounter", "EJ_SelectEncounter", encounterID)
end
local function EJ_SetDifficultyCompat(diffID)
    return EJ_Call("SetDifficulty", "EJ_SetDifficulty", diffID)
end
local function EJ_GetDifficultyCompat()
    return EJ_Call("GetDifficulty", "EJ_GetDifficulty")
end
local function EJ_SetLootFilterCompat(classID, specID)
    return EJ_Call("SetLootFilter", "EJ_SetLootFilter", classID, specID)
end
local function EJ_SetSlotFilterCompat(slotFilter)
    return EJ_Call("SetSlotFilter", "EJ_SetSlotFilter", slotFilter)
end
local function EJ_GetNumLootCompat()
    return EJ_Call("GetNumLoot", "EJ_GetNumLoot") or 0
end
local function EJ_GetSlotFilterCompat()
    return EJ_Call("GetSlotFilter", "EJ_GetSlotFilter")
end
local function EJ_GetLootFilterCompat()
    if CEJ.GetLootFilter then
        return CEJ.GetLootFilter()
    elseif _G.EJ_GetLootFilter then
        return _G.EJ_GetLootFilter()
    end
    return nil, nil
end

local function EJ_GetLootInfoByIndexCompat(index)
    if CEJ.GetLootInfoByIndex then
        return CEJ.GetLootInfoByIndex(index)
    end
    if _G.EJ_GetLootInfoByIndex then
        local name, icon, slot, armorType, itemID, itemLink = _G.EJ_GetLootInfoByIndex(index)
        if name then
            return { name = name, icon = icon, itemID = itemID, link = itemLink }
        end
    end
    return nil
end

------------------------------------------------------------------------
-- Item Stats Compatibility
------------------------------------------------------------------------
local function GetItemStatsCompat(itemLink)
    if C_Item and C_Item.GetItemStats then
        return C_Item.GetItemStats(itemLink)
    elseif _G.GetItemStats then
        local stats = {}
        _G.GetItemStats(itemLink, stats)
        if next(stats) then return stats end
        return nil
    end
    return nil
end

------------------------------------------------------------------------
-- Constants
------------------------------------------------------------------------
local EISFT = Enum.ItemSlotFilterType

ns.LOOT_DIFF_COLORS = {
    GREEN  = { r = 0.12, g = 0.75, b = 0.12 },
    BLUE   = { r = 0.00, g = 0.44, b = 0.87 },
    PURPLE = { r = 0.64, g = 0.21, b = 0.93 },
}

ns.LOOT_DIFFICULTIES = {
    DUNGEON = {
        { id = 1,  name = "N",   fullName = "Normal",     color = "GREEN"  },
        { id = 2,  name = "H",   fullName = "Heroic",     color = "BLUE"   },
        { id = 23, name = "M",   fullName = "Mythic",     color = "PURPLE" },
    },
    RAID = {
        { id = 17, name = "LFR", fullName = "LFR",        color = "GREEN",  colOffset = 1 },
        { id = 14, name = "N",   fullName = "Normal",     color = "BLUE",   colOffset = 2 },
        { id = 15, name = "H",   fullName = "Heroic",     color = "PURPLE", colOffset = 3 },
        { id = 16, name = "M",   fullName = "Mythic",     color = "PURPLE", colOffset = 4 },
    },
    WORLD_BOSS = {
        { id = 0,  name = "WB",  fullName = "World Boss", color = "PURPLE", colOffset = 3 },
    },
}

ns.LOOT_SLOT_FILTERS = {
    { name = "Head",      filter = EISFT.Head      },
    { name = "Neck",      filter = EISFT.Neck      },
    { name = "Shoulder",  filter = EISFT.Shoulder  },
    { name = "Back",      filter = EISFT.Cloak     },
    { name = "Chest",     filter = EISFT.Chest     },
    { name = "Wrist",     filter = EISFT.Wrist     },
    { name = "Hands",     filter = EISFT.Hand      },
    { name = "Waist",     filter = EISFT.Waist     },
    { name = "Legs",      filter = EISFT.Legs      },
    { name = "Feet",      filter = EISFT.Feet      },
    { name = "Finger",    filter = EISFT.Finger    },
    { name = "Trinket",   filter = EISFT.Trinket   },
    { name = "Main Hand", filter = EISFT.MainHand  },
    { name = "Off Hand",  filter = EISFT.OffHand   },
}

ns.LOOT_SECONDARY_STATS = {
    { key = "ITEM_MOD_CRIT_RATING_SHORT",   short = "Crit",    name = "Critical Strike" },
    { key = "ITEM_MOD_HASTE_RATING_SHORT",  short = "Haste",   name = "Haste" },
    { key = "ITEM_MOD_MASTERY_RATING_SHORT", short = "Mastery", name = "Mastery" },
    { key = "ITEM_MOD_VERSATILITY",          short = "Vers",    name = "Versatility" },
}

------------------------------------------------------------------------
-- Caches
------------------------------------------------------------------------
local lootBrowserCache = {}
local instanceCache = nil
ns.isLootScanning = false

------------------------------------------------------------------------
-- Navigation State
------------------------------------------------------------------------
ns.lootBrowserState = {
    selectedSpecIndex = nil,
    selectedSpecID    = nil,
    selectedSlot      = EISFT.Head,
    selectedSlotName  = "Head",
    selectedStats     = {},
}

------------------------------------------------------------------------
-- EJ Interference Suppression (ref-counted)
------------------------------------------------------------------------
local ejOriginalOnEvent = nil
local ejSuppressCount = 0
local ejSavedDifficulty = nil
local ejSavedSlotFilter = nil
local ejSavedLootClassID = nil
local ejSavedLootSpecID = nil

local function SuppressEJ()
    ejSuppressCount = ejSuppressCount + 1
    if ejSuppressCount > 1 then return end

    if EncounterJournal then
        ejSavedDifficulty = EJ_GetDifficultyCompat()
        ejSavedSlotFilter = EJ_GetSlotFilterCompat()
        ejSavedLootClassID, ejSavedLootSpecID = EJ_GetLootFilterCompat()

        ejOriginalOnEvent = EncounterJournal:GetScript("OnEvent")
        EncounterJournal:SetScript("OnEvent", nil)
        EncounterJournal:UnregisterEvent("EJ_LOOT_DATA_RECIEVED")
        EncounterJournal:UnregisterEvent("EJ_DIFFICULTY_UPDATE")
        EncounterJournal:UnregisterEvent("UNIT_LEVEL")
    end
end

local function UnsuppressEJ()
    ejSuppressCount = ejSuppressCount - 1
    if ejSuppressCount > 0 then return end
    ejSuppressCount = 0

    if EncounterJournal then
        if ejSavedDifficulty then
            EJ_SetDifficultyCompat(ejSavedDifficulty)
            ejSavedDifficulty = nil
        end
        if ejSavedSlotFilter then
            EJ_SetSlotFilterCompat(ejSavedSlotFilter)
            ejSavedSlotFilter = nil
        end
        if ejSavedLootClassID and ejSavedLootSpecID then
            EJ_SetLootFilterCompat(ejSavedLootClassID, ejSavedLootSpecID)
            ejSavedLootClassID = nil
            ejSavedLootSpecID = nil
        end

        if ejOriginalOnEvent then
            EncounterJournal:SetScript("OnEvent", ejOriginalOnEvent)
            ejOriginalOnEvent = nil
        end
        EncounterJournal:RegisterEvent("EJ_LOOT_DATA_RECIEVED")
        EncounterJournal:RegisterEvent("EJ_DIFFICULTY_UPDATE")
        EncounterJournal:RegisterEvent("UNIT_LEVEL")
    end
end

------------------------------------------------------------------------
-- Seasonal Dungeon Allowlist (Midnight Season 1)
------------------------------------------------------------------------
local SEASONAL_DUNGEON_IDS = {
    [1300] = true,  -- Magisters' Terrace
    [1315] = true,  -- Maisara Caverns
    [1316] = true,  -- Nexus-Point Xenas
    [1299] = true,  -- Windrunner Spire
    [1201] = true,  -- Algeth'ar Academy
    [278]  = true,  -- Pit of Saron
    [945]  = true,  -- Seat of the Triumvirate
    [476]  = true,  -- Skyreach
}

local WORLD_BOSS_INSTANCE_ID = 1312

------------------------------------------------------------------------
-- Instance + Boss Discovery (cached per session)
------------------------------------------------------------------------
local function BuildInstanceCache()
    if instanceCache then return instanceCache end

    SuppressEJ()

    local ok, result = pcall(function()
        local instances = { dungeons = {}, raids = {}, worldBosses = {} }

        local currentTier = EJ_GetCurrentTierCompat()
        if not currentTier then return nil end

        local numTiers = EJ_GetNumTiersCompat()
        local foundDungeons = {}
        for tier = numTiers, 1, -1 do
            EJ_SelectTierCompat(tier)
            local index = 1
            while true do
                local instanceID, name = EJ_GetInstanceByIndexCompat(index, false)
                if not instanceID then break end
                if SEASONAL_DUNGEON_IDS[instanceID] and not foundDungeons[instanceID] then
                    foundDungeons[instanceID] = true
                    EJ_SelectInstanceCompat(instanceID)
                    local bosses = {}
                    local bi = 1
                    while true do
                        local bossName, _, bossID = EJ_GetEncounterInfoByIndexCompat(bi)
                        if not bossName then break end
                        table.insert(bosses, { name = bossName, encounterID = bossID })
                        bi = bi + 1
                    end

                    local hasNormal = false
                    if #bosses > 0 then
                        EJ_SelectEncounterCompat(bosses[1].encounterID)
                        EJ_SetDifficultyCompat(1)
                        EJ_SetSlotFilterCompat(EISFT.NoFilter)
                        local numLoot = EJ_GetNumLootCompat()
                        if numLoot and numLoot > 0 then
                            local testInfo = EJ_GetLootInfoByIndexCompat(1)
                            if testInfo and testInfo.link then
                                local _, _, _, itemLevel = GetItemInfo(testInfo.link)
                                if itemLevel then
                                    hasNormal = (itemLevel >= 200)
                                end
                            end
                        end
                    end

                    table.insert(instances.dungeons, {
                        instanceID = instanceID,
                        name = name,
                        bosses = bosses,
                        hasNormal = hasNormal,
                    })
                    EJ_SelectTierCompat(tier)
                end
                index = index + 1
            end
        end

        EJ_SelectTierCompat(currentTier)

        local raidIdx = 1
        while true do
            local instanceID, name = EJ_GetInstanceByIndexCompat(raidIdx, true)
            if not instanceID then break end

            EJ_SelectInstanceCompat(instanceID)
            local bosses = {}
            local bi = 1
            while true do
                local bossName, _, bossID = EJ_GetEncounterInfoByIndexCompat(bi)
                if not bossName then break end
                table.insert(bosses, { name = bossName, encounterID = bossID })
                bi = bi + 1
            end

            if instanceID == WORLD_BOSS_INSTANCE_ID then
                table.insert(instances.worldBosses, {
                    instanceID = instanceID,
                    name = name,
                    bosses = bosses,
                })
            else
                table.insert(instances.raids, {
                    instanceID = instanceID,
                    name = name,
                    bosses = bosses,
                })
            end
            raidIdx = raidIdx + 1
        end

        return instances
    end)

    UnsuppressEJ()

    if not ok then
        print("|cff00ff00YippYapp|r |cffff6060Loot Browser:|r Instance cache error: " .. tostring(result))
        return nil
    end

    instanceCache = result
    return result
end

------------------------------------------------------------------------
-- Loot Scanning
------------------------------------------------------------------------
local function ScanLootForEncounter(instanceID, encounterID, difficultyID, classID, specID, slotFilter)
    local items = {}
    EJ_SelectInstanceCompat(instanceID)
    EJ_SelectEncounterCompat(encounterID)
    EJ_SetDifficultyCompat(difficultyID)

    if classID and specID then
        EJ_SetLootFilterCompat(classID, specID)
    end
    if slotFilter then
        EJ_SetSlotFilterCompat(slotFilter)
    end

    local index = 1
    while true do
        local info = EJ_GetLootInfoByIndexCompat(index)
        if not info or not info.name then break end
        table.insert(items, {
            name     = info.name,
            icon     = info.icon,
            itemID   = info.itemID,
            itemLink = info.link,
        })
        index = index + 1
    end
    return items
end

local function ScanSourceType(results, instances, sourceType, difficulties, classID, specID, slotFilter)
    for _, inst in ipairs(instances) do
        for _, boss in ipairs(inst.bosses) do
            local entry = {
                sourceName  = inst.name,
                sourceType  = sourceType,
                bossName    = boss.name,
                encounterID = boss.encounterID,
                items       = {},
            }

            if sourceType == "worldboss" then
                for _, tryDiff in ipairs({0, 14, 15}) do
                    local items = ScanLootForEncounter(inst.instanceID, boss.encounterID, tryDiff, classID, specID, slotFilter)
                    if #items > 0 then
                        entry.items[0] = items
                        break
                    end
                end
            else
                for _, diff in ipairs(difficulties) do
                    local skip = false
                    if sourceType == "dungeon" and diff.id == 1 and inst.hasNormal == false then
                        skip = true
                    end
                    if not skip then
                        local items = ScanLootForEncounter(inst.instanceID, boss.encounterID, diff.id, classID, specID, slotFilter)
                        if #items > 0 then
                            entry.items[diff.id] = items
                        end
                    end
                end
            end

            local hasItems = false
            for _ in pairs(entry.items) do hasItems = true; break end
            if hasItems then
                table.insert(results, entry)
            end
        end
    end
end

function ns:ScanLootBrowserSlot(specIndex, slotFilter)
    local classID = select(3, UnitClass("player"))
    local specID = select(1, GetSpecializationInfo(specIndex))

    local cacheKey = specID .. "-" .. slotFilter
    if lootBrowserCache[cacheKey] then
        return lootBrowserCache[cacheKey]
    end

    if not instanceCache then
        local built = BuildInstanceCache()
        if not built then
            print("|cff00ff00YippYapp|r |cffff6060Loot Browser:|r Could not load instance data. Try /reload.")
            return {}
        end
    end

    SuppressEJ()
    ns.isLootScanning = true

    local ok, results = pcall(function()
        local r = {}
        ScanSourceType(r, instanceCache.dungeons,    "dungeon",   ns.LOOT_DIFFICULTIES.DUNGEON,    classID, specID, slotFilter)
        ScanSourceType(r, instanceCache.raids,       "raid",      ns.LOOT_DIFFICULTIES.RAID,       classID, specID, slotFilter)
        ScanSourceType(r, instanceCache.worldBosses, "worldboss", ns.LOOT_DIFFICULTIES.WORLD_BOSS, classID, specID, slotFilter)
        return r
    end)

    ns.isLootScanning = false
    UnsuppressEJ()

    if not ok then
        print("|cff00ff00YippYapp|r |cffff6060Loot Browser:|r Scan error: " .. tostring(results))
        return {}
    end

    lootBrowserCache[cacheKey] = results
    return results
end

------------------------------------------------------------------------
-- Secondary Stat Filtering (client-side post-processing)
------------------------------------------------------------------------
function ns:FilterLootByStats(results, statKeys)
    if not statKeys or #statKeys == 0 then return results, 0 end

    local preFilterCount = #results
    local filtered = {}
    for _, entry in ipairs(results) do
        local newEntry = {
            sourceName  = entry.sourceName,
            sourceType  = entry.sourceType,
            bossName    = entry.bossName,
            encounterID = entry.encounterID,
            items       = {},
        }

        for diffID, items in pairs(entry.items) do
            local kept = {}
            for _, item in ipairs(items) do
                if not item.itemLink then
                    table.insert(kept, item)
                else
                    local stats = GetItemStatsCompat(item.itemLink)
                    if not stats then
                        table.insert(kept, item)
                    else
                        local hasAll = true
                        for _, key in ipairs(statKeys) do
                            if not stats[key] then
                                hasAll = false
                                break
                            end
                        end
                        if hasAll then
                            table.insert(kept, item)
                        end
                    end
                end
            end
            if #kept > 0 then
                newEntry.items[diffID] = kept
            end
        end

        local hasItems = false
        for _ in pairs(newEntry.items) do hasItems = true; break end
        if hasItems then
            table.insert(filtered, newEntry)
        end
    end
    return filtered, preFilterCount
end

------------------------------------------------------------------------
-- Cache Invalidation
------------------------------------------------------------------------
function ns:ClearLootBrowserCache()
    wipe(lootBrowserCache)
end

function ns:ClearAllLootBrowserCaches()
    wipe(lootBrowserCache)
    instanceCache = nil
end

------------------------------------------------------------------------
-- Trinket Myth 6/6 Helper
-- Bonus IDs 10390 + 12806 set any season item to Myth 6/6 (289 ilvl).
-- We build a hyperlink with these bonuses for correct tooltips + ilvl.
------------------------------------------------------------------------
local MYTH_BONUS_IDS = "10390:12806"
local trinketMythLinkCache = {}  -- itemID -> { itemLink, ilvl }

function ns:GetTrinketMythInfo(itemID)
    if trinketMythLinkCache[itemID] then
        return trinketMythLinkCache[itemID]
    end

    -- Build item string with Myth 6/6 bonus IDs
    local itemString = "item:" .. itemID .. "::::::::70::::2:" .. MYTH_BONUS_IDS
    local name, link, _, ilvl = GetItemInfo(itemString)
    if name and link then
        local info = { itemLink = link, ilvl = ilvl or 289 }
        trinketMythLinkCache[itemID] = info
        return info
    end

    -- Item not cached yet — request load, return fallback
    C_Item.RequestLoadItemDataByID(itemID)
    return { itemLink = nil, ilvl = 289 }
end

function ns:GetTrinketMaxIlvl(itemID)
    local info = self:GetTrinketMythInfo(itemID)
    return info.ilvl
end

------------------------------------------------------------------------
-- Navigation Functions
------------------------------------------------------------------------
function ns:LootBrowser_DetectSpec()
    local lootSpecID = GetLootSpecialization()
    if lootSpecID == 0 then
        local currentSpec = GetSpecialization()
        if currentSpec then
            ns.lootBrowserState.selectedSpecIndex = currentSpec
            ns.lootBrowserState.selectedSpecID = select(1, GetSpecializationInfo(currentSpec))
        end
    else
        for i = 1, GetNumSpecializations() do
            local specID = GetSpecializationInfo(i)
            if specID == lootSpecID then
                ns.lootBrowserState.selectedSpecIndex = i
                ns.lootBrowserState.selectedSpecID = specID
                break
            end
        end
    end
end

function ns:LootBrowser_SwitchSpec(specIndex)
    if ns.isLootScanning then return end
    local specID = select(1, GetSpecializationInfo(specIndex))
    if not specID then return end
    ns.lootBrowserState.selectedSpecIndex = specIndex
    ns.lootBrowserState.selectedSpecID = specID
    ns:ClearLootBrowserCache()
    if ns.LootBrowser_ShowPage then ns:LootBrowser_ShowPage() end
end

function ns:LootBrowser_SwitchSlot(slotFilter, slotName)
    ns.lootBrowserState.selectedSlot = slotFilter
    ns.lootBrowserState.selectedSlotName = slotName
    if ns.LootBrowser_ShowPage then ns:LootBrowser_ShowPage() end
end

function ns:LootBrowser_ToggleStat(statKey)
    local stats = ns.lootBrowserState.selectedStats
    for i, key in ipairs(stats) do
        if key == statKey then
            table.remove(stats, i)
            ns:LootBrowser_OnStatFilterChanged()
            return
        end
    end
    if #stats >= 2 then return end
    table.insert(stats, statKey)
    ns:LootBrowser_OnStatFilterChanged()
end

function ns:LootBrowser_ClearStats()
    wipe(ns.lootBrowserState.selectedStats)
    ns:LootBrowser_OnStatFilterChanged()
end

function ns:LootBrowser_OnStatFilterChanged()
    if ns.LootBrowser_UpdateStatBar then ns:LootBrowser_UpdateStatBar() end
    if ns.lootBrowserState.selectedSlot and ns.LootBrowser_RefreshDisplay then
        ns:LootBrowser_RefreshDisplay()
    end
end

------------------------------------------------------------------------
-- EJ Event Listener (debounced cache refresh)
------------------------------------------------------------------------
local ejFrame = CreateFrame("Frame")
local lbRefreshPending = false
ejFrame:RegisterEvent("EJ_LOOT_DATA_RECIEVED")
ejFrame:RegisterEvent("EJ_DIFFICULTY_UPDATE")
ejFrame:SetScript("OnEvent", function()
    if not ns.isLootScanning then
        ns:ClearLootBrowserCache()
        if not lbRefreshPending
           and ns.LootBrowserFrame and ns.LootBrowserFrame:IsShown()
           and ns.lootBrowserState.selectedSlot
           and ns.LootBrowser_RefreshDisplay then
            lbRefreshPending = true
            C_Timer.After(0.5, function()
                lbRefreshPending = false
                if ns.LootBrowserFrame and ns.LootBrowserFrame:IsShown()
                   and ns.lootBrowserState.selectedSlot then
                    ns:LootBrowser_RefreshDisplay()
                end
            end)
        end
    end
end)
