--[[
============================================================================
ETB - SourceDB.lua (AtlasLoot + EJ Enrichment Layer)
============================================================================
Provides boss names, instance keys, and source metadata for 18,845 items
from AtlasLoot + Encounter Journal merged data.

Used by MapperBridge as enrichment when its own data is missing
encounter names, instance names, or source details.

ARCHITECTURE:
  - Data files (Source_Classic.lua ... Source_MoP.lua) register loader
    functions on NS._SrcLoaders[expansion]
  - On first access, loaders run and return string tables + item arrays
  - Items stored as compact arrays, decoded on demand with LRU cache
  - Zero memory impact until first query

USAGE (internal to MapperBridge):
  local info = ETB.SourceDB:Get(itemID)
  if info then
      info.bossName       -- "Ook-Ook"
      info.instanceKey    -- "StormstoutBrewery"
      info.instanceID     -- 302 (numeric, for Names.inst lookup)
      info.instanceName   -- "Deadmines" (readable, for items without instanceKey)
      info.encounterID    -- 668
      info.ejInstanceID   -- 63 (Encounter Journal instance ID)
      info.mapID          -- 5963
      info.expansion      -- "MoP"
      info.sourceType     -- "LOOT"
      info.isDungeon      -- true
      info.isRaid         -- false
      info.isTrash        -- false
      info.isWorldBoss    -- false
      info.isCrafted      -- false
      info.recipeItemID   -- nil or recipe item ID
  end
============================================================================
]]--

local ADDON_NAME, NS = ...
local ETB = NS.ETB

-- Loader registration table (data files write here)
NS._SrcLoaders = NS._SrcLoaders or {}

-- Public API
ETB.SourceDB = {}
local SDB = ETB.SourceDB

---------------------------------------------------------------------------
-- Constants
---------------------------------------------------------------------------

local SOURCE_TYPES = {
    [0]="UNKNOWN",[1]="LOOT",[2]="QUEST",[3]="VENDOR",[4]="FIRST_AID",
    [5]="BLACKSMITHING",[6]="LEATHERWORKING",[7]="ALCHEMY",
    [9]="COOKING",[10]="MINING",[11]="TAILORING",[12]="ENGINEERING",
    [13]="ENCHANTING",[14]="FISHING",[16]="UNKNOWN_16",
    [17]="JEWELCRAFTING",[18]="INSCRIPTION",[19]="ARCHAEOLOGY",
}

local PROFESSION_SOURCES = {
    [4]=true,[5]=true,[6]=true,[7]=true,[9]=true,[10]=true,
    [11]=true,[12]=true,[13]=true,[14]=true,[17]=true,[18]=true,[19]=true,
}

local FLAG_DUNGEON = 0x01
local FLAG_RAID    = 0x02
local FLAG_TRASH   = 0x04
local FLAG_WBOSS   = 0x08

local EXPANSIONS = { "Classic", "TBC", "Wrath", "Cata", "MoP" }

---------------------------------------------------------------------------
-- Internal State
---------------------------------------------------------------------------

local loaded = {}           -- expansion -> {instances, bosses, items}
local itemExpansion = {}    -- itemID -> expansion
local indexBuilt = false

-- LRU cache for decoded records
local cache = {}
local cacheOrder = {}
local CACHE_MAX = 200

---------------------------------------------------------------------------
-- Data Loading (lazy)
---------------------------------------------------------------------------

local function LoadExpansion(expansion)
    if loaded[expansion] then return true end
    local loader = NS._SrcLoaders and NS._SrcLoaders[expansion]
    if not loader or type(loader) ~= "function" then return false end
    
    local I, B, D, N = loader()
    if not D then return false end
    
    loaded[expansion] = { instances = I or {}, bosses = B or {}, items = D, instNames = N or {} }
    NS._SrcLoaders[expansion] = true  -- Free the loader
    return true
end

local function BuildIndex()
    if indexBuilt then return end
    for _, exp in ipairs(EXPANSIONS) do
        if LoadExpansion(exp) then
            for itemID in pairs(loaded[exp].items) do
                itemExpansion[itemID] = exp
            end
        end
    end
    indexBuilt = true
end

---------------------------------------------------------------------------
-- Record Decoding
---------------------------------------------------------------------------

local function DecodeRecord(expansion, itemID, raw)
    local expData = loaded[expansion]
    if not expData then return nil end
    
    local srcTypeID   = raw[1] or 0
    local instIdx     = raw[2] or 0
    local bossIdx     = raw[3] or 0
    local encounterID = raw[4] or 0
    local bossNameIdx = raw[5] or 0
    local mapID       = raw[6] or 0
    local difficulty  = raw[7] or 0
    local flags       = raw[8] or 0
    local recipeID    = raw[9] or 0
    local instanceID  = raw[10] or 0
    local ejInstID    = raw[11] or 0
    local instNameIdx = raw[12] or 0
    
    local rec = {
        itemID       = itemID,
        expansion    = expansion,
        sourceType   = SOURCE_TYPES[srcTypeID] or "UNKNOWN",
        sourceTypeID = srcTypeID,
    }
    
    if instIdx > 0 and expData.instances[instIdx] then
        rec.instanceKey = expData.instances[instIdx]
    end
    if bossIdx > 0 then rec.bossIdx = bossIdx end
    if bossNameIdx > 0 and expData.bosses[bossNameIdx] then
        rec.bossName = expData.bosses[bossNameIdx]
    end
    if encounterID > 0 then rec.encounterID = encounterID end
    if mapID > 0 then rec.mapID = mapID end
    if difficulty > 0 then rec.difficulty = difficulty end
    if recipeID > 0 then rec.recipeItemID = recipeID end
    if instanceID > 0 then rec.instanceID = instanceID end
    if ejInstID > 0 then rec.ejInstanceID = ejInstID end
    if instNameIdx > 0 and expData.instNames[instNameIdx] then
        rec.instanceName = expData.instNames[instNameIdx]
    end
    
    rec.isDungeon   = bit.band(flags, FLAG_DUNGEON) > 0
    rec.isRaid      = bit.band(flags, FLAG_RAID) > 0
    rec.isTrash     = bit.band(flags, FLAG_TRASH) > 0
    rec.isWorldBoss = bit.band(flags, FLAG_WBOSS) > 0
    rec.isCrafted   = PROFESSION_SOURCES[srcTypeID] or false
    
    return rec
end

---------------------------------------------------------------------------
-- Cache
---------------------------------------------------------------------------

local function CacheGet(itemID)
    return cache[itemID]
end

local function CachePut(itemID, record)
    if cache[itemID] then return end
    if #cacheOrder >= CACHE_MAX then
        local evictID = table.remove(cacheOrder, 1)
        cache[evictID] = nil
    end
    cache[itemID] = record
    table.insert(cacheOrder, itemID)
end

---------------------------------------------------------------------------
-- PUBLIC API
---------------------------------------------------------------------------

--- Get enrichment data for an item.
-- @param itemID number
-- @return table|nil
function SDB:Get(itemID)
    if not itemID then return nil end
    
    local cached = CacheGet(itemID)
    if cached then return cached end
    
    if not indexBuilt then BuildIndex() end
    
    local expansion = itemExpansion[itemID]
    if not expansion then return nil end
    
    local expData = loaded[expansion]
    if not expData then return nil end
    
    local raw = expData.items[itemID]
    if not raw then return nil end
    
    local record = DecodeRecord(expansion, itemID, raw)
    if record then CachePut(itemID, record) end
    return record
end

--- Quick existence check.
function SDB:Has(itemID)
    if not indexBuilt then BuildIndex() end
    return itemExpansion[itemID] ~= nil
end

--- Get boss name for an item (most common enrichment).
function SDB:GetBossName(itemID)
    local rec = self:Get(itemID)
    return rec and rec.bossName or nil
end

--- Get instance key for an item.
function SDB:GetInstanceKey(itemID)
    local rec = self:Get(itemID)
    return rec and rec.instanceKey or nil
end

--- Get item count.
function SDB:GetCount()
    if not indexBuilt then BuildIndex() end
    local count = 0
    for _ in pairs(itemExpansion) do count = count + 1 end
    return count
end

--- Get items for an instance key.
function SDB:GetByInstance(instanceKey)
    if not indexBuilt then BuildIndex() end
    local result = {}
    for _, exp in ipairs(EXPANSIONS) do
        if loaded[exp] then
            local instIdx
            for i, key in ipairs(loaded[exp].instances) do
                if key == instanceKey then instIdx = i; break end
            end
            if instIdx then
                for itemID, raw in pairs(loaded[exp].items) do
                    if (raw[2] or 0) == instIdx then
                        result[#result + 1] = itemID
                    end
                end
            end
        end
    end
    return result
end

--- Get items for an encounter.
function SDB:GetByEncounter(encounterID)
    if not indexBuilt then BuildIndex() end
    local result = {}
    for _, exp in ipairs(EXPANSIONS) do
        if loaded[exp] then
            for itemID, raw in pairs(loaded[exp].items) do
                if (raw[4] or 0) == encounterID then
                    result[#result + 1] = itemID
                end
            end
        end
    end
    return result
end

--- Get stats.
function SDB:GetStats()
    if not indexBuilt then BuildIndex() end
    local byExp = {}
    local total = 0
    for _, exp in ipairs(EXPANSIONS) do
        if loaded[exp] then
            local count = 0
            for _ in pairs(loaded[exp].items) do count = count + 1 end
            byExp[exp] = count
            total = total + count
        end
    end
    return { total = total, byExpansion = byExp }
end

--- Force load all data.
function SDB:LoadAll()
    for _, exp in ipairs(EXPANSIONS) do LoadExpansion(exp) end
    BuildIndex()
end

--- Clear decode cache.
function SDB:ClearCache()
    cache = {}
    cacheOrder = {}
end

SDB.SOURCE_TYPES = SOURCE_TYPES
SDB.EXPANSIONS = EXPANSIONS

--- Get all items flagged as world boss loot.
-- Returns a flat array of {itemID=, encounterID=} entries for BossLootTooltip.
-- Scans raw data directly to avoid decoding every item (only checks flags field).
function SDB:GetAllWorldBossItems()
    if not indexBuilt then BuildIndex() end
    local result = {}
    for _, exp in ipairs(EXPANSIONS) do
        if loaded[exp] then
            for itemID, raw in pairs(loaded[exp].items) do
                local flags = raw[8] or 0
                if bit.band(flags, FLAG_WBOSS) > 0 then
                    local encounterID = raw[4]
                    if encounterID and encounterID ~= 0 then
                        result[#result + 1] = { itemID = itemID, encounterID = encounterID }
                    end
                end
            end
        end
    end
    return result
end
