--[[
============================================================================
ETB - SourceMapper.lua
============================================================================
Core source mapping system that bridges Blizzard's API source data with
human-readable source information.

ARCHITECTURE:
  - Uses Blizzard's C_TransmogCollection.GetSourceInfo() at runtime
  - Maps sourceType IDs to human-readable categories
  - Uses InstanceData for boss/dungeon/raid lookups
  - Supports static override data for items without API info
  - LRU cache for performance

BLIZZARD SOURCE TYPES (from C_TransmogCollection.GetSourceInfo):
  1 = Boss Drop (Dungeon/Raid)
  2 = Quest Reward
  3 = Vendor
  4 = World Drop
  5 = Achievement
  6 = Profession (Crafted)
  7 = Unavailable (no longer obtainable)

USAGE:
  local info = ETB.SourceMapper:GetSourceInfo(sourceID)
  local formatted = ETB.SourceMapper:FormatSource(itemID)
============================================================================
]]--

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

ETB.SourceMapper = {
    cache = nil,  -- LRU cache for source info
    overrides = {},  -- Static overrides for items without API data
    initialized = false,
    
    stats = {
        queries = 0,
        cacheHits = 0,
        cacheMisses = 0,
        apiCalls = 0,
        overrideHits = 0,
    },
}

local SM = ETB.SourceMapper

-- =============================================================================
-- SOURCE TYPE DEFINITIONS
-- Extended from SourceTypes.lua with additional metadata
-- =============================================================================

local SOURCE_TYPE_INFO = {
    [1] = {
        type = "BOSS",
        name = "Boss Drop",
        shortName = "Boss",
        color = "|cffff8000",  -- Orange (Legendary color)
        icon = "Interface\\Icons\\INV_Misc_Head_Dragon_01",
        description = "Drops from dungeon or raid bosses",
        priority = 1,  -- For sorting (lower = show first)
    },
    [2] = {
        type = "QUEST",
        name = "Quest",
        shortName = "Quest",
        color = "|cffffff00",  -- Yellow
        icon = "Interface\\Icons\\INV_Misc_QuestionMark",
        description = "Reward from completing quests",
        priority = 3,
    },
    [3] = {
        type = "VENDOR",
        name = "Vendor",
        shortName = "Vendor",
        color = "|cffffffff",  -- White
        icon = "Interface\\Icons\\INV_Misc_Coin_01",
        description = "Purchased from vendors",
        priority = 2,
    },
    [4] = {
        type = "WORLD_DROP",
        name = "World Drop",
        shortName = "World",
        color = "|cff1eff00",  -- Green
        icon = "Interface\\Icons\\INV_Misc_Bag_10",
        description = "Random drop from creatures in the world",
        priority = 5,
    },
    [5] = {
        type = "ACHIEVEMENT",
        name = "Achievement",
        shortName = "Achieve",
        color = "|cffffff00",  -- Yellow
        icon = "Interface\\Icons\\Achievement_General",
        description = "Reward from achievements",
        priority = 4,
    },
    [6] = {
        type = "PROFESSION",
        name = "Profession",
        shortName = "Crafted",
        color = "|cffff7c0a",  -- Orange
        icon = "Interface\\Icons\\Trade_BlackSmithing",
        description = "Crafted by professions",
        priority = 2,
    },
    [7] = {
        type = "UNAVAILABLE",
        name = "Unavailable",
        shortName = "N/A",
        color = "|cff808080",  -- Gray
        icon = "Interface\\Icons\\INV_Misc_QuestionMark",
        description = "No longer obtainable in game",
        priority = 99,
    },
}

-- Default for unknown source types
local UNKNOWN_SOURCE = {
    type = "UNKNOWN",
    name = "Unknown",
    shortName = "???",
    color = "|cff808080",
    icon = "Interface\\Icons\\INV_Misc_QuestionMark",
    description = "Source unknown",
    priority = 100,
}

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

function SM:Initialize()
    if self.initialized then return true end
    
    -- Create LRU cache (500 entries should be plenty for visible items)
    self.cache = ETB:CreateLRUCache(500, "SourceMapper")
    
    -- Load static overrides
    self:LoadOverrides()
    
    self.initialized = true
    ETB:Debug("SourceMapper", "SourceMapper initialized")
    return true
end

function SM:LoadOverrides()
    -- MapperBridge handles all source data — no manual overrides needed
    ETB:Debug("SourceMapper", "MapperBridge provides source data")
end

-- =============================================================================
-- CORE API: Get Source Info
-- =============================================================================

-- Get complete source info for a sourceID (from Blizzard's API)
function SM:GetSourceInfo(sourceID)
    if not sourceID then return nil end
    
    self.stats.queries = self.stats.queries + 1
    
    -- Check cache first
    local cached = self.cache and self.cache:Get(sourceID)
    if cached then
        self.stats.cacheHits = self.stats.cacheHits + 1
        return cached
    end
    
    self.stats.cacheMisses = self.stats.cacheMisses + 1
    
    -- Query Blizzard API
    local info = self:QueryBlizzardAPI(sourceID)
    
    -- Cache the result
    if info and self.cache then
        self.cache:Set(sourceID, info)
    end
    
    return info
end

-- Query Blizzard's C_TransmogCollection.GetSourceInfo
function SM:QueryBlizzardAPI(sourceID)
    if not C_TransmogCollection or not C_TransmogCollection.GetSourceInfo then
        return nil
    end
    
    self.stats.apiCalls = self.stats.apiCalls + 1
    
    local success, sourceInfo = pcall(C_TransmogCollection.GetSourceInfo, sourceID)
    if not success or not sourceInfo then
        return nil
    end
    
    -- Build our enhanced source info
    local info = {
        sourceID = sourceID,
        itemID = sourceInfo.itemID,
        itemModID = sourceInfo.itemModID,
        visualID = sourceInfo.visualID,
        isCollected = sourceInfo.isCollected,
        quality = sourceInfo.quality,
        
        -- Source type info from Blizzard API
        sourceType = sourceInfo.sourceType,
        sourceTypeInfo = SOURCE_TYPE_INFO[sourceInfo.sourceType] or UNKNOWN_SOURCE,
        
        -- Will be enriched from static data
        sourceName = nil,
        sourceLocation = nil,
        instanceID = nil,
        encounterID = nil,
        difficultyID = nil,
    }
    
    -- CRITICAL: Enrich with static data for location info
    -- Blizzard API gives us sourceType but NOT location details
    self:EnrichFromStaticData(info)
    
    return info
end

-- Enrich API data with static location info from MapperBridge
-- preferredItemID: when showing tooltip for a specific item, use this for lookup first
--   (API may return source for a different item with same appearance)
function SM:EnrichFromStaticData(info, preferredItemID)
    if not info then return end
    
    local lookupItemID = preferredItemID or info.itemID
    if not lookupItemID then return end
    
    -- MapperBridge provides all source data via SourceData_ATT API
    if ETB.SourceData_ATT then
        local attSource = ETB.SourceData_ATT:GetPrimarySource(lookupItemID)
        if attSource then
            self.stats.overrideHits = self.stats.overrideHits + 1
            
            -- Copy location info from mapper data
            info.sourceName = attSource.name
            info.sourceLocation = attSource.location
            info.encounterID = attSource.encounterID
            info.instanceID = attSource.instanceID
            info.difficultyID = attSource.difficultyID
            info.isWorldBoss = attSource.isWorldBoss
            
            -- Map type to Blizzard sourceType
            if not info.sourceType and attSource.type then
                local typeMap = {
                    BOSS = 1, QUEST = 2, VENDOR = 3, WORLD_DROP = 4,
                    ACHIEVEMENT = 5, CRAFTED = 6, PROFESSION = 6,
                    UNAVAILABLE = 7, PVP = 3,
                }
                info.sourceType = typeMap[attSource.type] or info.sourceType
                info.sourceTypeInfo = SOURCE_TYPE_INFO[info.sourceType] or UNKNOWN_SOURCE
            end
            
            return
        end
    end
    
    -- No static data found - set generic names based on source type
    self:SetGenericSourceName(info)
end

-- Set generic source names when no static data is available
function SM:SetGenericSourceName(info)
    if not info or not info.sourceTypeInfo then return end
    
    local sourceType = info.sourceTypeInfo.type
    
    if sourceType == "BOSS" then
        info.sourceName = "Boss Drop"
        info.sourceLocation = "Dungeon/Raid"
    elseif sourceType == "VENDOR" then
        info.sourceName = "Vendor"
        info.sourceLocation = nil
    elseif sourceType == "QUEST" then
        info.sourceName = "Quest Reward"
        info.sourceLocation = nil
    elseif sourceType == "WORLD_DROP" then
        info.sourceName = "World Drop"
        info.sourceLocation = nil
    elseif sourceType == "ACHIEVEMENT" then
        info.sourceName = "Achievement"
        info.sourceLocation = nil
    elseif sourceType == "PROFESSION" then
        info.sourceName = "Crafted"
        info.sourceLocation = nil
    elseif sourceType == "UNAVAILABLE" then
        info.sourceName = "No Longer Available"
        info.sourceLocation = nil
    end
end

-- =============================================================================
-- HIGH-LEVEL ITEM SOURCE API
-- =============================================================================

-- Get all source info for an item (may have multiple sources)
function SM:GetItemSources(itemID)
    if not itemID then return {} end
    
    local sources = {}
    local foundFromAPI = false
    
    -- First, try to get appearance info from Blizzard API
    if C_TransmogCollection and C_TransmogCollection.GetItemInfo then
        local appearanceID, sourceID = C_TransmogCollection.GetItemInfo(itemID)
        
        if sourceID then
            local info = self:GetSourceInfo(sourceID)
            if info then
                sources[#sources + 1] = info
                foundFromAPI = true
            end
        end
        
        -- Get all sources for this appearance (other items with same look)
        if appearanceID and C_TransmogCollection.GetAppearanceSources then
            local appSources = C_TransmogCollection.GetAppearanceSources(appearanceID)
            if appSources then
                for _, appSource in ipairs(appSources) do
                    if appSource.sourceID and appSource.sourceID ~= sourceID then
                        local info = self:GetSourceInfo(appSource.sourceID)
                        if info then
                            sources[#sources + 1] = info
                        end
                    end
                end
            end
        end
    end
    
    -- If we got sources from API, enrich from static using requested itemID
    -- API may return source for different item (same appearance); always re-enrich so we show correct boss for the item being viewed
    if foundFromAPI and #sources > 0 then
        for _, source in ipairs(sources) do
            self:EnrichFromStaticData(source, itemID)
        end
        
        -- Prefer SourceData_ATT for the exact item being viewed (correct tier boss mapping)
        -- Use priority 0 so it always sorts first over API sources (priority 1)
        -- Supports SOURCES_MULTI: items that drop from multiple bosses
        if ETB.SourceData_ATT then
            local attSources = ETB.SourceData_ATT:GetAllSources(itemID)
            if #attSources > 0 then
                local primary = attSources[1]
                local typeMap = {
                    BOSS = 1, QUEST = 2, VENDOR = 3, WORLD_DROP = 4,
                    ACHIEVEMENT = 5, CRAFTED = 6, PROFESSION = 6,
                    UNAVAILABLE = 7, PVP = 3,
                }
                local typeInfo = SOURCE_TYPE_INFO[typeMap[primary.type]] or UNKNOWN_SOURCE
                local bossNames = {}
                for _, s in ipairs(attSources) do
                    if s.name and s.name ~= "Unknown" then
                        bossNames[#bossNames + 1] = s.name
                    end
                end
                local sourceName = #bossNames > 0 and table.concat(bossNames, " / ") or primary.name
                table.insert(sources, 1, {
                    itemID = itemID,
                    sourceName = sourceName,
                    sourceLocation = primary.location,
                    sourceType = typeMap[primary.type],
                    sourceTypeInfo = typeInfo,
                    instanceID = primary.instanceID,
                    encounterID = primary.encounterID,
                    difficultyID = primary.difficultyID,
                    isWorldBoss = primary.isWorldBoss,
                    isStatic = true,
                    _sortPriority = 0,
                })
            end
        end
    end
    
    -- If no API data, fall back entirely to MapperBridge
    if #sources == 0 then
        if ETB.SourceData_ATT then
            local attSources = ETB.SourceData_ATT:GetAllSources(itemID)
            if #attSources > 0 then
                local primary = attSources[1]
                local typeMap = {
                    BOSS = 1, QUEST = 2, VENDOR = 3, WORLD_DROP = 4,
                    ACHIEVEMENT = 5, CRAFTED = 6, PROFESSION = 6,
                    UNAVAILABLE = 7, PVP = 3,
                }
                local bossNames = {}
                for _, s in ipairs(attSources) do
                    if s.name and s.name ~= "Unknown" then
                        bossNames[#bossNames + 1] = s.name
                    end
                end
                local sourceName = #bossNames > 0 and table.concat(bossNames, " / ") or primary.name
                sources[#sources + 1] = {
                    itemID = itemID,
                    sourceName = sourceName,
                    sourceLocation = primary.location,
                    sourceType = typeMap[primary.type],
                    sourceTypeInfo = SOURCE_TYPE_INFO[typeMap[primary.type]] or UNKNOWN_SOURCE,
                    instanceID = primary.instanceID,
                    encounterID = primary.encounterID,
                    difficultyID = primary.difficultyID,
                    isWorldBoss = primary.isWorldBoss,
                    isStatic = true,
                }
            end
        end
    end
    
    -- Sort by priority (lower = first); prefer static/ATT source when _sortPriority set
    table.sort(sources, function(a, b)
        local priorityA = a._sortPriority or (a.sourceTypeInfo and a.sourceTypeInfo.priority) or 100
        local priorityB = b._sortPriority or (b.sourceTypeInfo and b.sourceTypeInfo.priority) or 100
        return priorityA < priorityB
    end)
    
    return sources
end

-- Get the "best" (primary) source for an item
function SM:GetPrimarySource(itemID)
    local sources = self:GetItemSources(itemID)
    return sources[1]
end

-- =============================================================================
-- FORMATTING FUNCTIONS
-- =============================================================================

-- Format source for display in tooltips
function SM:FormatSourceTooltip(itemID)
    local source = self:GetPrimarySource(itemID)
    if not source then return nil end
    
    local lines = {}
    local typeInfo = source.sourceTypeInfo or UNKNOWN_SOURCE
    
    -- Get extended data from MapperBridge (has all 39 fields)
    local ext = ETB.SourceData_ATT and ETB.SourceData_ATT:GetPrimarySource(itemID) or nil
    
    -- Source type line
    lines[#lines + 1] = typeInfo.color .. typeInfo.name .. "|r"
    
    -- Source name and location (fallback to MapperBridge for multi-boss)
    local bossName = source.sourceName
    local location = source.sourceLocation
    if ((not bossName or bossName == "Unknown") or (not location or location == "")) and ETB.SourceData_ATT then
        local attSources = ETB.SourceData_ATT:GetAllSources(itemID)
        if #attSources > 0 then
            if not bossName or bossName == "Unknown" then
                local names = {}
                for _, s in ipairs(attSources) do
                    if s.name and s.name ~= "Unknown" then names[#names + 1] = s.name end
                end
                bossName = #names > 0 and table.concat(names, " / ") or attSources[1].name
            end
            if (not location or location == "") and attSources[1].location then
                location = attSources[1].location
            end
        end
    end
    if bossName and bossName ~= "Unknown" then
        lines[#lines + 1] = "|cffffd100From:|r " .. bossName
    end
    if location and location ~= "" then
        lines[#lines + 1] = "|cffffd100Location:|r " .. location
    end
    
    -- Difficulty
    local diffID = source.difficultyID or (ext and ext.difficultyID) or nil
    if diffID and ETB.InstanceData then
        local diffName = ETB.InstanceData:GetDifficultyName(diffID)
        if diffName and diffName ~= "Unknown" then
            lines[#lines + 1] = "|cffffd100Difficulty:|r " .. diffName
        end
    end
    
    -- Extended fields from mapper data
    if ext then
        -- Coordinates (farming location)
        if ext.coordinates and ETB.SourceData_ATT.FormatCoords then
            local coordStr = ETB.SourceData_ATT:FormatCoords(ext.coordinates)
            if coordStr then
                lines[#lines + 1] = "|cffffd100Coords:|r " .. coordStr
            end
        end
        
        -- Additional zones (for world drops)
        if ext.zones and #ext.zones > 1 then
            local extra = {}
            for _, z in ipairs(ext.zones) do
                if z ~= ext.zone then extra[#extra + 1] = z end
            end
            if #extra > 0 then
                local str = table.concat(extra, ", ")
                if #str > 50 then str = str:sub(1, 47) .. "..." end
                lines[#lines + 1] = "|cffffd100Also in:|r " .. str
            end
        end
        
        -- Cost (currency, items, gold)
        if ext.costs and ETB.SourceData_ATT.FormatCosts then
            local costStr = ETB.SourceData_ATT:FormatCosts(ext.costs)
            if costStr then
                lines[#lines + 1] = "|cffffd100Cost:|r " .. costStr
            end
        end
        
        -- Reputation requirement
        if ext.repRequirement and ETB.SourceData_ATT.FormatReputation then
            local repStr = ETB.SourceData_ATT:FormatReputation(ext)
            if repStr then
                lines[#lines + 1] = "|cffffd100Reputation:|r " .. repStr
            end
        end
        
        -- Class restrictions
        if ext.classRestrictions and ETB.SourceData_ATT.FormatClasses then
            local classStr = ETB.SourceData_ATT:FormatClasses(ext.classRestrictions)
            if classStr then
                lines[#lines + 1] = "|cffffd100Classes:|r " .. classStr
            end
        end
        
        -- Tier token info
        if ext.tokenInfo and ETB.SourceData_ATT.FormatToken then
            local tokStr = ETB.SourceData_ATT:FormatToken(ext)
            if tokStr then
                lines[#lines + 1] = "|cffff8000Token:|r " .. tokStr
            end
        elseif ETB.SourceData_ATT and ETB.SourceData_ATT.GetTierToken then
            local tokenName = ETB.SourceData_ATT:GetTierToken(itemID)
            if tokenName then
                lines[#lines + 1] = "|cffff8000Token:|r " .. tokenName
            end
        end
        
        -- Crafting info
        if ext.craftingInfo and ETB.SourceData_ATT.FormatCrafting then
            local craftStr = ETB.SourceData_ATT:FormatCrafting(ext)
            if craftStr then
                lines[#lines + 1] = "|cffff7c0aCraft:|r " .. craftStr
            end
        end
        
        -- Required level
        if ext.reqLevel and ext.reqLevel > 0 then
            lines[#lines + 1] = "|cffffd100Req Level:|r " .. ext.reqLevel
        end
    end
    
    return lines
end

-- Format source for single-line display
function SM:FormatSourceShort(itemID)
    local source = self:GetPrimarySource(itemID)
    if not source then return nil end
    
    local typeInfo = source.sourceTypeInfo or UNKNOWN_SOURCE
    
    local parts = {}
    
    if source.sourceName then
        parts[#parts + 1] = source.sourceName
    end
    
    if source.sourceLocation then
        parts[#parts + 1] = "(" .. source.sourceLocation .. ")"
    end
    
    if #parts == 0 then
        return typeInfo.color .. typeInfo.name .. "|r"
    end
    
    return typeInfo.color .. table.concat(parts, " ") .. "|r"
end

-- Format source with icon
function SM:FormatSourceWithIcon(itemID, iconSize)
    iconSize = iconSize or 16
    
    local source = self:GetPrimarySource(itemID)
    if not source then return nil end
    
    local typeInfo = source.sourceTypeInfo or UNKNOWN_SOURCE
    local icon = string.format("|T%s:%d:%d|t", typeInfo.icon, iconSize, iconSize)
    
    return icon .. " " .. self:FormatSourceShort(itemID)
end

-- =============================================================================
-- SOURCE TYPE UTILITIES
-- =============================================================================

function SM:GetSourceType(sourceTypeID)
    return SOURCE_TYPE_INFO[sourceTypeID] or UNKNOWN_SOURCE
end

function SM:GetSourceTypeByName(name)
    if not name then return nil end
    
    local searchName = string.upper(name)
    
    for id, info in pairs(SOURCE_TYPE_INFO) do
        if info.type == searchName or 
           string.upper(info.name) == searchName or
           string.upper(info.shortName) == searchName then
            return info
        end
    end
    
    return nil
end

function SM:GetAllSourceTypes()
    local types = {}
    for id, info in pairs(SOURCE_TYPE_INFO) do
        types[#types + 1] = {
            id = id,
            type = info.type,
            name = info.name,
            shortName = info.shortName,
            color = info.color,
            priority = info.priority,
        }
    end
    table.sort(types, function(a, b) return a.priority < b.priority end)
    return types
end

-- =============================================================================
-- CACHE MANAGEMENT
-- =============================================================================

function SM:ClearCache()
    if self.cache then
        self.cache:Clear()
    end
    ETB:Debug("SourceMapper", "Cache cleared")
end

function SM:GetCacheStats()
    if not self.cache then
        return { size = 0, maxSize = 0, hitRate = 0 }
    end
    return self.cache:GetStats()
end

-- =============================================================================
-- DEBUG & STATS
-- =============================================================================

function SM:GetStats()
    return {
        queries = self.stats.queries,
        cacheHits = self.stats.cacheHits,
        cacheMisses = self.stats.cacheMisses,
        apiCalls = self.stats.apiCalls,
        overrideHits = self.stats.overrideHits,
        hitRate = self.stats.queries > 0 
            and (self.stats.cacheHits / self.stats.queries * 100) 
            or 0,
    }
end

function SM:PrintStats()
    local stats = self:GetStats()
    ETB:Print("|cff00ffff[SourceMapper]|r Statistics:")
    ETB:Print(string.format("  Queries: %d", stats.queries))
    ETB:Print(string.format("  Cache Hits: %d (%.1f%%)", stats.cacheHits, stats.hitRate))
    ETB:Print(string.format("  Cache Misses: %d", stats.cacheMisses))
    ETB:Print(string.format("  API Calls: %d", stats.apiCalls))
    ETB:Print(string.format("  Override Hits: %d", stats.overrideHits))
    
    if self.cache then
        local cacheStats = self.cache:GetStats()
        ETB:Print(string.format("  Cache Size: %d/%d", cacheStats.size, cacheStats.maxSize))
    end
end

-- Test function to verify source mapping for an item
function SM:TestItem(itemID)
    ETB:Print(string.format("|cff00ffff[SourceMapper]|r Testing item %d", itemID))
    
    local sources = self:GetItemSources(itemID)
    
    if #sources == 0 then
        ETB:Print("  |cffff0000No sources found!|r")
        return
    end
    
    ETB:Print(string.format("  Found %d source(s):", #sources))
    
    for i, source in ipairs(sources) do
        local typeInfo = source.sourceTypeInfo or UNKNOWN_SOURCE
        ETB:Print(string.format("  [%d] %s%s|r", i, typeInfo.color, typeInfo.name))
        
        if source.sourceName then
            ETB:Print(string.format("      Name: %s", source.sourceName))
        end
        if source.sourceLocation then
            ETB:Print(string.format("      Location: %s", source.sourceLocation))
        end
        if source.isCollected ~= nil then
            ETB:Print(string.format("      Collected: %s", tostring(source.isCollected)))
        end
        if source.isStatic then
            ETB:Print("      |cffff8000(from static data)|r")
        end
    end
    
    -- Also show formatted versions
    ETB:Print("  Formatted short: " .. (self:FormatSourceShort(itemID) or "nil"))
end

ETB:Debug("SourceMapper", "SourceMapper.lua loaded")
