local addonName, addon = ...

addon.GameData = addon.GameData or {}

local GameData = addon.GameData

GameData.characters = {}
GameData.dungeons = {}
GameData.raids = {}
GameData.keys = {}
GameData.currentTierName = nil
GameData.raidExpansions = {}

local function SortByName(a, b)
    return string.lower(a.name or "") < string.lower(b.name or "")
end

local function SafeRealmName()
    local realm = GetRealmName and GetRealmName() or ""
    if realm == nil or realm == "" then
        realm = "UnknownRealm"
    end
    return realm
end

function GameData:GetCurrentCharacterKey()
    local name, realm = UnitFullName("player")

    if not name or name == "" then
        name = UnitName("player")
    end

    if not realm or realm == "" then
        realm = SafeRealmName()
    end

    if not name or name == "" then
        return nil
    end

    return name .. "-" .. realm
end

function GameData:GetCurrentCharacter()
    local key = self:GetCurrentCharacterKey()
    if not key then
        return nil
    end

    return WhatsNextUserData
        and WhatsNextUserData.characters
        and WhatsNextUserData.characters[key]
        or nil
end

function GameData:RefreshCurrentCharacter()
    if not WhatsNextUserData then
        return
    end

    WhatsNextUserData.characters = WhatsNextUserData.characters or {}

    local key = self:GetCurrentCharacterKey()
    if not key then
        return
    end

    local name, realm = UnitFullName("player")

    if not name or name == "" then
        name = UnitName("player")
    end

    if not realm or realm == "" then
        realm = SafeRealmName()
    end

    local localizedClass, classFile, classID = UnitClass("player")

    local character = WhatsNextUserData.characters[key] or {}

    character.key = key
    character.name = name or key
    character.realm = realm or ""
    character.className = localizedClass or ""
    character.classFile = classFile or ""
    character.classID = classID
    character.lastSeen = time()

    character.specs = {}
    character.professions = {}

    --------------------------------------------------------
    -- SPECIALIZATIONS
    --------------------------------------------------------

    if C_SpecializationInfo
        and C_SpecializationInfo.GetNumSpecializationsForClassID
        and C_SpecializationInfo.GetSpecializationInfo
        and classID
    then

        local count =
            C_SpecializationInfo.GetNumSpecializationsForClassID(classID)
            or 0

        local currentSpecIndex =
            C_SpecializationInfo.GetSpecialization
            and C_SpecializationInfo.GetSpecialization()
            or nil

        local currentSpecID = nil

        for index = 1, count do

            local specID, specName, _, icon, role =
                C_SpecializationInfo.GetSpecializationInfo(
                    index,
                    false,
                    false,
                    nil,
                    UnitSex("player"),
                    nil,
                    classID
                )

            if specID and specName then

                table.insert(
                    character.specs,
                    {
                        id = specID,
                        name = specName,
                        icon = icon,
                        role = role,
                    }
                )

                if currentSpecIndex == index then
                    currentSpecID = specID
                end

            end

        end

        character.currentSpecID = currentSpecID

    end

    --------------------------------------------------------
    -- PROFESSIONS
    --------------------------------------------------------

    if GetProfessions and GetProfessionInfo then

        local prof1, prof2, archaeology, fishing, cooking =
            GetProfessions()

        local indices = {
            prof1,
            prof2,
            cooking,
            fishing,
            archaeology,
        }

        local seen = {}

        for _, professionIndex in ipairs(indices) do

            if professionIndex then

                local name,
                    icon,
                    skillLevel,
                    maxSkillLevel,
                    _,
                    _,
                    skillLine,
                    skillModifier,
                    _,
                    _,
                    skillLineName =
                    GetProfessionInfo(professionIndex)

                if name and skillLine then

                    --------------------------------------------------------
                    -- GetProfessionInfo can report the Classic child line
                    -- as the profession's current/default expansion. For
                    -- What's Next we want the current Retail expansion by
                    -- default. Resolve the newest child skill line for this
                    -- base profession from Blizzard's own profession list.
                    --------------------------------------------------------

                    local displaySkillLine = skillLine
                    local displaySkillLineName = skillLineName

                    if C_TradeSkillUI
                        and C_TradeSkillUI.GetAllProfessionTradeSkillLines
                        and C_TradeSkillUI.GetProfessionInfoBySkillLineID
                    then
                        local allLines = C_TradeSkillUI.GetAllProfessionTradeSkillLines() or {}
                        local newestID = nil
                        local newestName = nil

                        for _, childSkillLineID in ipairs(allLines) do
                            local ok, info = pcall(
                                C_TradeSkillUI.GetProfessionInfoBySkillLineID,
                                childSkillLineID
                            )

                            if ok and info then
                                local parentID = info.parentProfessionID
                                    or info.parentProfessionSkillLineID

                                -- skillLine from GetProfessionInfo is the
                                -- base profession ID on Retail. Match every
                                -- expansion child belonging to it and keep
                                -- the newest (highest) Blizzard skill-line ID.
                                if tonumber(parentID) == tonumber(skillLine)
                                    and (not newestID
                                        or tonumber(childSkillLineID) > tonumber(newestID))
                                then
                                    newestID = childSkillLineID
                                    newestName = info.professionName
                                        or info.name
                                        or info.skillLineName
                                end
                            end
                        end

                        if newestID then
                            displaySkillLine = newestID
                            displaySkillLineName = newestName
                                or ("Midnight " .. name)
                        end
                    end

                    if not seen[displaySkillLine] then
                        seen[displaySkillLine] = true

                        table.insert(
                            character.professions,
                            {
                                id = displaySkillLine,
                                baseProfessionID = skillLine,
                                name = name,
                                expansionName = displaySkillLineName
                                    or ("Midnight " .. name),
                                icon = icon,
                                skillLevel = skillLevel,
                                maxSkillLevel = maxSkillLevel,
                                skillModifier = skillModifier,
                            }
                        )
                    end

                end

            end

        end

        table.sort(character.professions, SortByName)

    end

    WhatsNextUserData.characters[key] = character
end

function GameData:GetCharacters()
    local result = {}

    if not WhatsNextUserData or not WhatsNextUserData.characters then
        return result
    end

    for _, character in pairs(WhatsNextUserData.characters) do
        table.insert(result, character)
    end

    table.sort(result, function(a, b)
        local an = (a.name or "") .. "-" .. (a.realm or "")
        local bn = (b.name or "") .. "-" .. (b.realm or "")
        return string.lower(an) < string.lower(bn)
    end)

    return result
end

function GameData:GetCharacter(characterKey)
    if not WhatsNextUserData or not WhatsNextUserData.characters then
        return nil
    end

    return WhatsNextUserData.characters[characterKey]
end

function GameData:GetSpecs(characterKey)
    local character = self:GetCharacter(characterKey)
    return character and character.specs or {}
end

function GameData:GetProfessions(characterKey)
    local character = self:GetCharacter(characterKey)
    return character and character.professions or {}
end

function GameData:RefreshInstances()
    self.dungeons = {}
    self.raids = {}
    self.raidExpansions = {}
    self.currentTierName = nil

    if not EJ_GetNumTiers
        or not EJ_GetTierInfo
        or not EJ_SelectTier
        or not EJ_GetInstanceByIndex
    then
        return
    end

    local numTiers = EJ_GetNumTiers() or 0
    if numTiers <= 0 then
        return
    end

    -- Patch 12.1.0 is Midnight. The newest Encounter Journal tier
    -- is therefore the current expansion tier.
    local tierIndex = numTiers
    local tierName = EJ_GetTierInfo(tierIndex)

    self.currentTierName = tierName or "CURRENT EXPANSION"

    table.insert(
        self.raidExpansions,
        {
            name = self.currentTierName,
            tierIndex = tierIndex,
        }
    )

    EJ_SelectTier(tierIndex)

    for index = 1, 100 do

        local instanceID,
            name,
            _,
            _,
            _,
            _,
            _,
            _,
            _,
            _,
            mapID =
            EJ_GetInstanceByIndex(index, false)

        if not instanceID then
            break
        end

        table.insert(
            self.dungeons,
            {
                id = instanceID,
                name = name or ("Dungeon " .. tostring(instanceID)),
                mapID = mapID,
                type = "DUNGEON",
            }
        )

    end

    for index = 1, 100 do

        local instanceID,
            name,
            _,
            _,
            buttonImage,
            _,
            mapID =
            EJ_GetInstanceByIndex(index, true)

        if not instanceID then
            break
        end

        table.insert(
            self.raids,
            {
                id = instanceID,
                name = name or ("Raid " .. tostring(instanceID)),
                mapID = mapID,
                icon = buttonImage,
                type = "RAID",
                expansionName = self.currentTierName,
            }
        )

    end

    table.sort(self.dungeons, SortByName)
    table.sort(self.raids, SortByName)
end


function GameData:GetRaidExpansions()
    return self.raidExpansions or {}
end

function GameData:GetRaidsForExpansion(expansionName)
    local result = {}

    for _, raid in ipairs(self:GetRaids()) do
        if not expansionName
            or expansionName == ""
            or raid.expansionName == expansionName
        then
            table.insert(result, raid)
        end
    end

    return result
end

function GameData:GetSpec(characterKey, specID)
    for _, spec in ipairs(self:GetSpecs(characterKey)) do
        if tonumber(spec.id) == tonumber(specID) then
            return spec
        end
    end

    return nil
end

function GameData:GetProfession(characterKey, professionID)
    for _, profession in ipairs(self:GetProfessions(characterKey)) do
        if tonumber(profession.id) == tonumber(professionID) then
            return profession
        end
    end

    return nil
end

function GameData:RefreshKeys()
    self.keys = {}

    if not C_ChallengeMode
        or not C_ChallengeMode.GetMapTable
        or not C_ChallengeMode.GetMapUIInfo
    then
        return
    end

    local mapIDs = C_ChallengeMode.GetMapTable() or {}

    for _, challengeMapID in ipairs(mapIDs) do

        local name,
            id,
            timeLimit,
            texture,
            backgroundTexture,
            mapID =
            C_ChallengeMode.GetMapUIInfo(challengeMapID)

        if id and name then

            table.insert(
                self.keys,
                {
                    id = id,
                    name = name,
                    challengeMapID = challengeMapID,
                    mapID = mapID,
                    timeLimit = timeLimit,
                    texture = texture,
                    backgroundTexture = backgroundTexture,
                    type = "KEYS",
                }
            )

        end

    end

    table.sort(self.keys, SortByName)
end

function GameData:GetDungeons()
    return self.dungeons or {}
end

function GameData:GetRaids()
    return self.raids or {}
end

function GameData:GetKeys()
    return self.keys or {}
end

function GameData:FindDungeon(id, mode)
    local list =
        mode == "KEYS"
        and self:GetKeys()
        or self:GetDungeons()

    for _, dungeon in ipairs(list) do
        if tonumber(dungeon.id) == tonumber(id) then
            return dungeon
        end
    end

    return nil
end

function GameData:FindRaid(id)
    for _, raid in ipairs(self:GetRaids()) do
        if tonumber(raid.id) == tonumber(id) then
            return raid
        end
    end

    return nil
end

function GameData:RefreshAll()
    self:RefreshCurrentCharacter()
    self:RefreshInstances()
    self:RefreshKeys()
end

function GameData:Initialize()
    if not WhatsNextUserData then
        return
    end

    WhatsNextUserData.characters = WhatsNextUserData.characters or {}

    self:RefreshAll()

    if self.EventFrame then
        return
    end

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

    eventFrame:RegisterEvent("PLAYER_LOGIN")
    eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
    eventFrame:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
    eventFrame:RegisterEvent("SKILL_LINES_CHANGED")
    eventFrame:RegisterEvent("TRADE_SKILL_LIST_UPDATE")
    eventFrame:RegisterEvent("CHALLENGE_MODE_MAPS_UPDATE")

    eventFrame:SetScript(
        "OnEvent",
        function(_, event, unit)

            if event == "PLAYER_SPECIALIZATION_CHANGED"
                and unit
                and unit ~= "player"
            then
                return
            end

            if event == "CHALLENGE_MODE_MAPS_UPDATE" then
                GameData:RefreshKeys()
            elseif event == "PLAYER_ENTERING_WORLD"
                or event == "PLAYER_LOGIN"
            then
                GameData:RefreshAll()
            else
                GameData:RefreshCurrentCharacter()
            end

            if addon.UI
                and addon.UI.MainFrame
                and addon.UI.MainFrame:IsShown()
                and addon.UI.RefreshDynamicSelectors
            then
                addon.UI:RefreshDynamicSelectors()
                addon.UI:Refresh()
            end

        end
    )
end
