local _, ns = ...

local L = ns.L or {}

local PROFESSION_SLOT_CATEGORY = {
    [1] = "primary",
    [2] = "primary",
    [3] = "secondary",
    [4] = "secondary",
    [5] = "secondary",
    [6] = "secondary",
}

local function trimRealm(realm)
    if not realm or realm == "" then
        return L.UNKNOWN or "Unknown"
    end

    return realm:gsub("%s+", "")
end

local function getCurrentEquippedItemLevel()
    if type(GetAverageItemLevel) ~= "function" then
        return nil
    end

    local ok, avgItemLevel, equippedItemLevel = pcall(GetAverageItemLevel)
    if not ok then
        return nil
    end

    local itemLevel = tonumber(equippedItemLevel) or tonumber(avgItemLevel)
    if itemLevel and itemLevel > 0 then
        return math.floor(itemLevel + 0.5)
    end

    return nil
end

local function getCurrentMoney()
    if type(GetMoney) ~= "function" then
        return nil
    end

    local ok, money = pcall(GetMoney)
    money = ok and tonumber(money) or nil
    if money then
        return math.max(0, math.floor(money + 0.5))
    end

    return nil
end

function ns:TrimRealm(realm)
    return trimRealm(realm)
end

function ns:GetCurrentEquippedItemLevel()
    return getCurrentEquippedItemLevel()
end

function ns:GetCurrentMoney()
    return getCurrentMoney()
end

local function buildCharacterKey(name, realm)
    return string.format("%s-%s", name or (L.UNKNOWN or "Unknown"), trimRealm(realm))
end

function ns:BuildCharacterKey(name, realm)
    return buildCharacterKey(name, realm)
end

local function scanCharacterProfessions()
    local list = {}

    if type(GetProfessions) ~= "function" or type(GetProfessionInfo) ~= "function" then
        return list
    end

    local indices = { GetProfessions() }
    local seenSkillLines = {}

    for slotIndex = 1, 6 do
        local index = indices[slotIndex]
        if index then
            local name, icon, skillLevel, maxSkillLevel, _, spellOffset, skillLineID = GetProfessionInfo(index)
            skillLineID = tonumber(skillLineID)
            if type(name) == "string" and name ~= "" and skillLineID and skillLineID > 0 and not seenSkillLines[skillLineID] then
                seenSkillLines[skillLineID] = true
                list[#list + 1] = {
                    slotIndex = slotIndex,
                    category = PROFESSION_SLOT_CATEGORY[slotIndex] or "secondary",
                    skillLineID = skillLineID,
                    name = name,
                    icon = icon,
                    spellOffset = tonumber(spellOffset),
                    skillLevel = tonumber(skillLevel) or 0,
                    maxSkillLevel = tonumber(maxSkillLevel) or 0,
                }
            end
        end
    end

    table.sort(list, function(a, b)
        local aCategory = a.category == "primary" and 1 or 2
        local bCategory = b.category == "primary" and 1 or 2
        if aCategory ~= bCategory then
            return aCategory < bCategory
        end

        local aSlot = tonumber(a.slotIndex) or 99
        local bSlot = tonumber(b.slotIndex) or 99
        if aSlot ~= bSlot then
            return aSlot < bSlot
        end

        return (a.name or "") < (b.name or "")
    end)

    return list
end

function ns:ScanCharacterProfessions()
    return scanCharacterProfessions()
end

function ns:GetCurrentCharacterInfo()
    local name, realm = UnitFullName("player")
    local className = select(1, UnitClass("player"))
    local level = UnitLevel("player") or 0

    return {
        name = name or UnitName("player") or (L.UNKNOWN or "Unknown"),
        realm = self:TrimRealm(realm or GetRealmName()),
        key = self:BuildCharacterKey(name or UnitName("player"), realm or GetRealmName()),
        className = className or (L.UNKNOWN or "Unknown"),
        level = level,
        money = getCurrentMoney(),
    }
end

function ns:GetSelectedCharacterKey()
    return self.db and self.db.ui and self.db.ui.selectedCharacterKey or self:GetCurrentCharacterInfo().key
end

function ns:SetSelectedCharacterKey(key)
    if not self.db or not self.db.ui or type(key) ~= "string" or key == "" then
        return
    end

    self.db.ui.selectedCharacterKey = key

    if self.UI and self.UI.Refresh then
        self.UI:Refresh()
    end
end

function ns:GetLiveVaultloomDB()
    if type(VaultloomDB) == "table" then
        return VaultloomDB
    end

    if type(MidnightVaultDB) == "table" then
        return MidnightVaultDB
    end

    return nil
end

function ns:GetLiveVaultloomCharacterRecord(key)
    local liveDB = self:GetLiveVaultloomDB()
    if not liveDB or type(liveDB.characters) ~= "table" then
        return nil
    end

    if type(key) == "string" and key ~= "" and type(liveDB.characters[key]) == "table" then
        return liveDB.characters[key]
    end

    local currentInfo = self:GetCurrentCharacterInfo()
    return liveDB.characters[currentInfo.key]
end

function ns:GetMainCharacterKey()
    local overrideKey = self.db and self.db.mainCharacterKey
    if type(overrideKey) == "string" and overrideKey ~= "" then
        return overrideKey
    end

    return self:GetCurrentCharacterInfo().key
end

function ns:SetMainCharacterKey(key)
    if not self.db or type(key) ~= "string" or key == "" then
        return
    end

    self.db.mainCharacterKey = key

    if self.UI and self.UI.Refresh then
        self.UI:Refresh()
    end
end

function ns:IsLiveMainCharacter(key)
    return self:GetMainCharacterKey() == key
end

function ns:GetLiveVaultloomCharacters(includeHidden)
    local liveDB = self:GetLiveVaultloomDB()
    local currentInfo = self:GetCurrentCharacterInfo()
    local currentKey = currentInfo.key
    local characters = {}
    local seen = {}

    local function resolveCharacterMoney(key, character, existingMoney)
        if key == currentKey then
            local currentMoney = self:GetCurrentMoney()
            if currentMoney ~= nil then
                return currentMoney
            end
        end

        local incomingMoney = tonumber(character and character.money)
        existingMoney = tonumber(existingMoney)
        if incomingMoney == 0 and existingMoney and existingMoney > 0 then
            return existingMoney
        end
        if incomingMoney ~= nil then
            return incomingMoney
        end
        return existingMoney
    end

    local function addCharacter(key, character)
        if type(key) ~= "string" or key == "" then
            return
        end

        if includeHidden ~= true and key ~= currentKey and self:IsCharacterHidden(key) then
            return
        end

        if seen[key] then
            for _, existing in ipairs(characters) do
                if existing.key == key then
                    existing.name = existing.name or character.name or currentInfo.name
                    existing.realm = existing.realm or self:TrimRealm((character and character.realm) or currentInfo.realm)
                    existing.className = existing.className or (character and character.className) or currentInfo.className
                    existing.classFile = existing.classFile or (character and (character.classFile or character.classToken or character.classFilename))
                    existing.level = math.max(tonumber(existing.level) or 0, tonumber(character and character.level) or 0)
                    existing.itemLevel = tonumber(existing.itemLevel) or tonumber(character and character.itemLevel)
                    existing.money = resolveCharacterMoney(key, character, existing.money)
                    if type(character and character.professions) == "table" then
                        existing.professions = character.professions
                    end
                    existing.lastSeen = math.max(tonumber(existing.lastSeen) or 0, tonumber(character and character.lastSeen) or 0)
                    break
                end
            end
            return
        end

        local classFile = character and (character.classFile or character.classToken or character.classFilename)
        characters[#characters + 1] = {
            key = key,
            name = character and character.name or currentInfo.name,
            realm = self:TrimRealm((character and character.realm) or currentInfo.realm),
            className = character and character.className or currentInfo.className,
            classFile = classFile,
            level = tonumber(character and character.level) or 0,
            itemLevel = tonumber(character and character.itemLevel),
            money = resolveCharacterMoney(key, character),
            professions = type(character and character.professions) == "table" and character.professions or nil,
            lastSeen = tonumber(character and character.lastSeen),
        }
        seen[key] = true
    end

    if liveDB and type(liveDB.characters) == "table" then
        for key, character in pairs(liveDB.characters) do
            addCharacter(key, character)
        end
    end

    if self.db and type(self.db.characters) == "table" then
        for key, character in pairs(self.db.characters) do
            addCharacter(key, character)
        end
    end

    if includeHidden == true and self.db and type(self.db.hiddenCharacters) == "table" then
        for key, hidden in pairs(self.db.hiddenCharacters) do
            if hidden == true and not seen[key] then
                local name, realm = tostring(key):match("^(.*)%-(.-)$")
                addCharacter(key, {
                    name = name or key,
                    realm = realm or "",
                    level = 0,
                    lastSeen = 0,
                })
            end
        end
    end

    if not seen[currentKey] then
        addCharacter(currentKey, {
            name = currentInfo.name,
            realm = currentInfo.realm,
            className = currentInfo.className,
            classFile = select(2, UnitClass("player")),
            level = currentInfo.level,
            itemLevel = self:GetCurrentEquippedItemLevel(),
            money = self:GetCurrentMoney(),
            professions = self:ScanCharacterProfessions(),
            lastSeen = time(),
        })
    end

    if #characters == 0 then
        characters[1] = {
            key = currentKey,
            name = currentInfo.name,
            realm = currentInfo.realm,
            className = currentInfo.className,
            classFile = select(2, UnitClass("player")),
            level = currentInfo.level,
            itemLevel = self:GetCurrentEquippedItemLevel(),
            money = self:GetCurrentMoney(),
            professions = self:ScanCharacterProfessions(),
            lastSeen = time(),
        }
    end

    table.sort(characters, function(a, b)
        local aMain = self:IsLiveMainCharacter(a.key)
        local bMain = self:IsLiveMainCharacter(b.key)
        if aMain ~= bMain then
            return aMain
        end

        local aCurrent = a.key == currentKey
        local bCurrent = b.key == currentKey
        if aCurrent ~= bCurrent then
            return aCurrent
        end

        local aSeen = a.lastSeen or 0
        local bSeen = b.lastSeen or 0
        if aSeen ~= bSeen then
            return aSeen > bSeen
        end

        local aLevel = a.level or 0
        local bLevel = b.level or 0
        if aLevel ~= bLevel then
            return aLevel > bLevel
        end

        return string.lower((a.name or "") .. "-" .. (a.realm or "")) < string.lower((b.name or "") .. "-" .. (b.realm or ""))
    end)

    return characters
end

function ns:GetAllVaultloomCharacters()
    return self:GetLiveVaultloomCharacters(true)
end

function ns:GetSelectedCharacter()
    local selectedKey = self:GetSelectedCharacterKey()
    local characters = self:GetLiveVaultloomCharacters()

    for _, character in ipairs(characters) do
        if character.key == selectedKey then
            return character
        end
    end

    local fallback = characters[1]
    if fallback then
        self.db.ui.selectedCharacterKey = fallback.key
    end
    return fallback
end

function ns:IsCurrentCharacterKey(key)
    if type(key) ~= "string" or key == "" then
        return false
    end

    return key == self:GetCurrentCharacterInfo().key
end

function ns:IsCharacterHidden(key)
    if type(key) ~= "string" or key == "" then
        return false
    end

    local hiddenCharacters = self.db and self.db.hiddenCharacters
    return type(hiddenCharacters) == "table" and hiddenCharacters[key] == true or false
end

function ns:CanHideCharacter(key)
    if type(key) ~= "string" or key == "" then
        return false
    end

    return not self:IsCurrentCharacterKey(key)
end

function ns:GetHiddenCharacterCount()
    local hiddenCharacters = self.db and self.db.hiddenCharacters
    if type(hiddenCharacters) ~= "table" then
        return 0
    end

    local count = 0
    for key, hidden in pairs(hiddenCharacters) do
        if hidden == true and not self:IsCurrentCharacterKey(key) then
            count = count + 1
        end
    end

    return count
end

function ns:SetCharacterHidden(key, hidden)
    if not self.db or type(key) ~= "string" or key == "" then
        return false
    end

    self.db.hiddenCharacters = type(self.db.hiddenCharacters) == "table" and self.db.hiddenCharacters or {}

    if hidden == true then
        if self:IsCurrentCharacterKey(key) then
            return false, "current"
        end

        self.db.hiddenCharacters[key] = true
        if self.db.ui and self.db.ui.selectedCharacterKey == key then
            self.db.ui.selectedCharacterKey = self:GetCurrentCharacterInfo().key
        end
    else
        self.db.hiddenCharacters[key] = nil
    end

    if self.UI and self.UI.Refresh then
        self.UI:Refresh()
    end

    return true
end

function ns:ClearHiddenCharacters()
    if not self.db then
        return
    end

    self.db.hiddenCharacters = {}

    if self.UI and self.UI.Refresh then
        self.UI:Refresh()
    end
end

function ns:IsUtilityResourceHidden(entryKey)
    if type(entryKey) ~= "string" or entryKey == "" then
        return false
    end

    local hiddenResources = self.db and self.db.ui and self.db.ui.hiddenUtilityResources
    return type(hiddenResources) == "table" and hiddenResources[entryKey] == true or false
end

function ns:GetHiddenUtilityResourceCount()
    local hiddenResources = self.db and self.db.ui and self.db.ui.hiddenUtilityResources
    if type(hiddenResources) ~= "table" then
        return 0
    end

    local count = 0
    for _, hidden in pairs(hiddenResources) do
        if hidden == true then
            count = count + 1
        end
    end

    return count
end

function ns:SetUtilityResourceHidden(entryKey, hidden)
    if not self.db or type(entryKey) ~= "string" or entryKey == "" then
        return false
    end

    self.db.ui = type(self.db.ui) == "table" and self.db.ui or {}
    self.db.ui.hiddenUtilityResources = type(self.db.ui.hiddenUtilityResources) == "table" and self.db.ui.hiddenUtilityResources or {}

    if hidden == true then
        self.db.ui.hiddenUtilityResources[entryKey] = true
    else
        self.db.ui.hiddenUtilityResources[entryKey] = nil
    end

    if self.UI and self.UI.Refresh then
        self.UI:Refresh()
    end

    return true
end

function ns:ClearHiddenUtilityResources()
    if not self.db then
        return
    end

    self.db.ui = type(self.db.ui) == "table" and self.db.ui or {}
    self.db.ui.hiddenUtilityResources = {}

    if self.UI and self.UI.Refresh then
        self.UI:Refresh()
    end
end
