local addonName, addon = ...

addon.LootTracker = addon.LootTracker or {}
addon.LootTracker.DB = addon.LootTracker.DB or {}

local DB = addon.LootTracker.DB

-- One SavedVariables profile is the source of truth for Loot Browser, My Loot
-- and Equipment Loot Test.  Queue one refresh after every mutation so changes
-- made in any view are immediately reflected in the others.
local lootRefreshQueued = false
local function NotifyLootViewsChanged()
    if lootRefreshQueued then return end
    lootRefreshQueued = true

    local function Refresh()
        lootRefreshQueued = false
        local UI = addon.UI
        if not UI then return end

        if UI.LootTrackerFrame and UI.RefreshLootTracker then
            UI:RefreshLootTracker()
        end
        if UI.EquipmentLootTestFrame and UI.RefreshEquipmentLootTest then
            UI:RefreshEquipmentLootTest()
        end
    end

    if C_Timer and C_Timer.After then
        C_Timer.After(0, Refresh)
    else
        Refresh()
    end
end

DB.PRIORITY_WANTED = "WANTED"
DB.PRIORITY_HIGH = "HIGH"
DB.PRIORITY_BIS = "BIS"

DB.PRIORITY_ORDER = {
    DB.PRIORITY_WANTED,
    DB.PRIORITY_HIGH,
    DB.PRIORITY_BIS,
}

function DB:Initialize()
    WhatsNextUserData = WhatsNextUserData or {}
    WhatsNextUserData.lootTracker = WhatsNextUserData.lootTracker or {}
end

function DB:GetProfile(characterKey, specID, create)
    if not WhatsNextUserData then
        self:Initialize()
    end

    if not characterKey or not specID then
        return nil
    end

    local characterProfiles = WhatsNextUserData.lootTracker[characterKey]

    if not characterProfiles and create then
        characterProfiles = {}
        WhatsNextUserData.lootTracker[characterKey] = characterProfiles
    end

    if not characterProfiles then
        return nil
    end

    local specKey = tostring(specID)
    local profile = characterProfiles[specKey]

    if not profile and create then
        profile = {
            items = {},
        }
        characterProfiles[specKey] = profile
    end

    return profile
end

function DB:GetTrackedItem(characterKey, specID, itemID)
    local profile = self:GetProfile(characterKey, specID, false)
    if not profile or not profile.items then
        return nil
    end

    return profile.items[tostring(itemID)]
end

function DB:TrackItem(characterKey, specID, itemID, data)
    local profile = self:GetProfile(characterKey, specID, true)
    if not profile then
        return nil
    end

    profile.items = profile.items or {}

    local key = tostring(itemID)
    local entry = profile.items[key] or {}

    entry.itemID = tonumber(itemID)
    entry.priority = data and data.priority or entry.priority or self.PRIORITY_WANTED
    entry.obtained = entry.obtained == true

    -- Void Core is an independent marker, not a fourth priority. It may coexist
    -- with WANTED, HIGH or BIS. Preserve it across TrackItem metadata refreshes.
    if data and data.voidCore ~= nil then
        entry.voidCore = data.voidCore == true
    else
        entry.voidCore = entry.voidCore == true
    end
    entry.sourceType = data and data.sourceType or entry.sourceType
    entry.sourceID = data and data.sourceID or entry.sourceID
    entry.sourceName = data and data.sourceName or entry.sourceName
    entry.bossID = data and data.bossID or entry.bossID
    entry.bossName = data and data.bossName or entry.bossName
    entry.slotID = data and data.slotID or entry.slotID

    entry.itemLevel =
        data and data.itemLevel
        or entry.itemLevel

    entry.trackType =
        data and data.trackType
        or entry.trackType

    entry.trackKey =
        data and data.trackKey
        or entry.trackKey

    entry.trackRank =
        data and data.trackRank
        or entry.trackRank

    entry.trackLabel =
        data and data.trackLabel
        or entry.trackLabel

    entry.bonusIDs =
        data and data.bonusIDs
        or entry.bonusIDs

    entry.craftPlan =
        data and data.craftPlan
        or entry.craftPlan

    entry.recipeSpellID =
        data and data.recipeSpellID
        or entry.recipeSpellID

    entry.professionID =
        data and data.professionID
        or entry.professionID

    entry.professionName =
        data and data.professionName
        or entry.professionName

    entry.addedAt = entry.addedAt or time()

    profile.items[key] = entry
    NotifyLootViewsChanged()
    return entry
end

function DB:UntrackItem(characterKey, specID, itemID)
    local profile = self:GetProfile(characterKey, specID, false)
    if not profile or not profile.items then
        return false
    end

    local key = tostring(itemID)
    if not profile.items[key] then
        return false
    end

    profile.items[key] = nil
    NotifyLootViewsChanged()
    return true
end


function DB:SetVoidCore(characterKey, specID, itemID, enabled)
    local item = self:GetTrackedItem(characterKey, specID, itemID)
    if not item then return false end

    item.voidCore = enabled == true
    -- The Void Core roll is a second tracked copy of the item. Its obtained state
    -- must never share the normal item's obtained flag.
    if item.voidCoreObtained == nil then item.voidCoreObtained = false end
    if not item.voidCore then item.voidCoreObtained = false end
    NotifyLootViewsChanged()
    return true
end

function DB:SetVoidCoreObtained(characterKey, specID, itemID, obtained)
    local item = self:GetTrackedItem(characterKey, specID, itemID)
    if not item or not item.voidCore then return false end

    item.voidCoreObtained = obtained == true
    if item.voidCoreObtained then
        item.voidCoreObtainedAt = item.voidCoreObtainedAt or time()
    else
        item.voidCoreObtainedAt = nil
    end
    NotifyLootViewsChanged()
    return true
end

function DB:SetPriority(characterKey, specID, itemID, priority)
    local item = self:GetTrackedItem(characterKey, specID, itemID)
    if not item then
        return false
    end

    if priority ~= self.PRIORITY_WANTED
        and priority ~= self.PRIORITY_HIGH
        and priority ~= self.PRIORITY_BIS
    then
        return false
    end

    item.priority = priority
    NotifyLootViewsChanged()
    return true
end

function DB:CyclePriority(characterKey, specID, itemID)
    local item = self:GetTrackedItem(characterKey, specID, itemID)
    if not item then
        return nil
    end

    local currentIndex = 1

    for index, priority in ipairs(self.PRIORITY_ORDER) do
        if priority == item.priority then
            currentIndex = index
            break
        end
    end

    currentIndex = currentIndex + 1
    if currentIndex > #self.PRIORITY_ORDER then
        currentIndex = 1
    end

    item.priority = self.PRIORITY_ORDER[currentIndex]
    NotifyLootViewsChanged()
    return item.priority
end

function DB:SetObtained(characterKey, specID, itemID, obtained)
    local item = self:GetTrackedItem(characterKey, specID, itemID)
    if not item then
        return false
    end

    item.obtained = obtained == true

    if item.obtained then
        item.obtainedAt = item.obtainedAt or time()
    else
        item.obtainedAt = nil
    end

    NotifyLootViewsChanged()
    return true
end

function DB:GetTrackedItems(characterKey, specID)
    local profile = self:GetProfile(characterKey, specID, false)
    local result = {}

    if not profile or not profile.items then
        return result
    end

    for _, item in pairs(profile.items) do
        table.insert(result, item)
    end

    table.sort(result, function(a, b)
        local aObtained = a.obtained == true
        local bObtained = b.obtained == true

        if aObtained ~= bObtained then
            return not aObtained
        end

        local aSlot = tonumber(a.slotID) or 99
        local bSlot = tonumber(b.slotID) or 99

        if aSlot ~= bSlot then
            return aSlot < bSlot
        end

        return (tonumber(a.itemID) or 0) < (tonumber(b.itemID) or 0)
    end)

    return result
end

function DB:GetProgress(characterKey, specID)
    local items = self:GetTrackedItems(characterKey, specID)
    local obtained = 0

    for _, item in ipairs(items) do
        if item.obtained then
            obtained = obtained + 1
        end
    end

    return obtained, #items
end
