local addonName, addon = ...

addon.LootTracker = addon.LootTracker or {}
addon.LootTracker.Query = addon.LootTracker.Query or {}

local LT = addon.LootTracker
local Query = LT.Query
local Data = LT.Data

Query.itemCache = Query.itemCache or {}
Query.sourceNameCache = Query.sourceNameCache or {}
Query.bossNameCache = Query.bossNameCache or {}

local function SpecAllowed(specList, specID)
    if not specList or not specID then
        return false
    end

    for _, allowedSpecID in ipairs(specList) do
        if tonumber(allowedSpecID) == tonumber(specID) then
            return true
        end
    end

    return false
end

function Query:IsItemAllowed(itemID, classID, specID, slotID)
    local item = Data.Items[tonumber(itemID)]

    if not item then
        return false
    end

    if slotID ~= nil
        and tonumber(slotID) >= 0
        and tonumber(item.slotId) ~= tonumber(slotID)
    then
        return false
    end

    local classSpecs = item.classes and item.classes[tonumber(classID)]
    if not classSpecs then
        return false
    end

    if tonumber(specID) == 0 then
        return true
    end

    return SpecAllowed(classSpecs, specID)
end

function Query:GetItemSlotID(itemID)
    local item = Data.Items[tonumber(itemID)]
    return item and item.slotId or 14
end

function Query:GetItemInfo(itemID)
    itemID = tonumber(itemID)

    if not itemID then
        return nil
    end

    local cached = self.itemCache[itemID]

    if cached
        and cached.name
        and cached.icon
    then
        return cached
    end

    local name, _, quality, _, _, _, _, _, _, icon

    if C_Item and C_Item.GetItemInfo then
        name, _, quality, _, _, _, _, _, _, icon =
            C_Item.GetItemInfo(itemID)
    elseif GetItemInfo then
        name, _, quality, _, _, _, _, _, _, icon =
            GetItemInfo(itemID)
    end

    if not icon
        and C_Item
        and C_Item.GetItemIconByID
    then
        icon = C_Item.GetItemIconByID(itemID)
    end

    if not name
        and C_Item
        and C_Item.RequestLoadItemDataByID
    then
        C_Item.RequestLoadItemDataByID(itemID)
    end

    cached = cached or {}
    cached.itemID = itemID
    cached.name = name or ("ITEM " .. tostring(itemID))
    cached.icon = icon or 134400
    cached.quality = quality
    cached.slotID = self:GetItemSlotID(itemID)

    self.itemCache[itemID] = cached
    return cached
end


function Query:GetEnglishItemName(itemID, localizedName)
    local EnglishNames = LT.EnglishNames
    if not EnglishNames or not EnglishNames.Get then
        return nil
    end

    return EnglishNames:Get(itemID, localizedName)
end

function Query:GetRussianItemName(itemID, localizedName)
    local RussianNames = LT.RussianNames
    if not RussianNames or not RussianNames.Get then return nil end
    return RussianNames:Get(itemID, localizedName)
end

function Query:GetItemNameParts(itemID, localizedName)
    itemID = tonumber(itemID)
    localizedName = localizedName or ("ITEM " .. tostring(itemID or "?"))

    local language = addon.Localization and addon.Localization:GetLanguage() or "en"
    local englishName = self:GetEnglishItemName(itemID, localizedName)
    local russianName = self:GetRussianItemName(itemID, localizedName)

    local primary, secondary, secondaryCode
    if language == "en" then
        primary = (englishName and englishName ~= "") and englishName or localizedName
        secondary = russianName
        secondaryCode = "RU"
    else
        primary = (russianName and russianName ~= "") and russianName or localizedName
        secondary = englishName
        secondaryCode = "EN"
    end

    if secondary == primary or secondary == localizedName and primary == localizedName then
        secondary = nil
    end
    return primary, secondary, secondaryCode
end

function Query:GetItemDisplayName(itemID, localizedName)
    local primary, secondary = self:GetItemNameParts(itemID, localizedName)
    if secondary and secondary ~= "" and secondary ~= primary then
        return primary .. "  |cff8f9aa8— " .. secondary .. "|r"
    end
    return primary
end

function Query:GetDungeonName(challengeModeID)
    local cacheKey = "D:" .. tostring(challengeModeID)

    if self.sourceNameCache[cacheKey] then
        return self.sourceNameCache[cacheKey]
    end

    local name = nil

    if C_ChallengeMode
        and C_ChallengeMode.GetMapUIInfo
    then
        name = C_ChallengeMode.GetMapUIInfo(challengeModeID)
    end

    name = name or ("KEY " .. tostring(challengeModeID))
    self.sourceNameCache[cacheKey] = name

    return name
end

function Query:GetRaidName(journalInstanceID)
    local cacheKey = "R:" .. tostring(journalInstanceID)

    if self.sourceNameCache[cacheKey] then
        return self.sourceNameCache[cacheKey]
    end

    local name = nil

    if EJ_GetInstanceInfo then
        local ok, value = pcall(EJ_GetInstanceInfo, journalInstanceID)
        if ok then
            name = value
        end
    end

    if not name
        and addon.GameData
        and addon.GameData.FindRaid
    then
        local raid = addon.GameData:FindRaid(journalInstanceID)
        name = raid and raid.name
    end

    name = name or ("RAID " .. tostring(journalInstanceID))
    self.sourceNameCache[cacheKey] = name

    return name
end

function Query:GetBossName(bossID)
    bossID = tonumber(bossID)

    if not bossID then
        return ""
    end

    if self.bossNameCache[bossID] then
        return self.bossNameCache[bossID]
    end

    local name = nil

    if EJ_GetEncounterInfo then
        local ok, value = pcall(EJ_GetEncounterInfo, bossID)
        if ok then
            name = value
        end
    end

    name = name or ("BOSS " .. tostring(bossID))
    self.bossNameCache[bossID] = name

    return name
end

function Query:GetDungeons(classID, specID, slotID)
    local result = {}

    for _, dungeon in ipairs(Data.Dungeons) do
        local items = {}

        for _, itemID in ipairs(dungeon.lootTable or {}) do
            if self:IsItemAllowed(
                itemID,
                classID,
                specID,
                slotID
            ) then
                table.insert(items, itemID)
            end
        end

        if #items > 0 then
            table.insert(result, {
                sourceType = "KEYS",
                sourceID = dungeon.challengeModeId,
                instanceID = dungeon.instanceId,
                name = self:GetDungeonName(dungeon.challengeModeId),
                background = dungeon.bgTexture,
                items = items,
            })
        end
    end

    return result
end

local RAID_DIFFICULTY_IDS = {
    lfr = 17,
    normal = 14,
    heroic = 15,
    mythic = 16,
}

function Query:GetRaidDifficultyID(difficulty)
    return RAID_DIFFICULTY_IDS[
        difficulty
        or "normal"
    ]
    or 14
end

local function GetLootTableForDifficulty(
    boss,
    difficultyID
)
    if not boss or not boss.lootTable then
        return {}
    end

    local loot =
        boss.lootTable[
            difficultyID
        ]

    if loot and #loot > 0 then
        return loot
    end

    -- Safe fallback only if this boss lacks the requested table.
    local preferred = {
        16,
        15,
        14,
        17,
    }

    for _, fallbackID in ipairs(
        preferred
    ) do
        local fallback =
            boss.lootTable[
                fallbackID
            ]

        if fallback
            and #fallback > 0
        then
            return fallback
        end
    end

    return {}
end

function Query:GetRaids(
    classID,
    specID,
    slotID,
    difficulty
)
    local result = {}

    local difficultyID =
        self:GetRaidDifficultyID(
            difficulty
        )

    for _, raid in ipairs(Data.Raids) do
        local bosses = {}

        for _, boss in ipairs(raid.bossList or {}) do
            local items = {}
            local seen = {}

            for _, itemID in ipairs(
                GetLootTableForDifficulty(
                    boss,
                    difficultyID
                )
            ) do
                if not seen[itemID]
                    and self:IsItemAllowed(
                        itemID,
                        classID,
                        specID,
                        slotID
                    )
                then
                    seen[itemID] = true
                    table.insert(items, itemID)
                end
            end

            if #items > 0 then
                table.insert(bosses, {
                    bossID = boss.bossId,
                    name = self:GetBossName(boss.bossId),
                    items = items,
                })
            end
        end

        if #bosses > 0 then
            table.insert(result, {
                sourceType = "RAID",
                sourceID = raid.journalInstanceId,
                instanceID = raid.instanceId,
                name = self:GetRaidName(raid.journalInstanceId),
                bosses = bosses,
            })
        end
    end

    return result
end

function Query:Initialize()
    if self.EventFrame then
        return
    end

    local frame = CreateFrame("Frame")
    self.EventFrame = frame

    frame:RegisterEvent("GET_ITEM_INFO_RECEIVED")

    frame:SetScript("OnEvent", function(_, _, itemID, success)
        if not success then
            return
        end

        Query.itemCache[tonumber(itemID)] = nil

        if addon.UI
            and addon.UI.MainFrame
            and addon.UI.MainFrame:IsShown()
            and (addon.UI.ActiveSection == "loot" or addon.UI.ActiveSection == "myloot")
            and addon.UI.ScheduleLootTrackerRefresh
        then
            addon.UI:ScheduleLootTrackerRefresh()
        end
    end)
end
