--[[
ETB Wishlist — Star items/sets, track farming progress
SavedVariable: ETB_Wishlist = { items = {[itemID]=true}, sets = {[setID]=true} }
]]--
local ADDON_NAME, NS = ...
local ETB = NS.ETB

ETB.Wishlist = {}
local WL = ETB.Wishlist

function WL:Initialize()
    -- Account-wide store. Appearances are account-wide and cross-character farm
    -- planning needs one shared list, so the wishlist lives in an account SV.
    ETB_WishlistAccount = ETB_WishlistAccount or { items = {}, sets = {} }
    ETB_WishlistAccount.items = ETB_WishlistAccount.items or {}
    ETB_WishlistAccount.sets = ETB_WishlistAccount.sets or {}

    -- One-time migration of THIS character's legacy per-character wishlist into
    -- the shared list (runs once per character, so every alt's saved items get
    -- folded in the first time it logs in with this build).
    if type(ETB_Wishlist) == "table" and not ETB_Wishlist._migrated then
        if type(ETB_Wishlist.items) == "table" then
            for id in pairs(ETB_Wishlist.items) do
                if id ~= "_migrated" then ETB_WishlistAccount.items[id] = true end
            end
        end
        if type(ETB_Wishlist.sets) == "table" then
            for id in pairs(ETB_Wishlist.sets) do ETB_WishlistAccount.sets[id] = true end
        end
        ETB_Wishlist._migrated = true
    end

    self.data = ETB_WishlistAccount
end

function WL:AddItem(itemID)
    if not self.data then self:Initialize() end
    self.data.items[itemID] = true
    self._visuals = nil
    ETB.EventBus:Fire("ETB_WISHLIST_CHANGED", "add", itemID)
end

function WL:RemoveItem(itemID)
    if not self.data then return end
    self.data.items[itemID] = nil
    self._visuals = nil
    ETB.EventBus:Fire("ETB_WISHLIST_CHANGED", "remove", itemID)
end

function WL:ToggleItem(itemID)
    if self:HasItem(itemID) then self:RemoveItem(itemID); return false
    else self:AddItem(itemID); return true end
end

function WL:HasItem(itemID)
    return self.data and self.data.items and self.data.items[itemID] == true
end

-- Appearance-aware wishlist. The wishlist stores itemIDs, but a player chasing a
-- LOOK is happy to get it from any item that shares the appearance. This set maps
-- each wishlisted item's visual to the item that anchors it, so a shared-appearance
-- sibling (e.g. the Boots that look like a wishlisted Greaves) reads as wishlisted
-- too. Cached; dropped on any wishlist change.
function WL:_ensureVisuals()
    if self._visuals then return end
    local v = {}
    if self.data and self.data.items and C_TransmogCollection
       and C_TransmogCollection.GetItemInfo then
        for itemID in pairs(self.data.items) do
            local ok, appID = pcall(C_TransmogCollection.GetItemInfo, itemID)
            if ok and appID and appID > 0 and not v[appID] then
                v[appID] = itemID
            end
        end
    end
    self._visuals = v
end

function WL:InvalidateVisuals() self._visuals = nil end

-- Returns isWishlisted, anchorItemID, isDirect.
--   isDirect  = the queried item is itself on the wishlist.
--   otherwise = it shares an appearance with anchorItemID, which is wishlisted.
function WL:HasAppearance(itemID)
    if not itemID then return false end
    if self:HasItem(itemID) then return true, itemID, true end
    if not (C_TransmogCollection and C_TransmogCollection.GetItemInfo) then return false end
    local ok, appID = pcall(C_TransmogCollection.GetItemInfo, itemID)
    if not (ok and appID and appID > 0) then return false end
    self:_ensureVisuals()
    local anchor = self._visuals[appID]
    if anchor then return true, anchor, false end
    return false
end

function WL:AddSet(setID)
    if not self.data then self:Initialize() end
    self.data.sets[setID] = true
    ETB.EventBus:Fire("ETB_WISHLIST_CHANGED", "addset", setID)
end

function WL:RemoveSet(setID)
    if not self.data then return end
    self.data.sets[setID] = nil
end

function WL:HasSet(setID)
    return self.data and self.data.sets and self.data.sets[setID] == true
end

function WL:GetItemCount()
    local count = 0
    if self.data and self.data.items then
        for _ in pairs(self.data.items) do count = count + 1 end
    end
    return count
end

function WL:GetAllItems()
    if not self.data or not self.data.items then return {} end
    local list = {}
    for itemID in pairs(self.data.items) do list[#list + 1] = itemID end
    return list
end

-- Group uncollected wishlist items by instance for farming.
-- Primary source is CollectionPlanner's JLoot-derived index (boss-accurate,
-- the same data the planner tree shows). SourceLookup is a fallback for items
-- with no instance drop — vendors, world drops, crafted, quest rewards.
function WL:GetFarmingGuide()
    if not self.data or not self.data.items then return {}, {} end

    local CP = ETB.CollectionPlanner
    local byInstance = {}  -- instanceName → { isRaid, expansion, bosses = { bossName → {itemIDs} } }
    local noSource = {}

    local function place(instName, isRaid, expansion, bossName, itemID)
        local inst = byInstance[instName]
        if not inst then
            inst = { isRaid = isRaid, expansion = expansion, bosses = {} }
            byInstance[instName] = inst
        end
        local boss = inst.bosses[bossName]
        if not boss then boss = {}; inst.bosses[bossName] = boss end
        boss[#boss + 1] = itemID
    end

    for itemID in pairs(self.data.items) do
        -- Skip already collected
        local ok, collected = pcall(C_TransmogCollection.PlayerHasTransmog, itemID)
        if not (ok and collected) then
            -- Authoritative: the planner's boss-loot graph.
            local loc = CP and CP.GetItemLocation and CP:GetItemLocation(itemID)
            if loc then
                place(loc.instName, loc.isRaid, loc.expansion,
                      loc.encName or "Unknown", itemID)
            else
                -- Fallback for non-instance sources.
                local sources = ETB.SourceLookup and ETB.SourceLookup:GetItemSources(itemID)
                if sources and sources[1] then
                    local s = sources[1]
                    place(s.instanceName or "Unknown", s.isRaid, s.expansion,
                          s.bossName or "Unknown", itemID)
                else
                    noSource[#noSource + 1] = itemID
                end
            end
        end
    end

    return byInstance, noSource
end
