-- Enhanced Raid Browser — Data Bridge v7
-- Dual-source: BiSData contains both "wh" (WoWHead) and "wowtbc" (WoWTBC)
-- Player can switch between sources via UI toggle or /erb source.
local ADDON_NAME, NS = ...
local ETB = NS.ETB
local ERB = ETB.ERB

local DB = {}
ERB.DataBridge = DB

DB.slotData = {}
DB.npcIndex = {}
DB.professionItems = {}
-- v15.7.2: P5 phase added in advance of Siege of Orgrimmar (June 2, 2026).
-- BiSData files don't yet contain P5 entries for any spec, so the phase tab
-- will appear empty until a v16.x data update populates them. Players can
-- build their own P5 lists via /etb cbis as soon as Phase 5 launches.
DB.phases = {"PR","P3","P5"}
DB.currentPhase = nil
DB.currentSlot = nil
DB.playerClassIdx = nil
DB.playerSpecIdx = nil
DB.playerClassName = nil
DB.playerSpecName = nil
DB.activeSource = "wowtbc"
DB.itemCount = 0

local PHASE_NAMES = { ["PR"]="Pre-Raid", ["P3"]="Phase 3 (ToT)", ["P5"]="Phase 5 (SoO)" }

local SLOT_ORDER = {
    "Head","Neck","Shoulder","Back","Chest","Wrist",
    "Hands","Waist","Legs","Feet","Finger","Trinket",
    "Weapon","Off hand",
}

local WOW_TO_BT_CLASS = {
    [1]=11,[2]=6,[3]=3,[4]=8,[5]=7,[6]=1,[7]=9,[8]=4,[9]=10,[10]=5,[11]=2,
}

local PROF_NAMES = {
    [164]="Blacksmithing",[165]="Leatherworking",[171]="Alchemy",
    [197]="Tailoring",[202]="Engineering",[333]="Enchanting",
    [755]="Jewelcrafting",[773]="Inscription",[185]="Cooking",
}

DB.SLOT_ORDER = SLOT_ORDER
DB.PHASE_NAMES = PHASE_NAMES

-- ============================================================================
-- INITIALIZATION
-- ============================================================================

function DB:Initialize()
    self.slotData = {}
    self.npcIndex = {}
    self.professionItems = {}
    -- Cleared on each Init; populated with a short tag on silent return.
    -- /erb diag reads this to show the user exactly where Initialize bailed.
    self.initStatus = nil

    if not ERB.BiSData then
        self.initStatus = "no-bisdata"  -- BiSData.lua didn't load or is empty
        return
    end

    -- Default source from saved vars or first available
    if not self.activeSource then
        local saved = ETBDB and ETBDB.erbSource
        if saved and ERB.BiSData[saved] then
            self.activeSource = saved
        elseif ERB.Sources then
            self.activeSource = ERB.Sources[1]
        else
            -- Legacy single-source format
            self.activeSource = nil
        end
    end

    -- Detect player class/spec
    local info = ERB:GetPlayerInfo()
    local _, _, wowClassID = UnitClass("player")
    self.playerClassIdx = WOW_TO_BT_CLASS[wowClassID]
    self.playerClassName = info.className

    -- Fuzzy spec matching
    self.playerSpecIdx = nil
    self.playerSpecName = nil
    local wowSpecName = info.specName or ""
    local wowSpecLower = wowSpecName:lower():gsub("%s+", "")

    if ERB.ClassData and ERB.ClassData[self.playerClassIdx] and wowSpecLower ~= "" then
        for btIdx, btName in pairs(ERB.ClassData[self.playerClassIdx]) do
            local btLower = btName:lower():gsub("%s+", "")
            if btLower == wowSpecLower then
                self.playerSpecIdx = btIdx
                self.playerSpecName = btName
                break
            end
            if btLower:find(wowSpecLower, 1, true) or wowSpecLower:find(btLower, 1, true) then
                self.playerSpecIdx = btIdx
                self.playerSpecName = btName
            end
        end
    end

    if not self.playerSpecIdx then
        local specIndex = info.specIndex or 0
        if specIndex > 0 and ERB.ClassData and ERB.ClassData[self.playerClassIdx] then
            if ERB.ClassData[self.playerClassIdx][specIndex] then
                self.playerSpecIdx = specIndex
                self.playerSpecName = ERB.ClassData[self.playerClassIdx][specIndex]
            end
        end
    end

    if not self.playerSpecIdx then
        self.playerSpecName = wowSpecName ~= "" and wowSpecName or nil
    end

    -- Track whether spec was detected from player data vs fallback
    local specWasDetected = (self.playerSpecIdx ~= nil)

    -- Get source data
    local srcData = self:GetSourceBiSData()
    if not srcData then
        self.initStatus = "no-source-data:" .. tostring(self.activeSource)
        return
    end

    local classData = srcData[self.playerClassIdx]
    if not classData then
        self.initStatus = "no-class-data:classIdx=" .. tostring(self.playerClassIdx) ..
                          " wowClassID=" .. tostring(wowClassID)
        return
    end

    local specData = classData[self.playerSpecIdx]
    if not specData then
        -- Spec not matched. Use first available as temporary fallback.
        self.specAutoDetected = false
        for si, sd in pairs(classData) do
            specData = sd
            self.playerSpecIdx = si
            if ERB.ClassData and ERB.ClassData[self.playerClassIdx] then
                self.playerSpecName = ERB.ClassData[self.playerClassIdx][si] or "?"
            end
            break
        end
    else
        self.specAutoDetected = specWasDetected
    end

    if not specData then
        self.initStatus = "no-spec-data:classIdx=" .. tostring(self.playerClassIdx) ..
                          " specIdx=" .. tostring(self.playerSpecIdx)
        return
    end

    self:LoadSpecData(specData)
    self.initStatus = "ok"
end

-- Get the BiSData table for the active source
function DB:GetSourceBiSData()
    if not ERB.BiSData then return nil end
    if self.activeSource and ERB.BiSData[self.activeSource] then
        return ERB.BiSData[self.activeSource]
    end
    -- Legacy: BiSData is the data itself (no source key)
    if ERB.BiSData[1] or ERB.BiSData[self.playerClassIdx] then
        return ERB.BiSData
    end
    return nil
end

-- ============================================================================
-- LOAD SPEC DATA
-- ============================================================================

function DB:LoadSpecData(specData)
    self.slotData = {}
    local uniqueItems = {}

    for _, phase in ipairs(self.phases) do
        local phaseData = specData[phase]
        if phaseData then
            for slotIdx, items in pairs(phaseData) do
                local slotName = ERB.SlotNames and ERB.SlotNames[slotIdx] or ("Slot" .. slotIdx)
                if not self.slotData[slotName] then self.slotData[slotName] = {} end
                if not self.slotData[slotName][phase] then self.slotData[slotName][phase] = {} end

                local phaseList = self.slotData[slotName][phase]
                for rank, itemID in ipairs(items) do
                    phaseList[#phaseList + 1] = {
                        itemID = itemID,
                        rank = rank,
                        label = rank == 1 and "BiS" or ("Alt " .. rank),
                        spec = self.playerSpecName,
                        isCurrent = true,
                        isUpgraded = (itemID >= 96000 and itemID < 98000),
                    }
                    uniqueItems[itemID] = true
                end
            end
        end
    end

    self.currentPhase = self.phases[1]
    self.currentSlot = self.currentSlot or "Head"

    local count = 0
    for _ in pairs(uniqueItems) do count = count + 1 end
    self.itemCount = count

    self:BuildNPCIndex()
    self:LoadProfessionData()
end

-- ============================================================================
-- SOURCE SWITCHING
-- ============================================================================

function DB:GetAvailableSources()
    local sources = {}
    if ERB.Sources then
        for _, k in ipairs(ERB.Sources) do sources[#sources + 1] = k end
    end
    -- v15.7.0: append user-defined custom BiS lists. Each list becomes a
    -- virtual source with key "cbis:<name>". The "cbis:" prefix is the
    -- discriminator that SwitchSource uses to route to ERB.CustomBiS rather
    -- than ERB.BiSData.
    if ERB.CustomBiS and ERB.CustomBiS.initialized then
        for _, name in ipairs(ERB.CustomBiS:ListAll()) do
            sources[#sources + 1] = "cbis:" .. name
        end
    end
    return sources
end

function DB:GetSourceLabel(key)
    if ERB.SourceLabels and ERB.SourceLabels[key] then
        return ERB.SourceLabels[key]
    end
    -- v15.7.0: synthesize a label for custom lists. The "★ " prefix marks
    -- them visually as user-created in the source-switcher button.
    if type(key) == "string" then
        local cbName = key:match("^cbis:(.+)$")
        if cbName then return "★ " .. cbName end
    end
    return key or "?"
end

function DB:SwitchSource(key)
    -- v15.7.0: custom-list path (key like "cbis:My PvE List")
    if type(key) == "string" then
        local cbName = key:match("^cbis:(.+)$")
        if cbName and ERB.CustomBiS then
            local slice = ERB.CustomBiS:GetVirtualSpecSlice(cbName)
            if not slice then return false end
            self.activeSource = key
            if ETBDB then ETBDB.erbSource = key end
            local classData = slice[self.playerClassIdx]
            local specData = classData and classData[self.playerSpecIdx]
            if specData then
                self:LoadSpecData(specData)
            end
            return true
        end
    end

    -- Curated source path
    if not ERB.BiSData or not ERB.BiSData[key] then return false end
    self.activeSource = key
    if ETBDB then ETBDB.erbSource = key end

    local srcData = ERB.BiSData[key]
    local classData = srcData and srcData[self.playerClassIdx]
    local specData = classData and classData[self.playerSpecIdx]

    if not specData and classData then
        for si, sd in pairs(classData) do
            specData = sd
            self.playerSpecIdx = si
            if ERB.ClassData and ERB.ClassData[self.playerClassIdx] then
                self.playerSpecName = ERB.ClassData[self.playerClassIdx][si] or "?"
            end
            break
        end
    end

    if specData then
        self:LoadSpecData(specData)
    end
    return true
end

function DB:CycleSource()
    local sources = self:GetAvailableSources()
    if #sources < 2 then return end
    for i, key in ipairs(sources) do
        if key == self.activeSource then
            local nxt = sources[(i % #sources) + 1]
            self:SwitchSource(nxt)
            return
        end
    end
end

-- ============================================================================
-- SPEC SWITCHING
-- ============================================================================

function DB:GetAvailableSpecs()
    local specs = {}
    if not self.playerClassIdx then return specs end
    local srcData = self:GetSourceBiSData()
    if not srcData then return specs end

    local classData = srcData[self.playerClassIdx]
    if not classData then return specs end

    if ERB.ClassData and ERB.ClassData[self.playerClassIdx] then
        for specIdx, specName in pairs(ERB.ClassData[self.playerClassIdx]) do
            if classData[specIdx] then
                specs[#specs + 1] = {
                    idx = specIdx,
                    name = specName,
                    isActive = (specIdx == self.playerSpecIdx),
                }
            end
        end
    end
    table.sort(specs, function(a, b) return a.idx < b.idx end)
    return specs
end

function DB:SwitchSpec(specIdx)
    local srcData = self:GetSourceBiSData()
    if not srcData then return false end
    local classData = srcData[self.playerClassIdx]
    if not classData or not classData[specIdx] then return false end

    self.playerSpecIdx = specIdx
    if ERB.ClassData and ERB.ClassData[self.playerClassIdx] then
        self.playerSpecName = ERB.ClassData[self.playerClassIdx][specIdx] or "?"
    end

    self:LoadSpecData(classData[specIdx])
    return true
end

-- ============================================================================
-- NPC INDEX
-- ============================================================================

function DB:BuildNPCIndex()
    self.npcIndex = {}
    
    -- Force lazy-load of boss loot data if needed
    if ETB._BossLootLoader then
        ETB._BossLootLoader()
        ETB._BossLootLoader = nil
    end
    
    -- Build reverse map: encounterID → {npcID1, npcID2, ...}
    local encToNPCs = {}
    if ETB.JNpcToEnc then
        for npcID, encID in pairs(ETB.JNpcToEnc) do
            if not encToNPCs[encID] then encToNPCs[encID] = {} end
            encToNPCs[encID][npcID] = true
        end
    end
    
    -- For each BiS item, find which NPC(s) drop it via JLoot
    if ETB.JLoot then
        for slotName, phases in pairs(self.slotData) do
            for phase, items in pairs(phases) do
                for _, item in ipairs(items) do
                    local lootData = ETB.JLoot[item.itemID]
                    if lootData then
                        local encounters = {}
                        if type(lootData[1]) == "table" then
                            for _, entry in ipairs(lootData) do
                                encounters[#encounters + 1] = entry[1]
                            end
                        else
                            encounters[1] = lootData[1]
                        end
                        
                        for _, encID in ipairs(encounters) do
                            if encToNPCs[encID] then
                                for npcID in pairs(encToNPCs[encID]) do
                                    if not self.npcIndex[npcID] then self.npcIndex[npcID] = {} end
                                    self.npcIndex[npcID][item.itemID] = true
                                end
                            end
                        end
                    end
                end
            end
        end
    -- Fallback to legacy ItemBossMap if JLoot not available
    elseif ERB.ItemBossMap then
        for slotName, phases in pairs(self.slotData) do
            for phase, items in pairs(phases) do
                for _, item in ipairs(items) do
                    local mapping = ERB.ItemBossMap[item.itemID]
                    if mapping then
                        local npcID = mapping[2]
                        if npcID and npcID > 0 then
                            if not self.npcIndex[npcID] then self.npcIndex[npcID] = {} end
                            self.npcIndex[npcID][item.itemID] = true
                        end
                    end
                end
            end
        end
    end
end

function DB:CountNPCMappings()
    local n = 0
    for _ in pairs(self.npcIndex) do n = n + 1 end
    return n
end

-- ============================================================================
-- PROFESSION DATA
-- ============================================================================

function DB:LoadProfessionData()
    self.professionItems = {}
    -- v12.67.0: ItemMetadata.lua is now lazy-loaded. __index metatables don't
    -- trigger on `pairs()`, so we must force-materialize before iterating.
    if ETB and ETB._ItemMetadataLoader then
        ETB._ItemMetadataLoader()
        ETB._ItemMetadataLoader = nil
    end
    if ETB and ETB.RecipeData then
        for itemID, data in pairs(ETB.RecipeData) do
            local skillLine = data[2] or 0
            local spellID = data[4] or data[3] or 0
            self.professionItems[itemID] = {
                profession = PROF_NAMES[skillLine] or "Crafted",
                skill = 0, recipeSpellID = spellID,
            }
        end
    end
end

function DB:IsCrafted(itemID) return self.professionItems[itemID] ~= nil end
function DB:GetProfessionInfo(itemID) return self.professionItems[itemID] end

function DB:GetReagents(itemID)
    local prof = self.professionItems[itemID]
    if not prof or not prof.recipeSpellID or prof.recipeSpellID == 0 then return nil end
    local rd = ERB.ReagentData or (ETB and ETB.ReagentData)
    if not rd then return nil end
    return rd[prof.recipeSpellID]
end

-- ============================================================================
-- QUERY API
-- ============================================================================

function DB:GetItemsForSlot(slot, phase)
    phase = phase or self.currentPhase
    if not self.slotData[slot] then return {} end
    return self.slotData[slot][phase] or {}
end

function DB:GetAllSlotCounts(phase)
    phase = phase or self.currentPhase
    local counts = {}
    for _, slot in ipairs(SLOT_ORDER) do
        local items = self.slotData[slot] and self.slotData[slot][phase]
        counts[slot] = items and #items or 0
    end
    return counts
end

function DB:GetPhases() return self.phases end
function DB:GetCurrentPhase() return self.currentPhase end
function DB:SetPhase(p) self.currentPhase = p end
function DB:GetPhaseName(p) return PHASE_NAMES[p] or p end
function DB:GetBiSCount() return self.itemCount end
function DB:GetBiSForNPC(npcID) return self.npcIndex[npcID] end

function DB:GetItemBiSInfo(itemID)
    for slotName, phases in pairs(self.slotData) do
        for phase, items in pairs(phases) do
            for _, item in ipairs(items) do
                if item.itemID == itemID then return item end
            end
        end
    end
    return nil
end

function DB:GetBossName(itemID)
    -- Try JLoot + JEncounters first (primary path)
    if ETB.JLoot and ETB.JEncounters then
        local lootData = ETB.JLoot[itemID]
        if lootData then
            local encID
            if type(lootData[1]) == "table" then
                encID = lootData[1][1]  -- First encounter for multi-drop items
            else
                encID = lootData[1]
            end
            local encData = ETB.JEncounters[encID]
            if encData then
                return encData[1]  -- Boss name
            end
        end
    end
    -- Fallback to legacy ItemBossMap
    if ERB.ItemBossMap and ERB.BossNames then
        local mapping = ERB.ItemBossMap[itemID]
        if mapping then return ERB.BossNames[mapping[1]] end
    end
    return nil
end
