-- data.lua
-- Pre-baked static indexes for instant, lag-free search
-- New Two-Stage Normalization Format

---@diagnostic disable: undefined-global
local ITEM_NAMES_LIST = ITEM_NAMES_LIST
local VENDOR_INDEX_LIST = VENDOR_INDEX_LIST
local ITEMLOCATIONSLIST = ITEMLOCATIONSLIST
local WDIGI_SOURCE_LOOKUP = WDIGI_SOURCE_LOOKUP

local ADDON_NAME, WDIGI = ...
WDIGI.Data = WDIGI.Data or {}

-- Control Characters
local RS = string.char(30) -- Record Separator (\x1e)
local US = string.char(31) -- Unit Separator (\x1f)

------------------------------------------------------------
-- VENDOR TYPE ENUM
------------------------------------------------------------
WDIGI.Data.VENDOR_TYPE = {
    SOLD_BY           = 1,
    DROPPED_BY        = 2,
    CONTAINED_IN      = 3,
    DISENCHANTED_FROM = 4,
    GATHERED_FROM     = 5,
    CRAFTED_BY        = 6,
    LEARNED_FROM      = 7,
    QUEST_REWARD      = 8,
    UNKNOWN           = 99,
}

------------------------------------------------------------
-- CACHE & STATE
------------------------------------------------------------
WDIGI.Data.Parsed = {}

------------------------------------------------------------
-- MONEY FORMATTER
------------------------------------------------------------
local function FormatMoney(copper)
    local amt = tonumber(copper) or 0
    if amt <= 0 then return "" end 

    local g = math.floor(amt / 10000)
    local s = math.floor((amt % 10000) / 100)
    local c = amt % 100

    local res = ""
    if g > 0 then res = res .. g .. "|cffffd700g|r " end
    if s > 0 then res = res .. s .. "|cffc7c7cfs|r " end
    if c > 0 or res == "" then res = res .. c .. "|cffeda55fc|r" end

    return res:trim()
end

------------------------------------------------------------
-- INTERNAL: Parse raw ITEMLOCATIONSLIST (The Inflator)
------------------------------------------------------------
local function ParseItemData(itemID)
    if WDIGI.Data.Parsed[itemID] then return WDIGI.Data.Parsed[itemID] end

    -- Use the global if it exists, otherwise a fallback empty table
    local itemsList = _G["ITEMLOCATIONSLIST"]
    local sourceLookup = _G["WDIGI_SOURCE_LOOKUP"]
    
    local raw = itemsList and itemsList[itemID]
    if type(raw) ~= "string" or raw == "" then return {} end

    local entries = {}
    local records = { strsplit(RS, raw) }

    for _, record in ipairs(records) do
        if record ~= "" then
            local sid, costVal, ver, dType, sID, sType = strsplit(US, record)
            
            -- Look up the NPC/Source data
            local sourceRaw = sourceLookup and sourceLookup[tonumber(sid)]
            if sourceRaw then
                local name, uMap, pMap, x, y = strsplit(US, sourceRaw)
                table.insert(entries, {
                    vendorName   = name or "Unknown",
                    mapID        = tonumber(uMap) or 0,
                    parentMapID  = tonumber(pMap) or 0,
                    tx           = tonumber(x) or 0,
                    ty           = tonumber(y) or 0,
                    cost         = costVal,
                    vendorType   = tonumber(dType) or 99,
                    npcID        = tonumber(sID) or 0,
                    sourceType   = sType or "",
                    clientVer    = ver or ""
                })
            end
        end
    end

    WDIGI.Data.Parsed[itemID] = entries
    return entries
end

------------------------------------------------------------
-- PUBLIC: Best location for navigation
------------------------------------------------------------
function WDIGI.Data.GetBestLocation(itemID)
    local entries = ParseItemData(itemID)
    if not entries or #entries == 0 then return nil end

    local myMapID, px, py = WDIGI.MapUtils.GetCurrentPlayerData()
    
    -- Fallback to first entry if player location is unknown
    if not myMapID or not px or not py then
        local e = entries[1] 
        return {
            vendorName = e.vendorName,
            zoneID     = e.mapID, -- consistent naming
            x          = e.tx,
            y          = e.ty,
            cost       = e.cost,
            vendorType = e.vendorType,
        }
    end

    local bestEntry, bestDist

    for _, e in ipairs(entries) do
        if e.mapID > 0 and e.tx > 0 then
            local onMap = WDIGI.MapUtils.IsSameZone(myMapID, e.mapID)

            if onMap then
                local dx = (e.tx / 100) - px
                local dy = (e.ty / 100) - py
                local dist = math.sqrt(dx * dx + dy * dy) * (WDIGI.MapUtils.GetZoneMultiplier() or 1000)

                if not bestDist or dist < bestDist then
                    bestDist  = dist
                    bestEntry = e
                end
            end
        end
    end

    local chosen = bestEntry or entries[1]
    return {
        vendorName = chosen.vendorName,
        zoneID     = chosen.mapID,
        x          = chosen.tx,
        y          = chosen.ty,
        cost       = chosen.cost,
        vendorType = chosen.vendorType,
    }
end

------------------------------------------------------------
-- PUBLIC: Universal Search Engine
------------------------------------------------------------
function WDIGI.Data.UniversalSearch(query)
    if not query or query == "" then return { items = {}, areas = {}, creatures = {} } end
    query = query:lower():trim()
    
    local results = { items = {}, areas = {}, creatures = {} }
    local seenVendors = {}

    local vendorIndex = _G["VENDOR_INDEX_LIST"]
    if vendorIndex then
        for vendorName, itemIds in pairs(vendorIndex) do
            if vendorName:find(query, 1, true) then
                if not seenVendors[vendorName] then
                    -- Return a simple string list
                    table.insert(results.creatures, vendorName)
                    seenVendors[vendorName] = true
                end
            end
            if #results.creatures >= 50 then break end
        end
    end
    
    -- 4. Search for ITEMS (Right Column)
    local namesList = _G["ITEM_NAMES_LIST"]
    if namesList then
        for id, name in pairs(namesList) do
            if name:lower():find(query, 1, true) then
                table.insert(results.items, id)
                if #results.items >= 60 then break end
            end
        end
    end

    return results
end

function WDIGI.Data.GetItemsByVendorName(vendorName)
    -- Normalize the name to match our lowercase index keys
    local key = vendorName:lower():trim()
    return VENDOR_INDEX_LIST and VENDOR_INDEX_LIST[key] or {}
end

------------------------------------------------------------
-- PUBLIC: Group by Item Source 
------------------------------------------------------------
function WDIGI.Data.GetGroupedSources(itemID)
    local sources = ParseItemData(itemID)
    local grouped = {}
    
    for _, s in ipairs(sources) do
        local mapInfo = C_Map.GetMapInfo(s.mapID)
        local zoneName = mapInfo and mapInfo.name or "Unknown Location"
        
        if not grouped[zoneName] then
            grouped[zoneName] = { 
                name = zoneName, 
                mapID = s.mapID, 
                entries = {} 
            }
        end
        table.insert(grouped[zoneName].entries, s)
    end
    
    return grouped
end

-- ============================================================================
-- WDIGI COMPENDIUM & TOOLTIP DATA PROCESSING UTILITIES
-- ============================================================================

--- Formats and colorizes raw string costs based on item source type
-- @param displayCost string The raw cost string from database ("0", "4.2", "50000", etc)
-- @param vendorType number The source mapping category ID (1 = Gold, 2 = Drop, etc)
-- @return string The escaped, colored markup text ready for UI rendering
function WDIGI.Data.GetFormattedCost(displayCost, vendorType)
    if not displayCost or displayCost == "" then return "" end

    -- 🎯 Anything except a standard gold vendor price is treated as a drop percentage
    if vendorType ~= 1 then
        local percentNum = 0
        
        if displayCost == "0" then
            displayCost = "<0.1%"
            percentNum = 0.05 
        else
            local sanitizedCost = displayCost:gsub("%%", "")
            percentNum = tonumber(sanitizedCost) or 0
            
            -- Clean formatting for messy float database decimals
            if not displayCost:find("%%") then
                if percentNum % 1 ~= 0 then
                    displayCost = string.format("%.1f%%", percentNum)
                else
                    displayCost = displayCost .. "%"
                end
            end
        end

        -- 🎨 GLOWING AMETHYST VARIANT COLOR SLIDE
        local factor = math.max(0, math.min(100, percentNum)) / 100
        local r_col = math.floor(207 - (207 * factor))
        local g_col = math.floor(122 + (96 * factor))
        local b_col = 255 

        return string.format("|cff%02x%02x%02x%s|r", r_col, g_col, b_col, displayCost)
    
    -- 💰 It's a standard gold store merchant vendor price
    elseif displayCost ~= "0" and not displayCost:find("%%") then
        local formattedGold = WDIGI.Data.FormatMoney(displayCost)
        return "|cffffd100" .. formattedGold .. "|r"
    end

    return ""
end

--- Takes a raw array of item IDs, parses metrics against a target source entity, and returns a sorted table
-- @param inventory table Array of numerical item IDs
-- @param targetVendor string The localized name of the target NPC/Source
-- @return table Pre-sorted, data-dense dictionary list
function WDIGI.Data.ProcessAndSortSources(inventory, targetVendor)
    local sortedInventory = {}
    local lowerTarget = tostring(targetVendor or ""):lower()

    for _, itemID in ipairs(inventory or {}) do
        local displayCost = ""
        local sortWeight = 0
        local isDrop = false
        local matchedSource = nil
        
        local itemData = WDIGI.Data.ParseItemData(itemID)
        local itemName = C_Item.GetItemNameByID(itemID) or ""

        for _, source in ipairs(itemData) do
            if tostring(source.vendorName or ""):lower() == lowerTarget then
                displayCost = source.cost or ""
                matchedSource = source
                
                if displayCost ~= "" then
                    if source.vendorType ~= 1 then 
                        isDrop = true
                        if displayCost == "0" then
                            sortWeight = 0.05
                        else
                            local sanitizedCost = displayCost:gsub("%%", "")
                            sortWeight = tonumber(sanitizedCost) or 0
                        end
                    else
                        sortWeight = 0
                    end
                end
                break
            end
        end

        table.insert(sortedInventory, {
            itemID = itemID,
            sortWeight = sortWeight,
            isDrop = isDrop,
            name = itemName:lower(),
            rawCost = displayCost,
            vendorType = matchedSource and matchedSource.vendorType or 99,
            sourceData = matchedSource -- Optional: Pass whole payload forward just in case coordinates are needed
        })
    end

    -- Smart multi-tiered sorting execution
    table.sort(sortedInventory, function(a, b)
        if a.isDrop and b.isDrop then
            if a.sortWeight ~= b.sortWeight then
                return a.sortWeight > b.sortWeight
            end
            return a.name < b.name
        elseif a.isDrop then
            return true
        elseif b.isDrop then
            return false
        else
            return a.name < b.name
        end
    end)

    return sortedInventory
end

--- Safely populates an item row with live client data or a database fallback name.
-- Automatically manages async server callbacks if data is uncached.
-- @param frame Table The frame/row widget containing .name and .icon elements
-- @param itemID Number The numerical item ID to look up
-- @param useLink Boolean True if you want to display the colored itemLink, false for raw name string
function WDIGI.Data.SafePopulateItemRow(frame, itemID, useLink)
    if not frame or not itemID or itemID == 0 then return end
    
    frame.id = itemID -- Bind the current ID to the frame for the event handler to check
    
    local itemName, itemLink, _, _, _, _, _, _, _, itemTexture = GetItemInfo(itemID)
    
    -- 📦 Fallback Check
    if not itemName then
        local fallbackName = ITEM_NAMES_LIST[itemID] or "Loading..."
        frame.name:SetText("|cff808080" .. fallbackName .. "|r") -- Gray text state
    else
        frame.name:SetText(useLink and itemLink or itemName)
    end
    
    frame.icon:SetTexture(itemTexture or 134400)
    
    -- 🔄 Asynchronous Callback Hook
    if not itemLink then
        frame:RegisterEvent("GET_ITEM_INFO_RECEIVED")
        frame:SetScript("OnEvent", function(s, event, incomingID, success)
            if event == "GET_ITEM_INFO_RECEIVED" and incomingID == s.id and success then
                local rName, rLink, _, _, _, _, _, _, _, rTex = GetItemInfo(s.id)
                if rLink then
                    s.name:SetText(useLink and rLink or rName)
                    s.icon:SetTexture(rTex or 134400)
                    s:UnregisterEvent("GET_ITEM_INFO_RECEIVED")
                    s:SetScript("OnEvent", nil)
                end
            end
        end)
    else
        -- Completely pristine state; clear stale old listeners
        frame:UnregisterEvent("GET_ITEM_INFO_RECEIVED")
        frame:SetScript("OnEvent", nil)
    end
end

------------------------------------------------------------
-- EXPORTS
------------------------------------------------------------
WDIGI.Data.ParseItemData = ParseItemData
WDIGI.Data.FormatMoney   = FormatMoney