--[[
============================================================================
ETB ColorSearch - Color-based transmog search and sorting
============================================================================
Uses RGB color data extracted from MogIt v5.5.3 (15,131 displayIDs).
Each displayID has 2-3 hex colors representing the item's visual appearance:
  c1 = base/shadow tone, c2 = primary material, c3 = highlight/accent

Provides:
  - GetDisplayColors(displayID) -> {r,g,b}, {r,g,b}, {r,g,b} or nil
  - GetItemColors(itemID) -> same (resolves itemID -> displayID first)
  - ColorDistance(r1,g1,b1, r2,g2,b2) -> number (Euclidean RGB distance²)
  - ScoreItem(itemID, targetR, targetG, targetB) -> distance (0 = exact match)
  - ScoreDisplay(displayID, targetR, targetG, targetB) -> distance
  - MatchesCategory(itemID, categoryID) -> bool
  - SortByColor(itemList, targetR, targetG, targetB) -> sorted list
  - GetCategoryForDisplay(displayID) -> categoryID or nil

Algorithm: Euclidean distance in RGB space, min across all 3 color channels.
Same approach as MogIt's "Approximate Colour" sort.
============================================================================
]]--

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

ETB.ColorSearch = ETB.ColorSearch or {}
local CS = ETB.ColorSearch

-- Parse cache: displayID -> {{r,g,b}, {r,g,b}, {r,g,b}}
CS.parseCache = {}
CS.parseCacheSize = 0
CS.maxCacheSize = 2000

-- =============================================================================
-- COLOR CATEGORIES (preset targets for dropdown filtering)
-- =============================================================================

CS.Categories = {
    { id = "RED",    name = "Red",    r = 200, g = 40,  b = 40,  threshold = 25000 },
    { id = "ORANGE", name = "Orange", r = 220, g = 130, b = 30,  threshold = 25000 },
    { id = "YELLOW", name = "Yellow", r = 220, g = 200, b = 50,  threshold = 25000 },
    { id = "GREEN",  name = "Green",  r = 50,  g = 180, b = 50,  threshold = 25000 },
    { id = "TEAL",   name = "Teal",   r = 40,  g = 170, b = 170, threshold = 25000 },
    { id = "BLUE",   name = "Blue",   r = 50,  g = 80,  b = 210, threshold = 25000 },
    { id = "PURPLE", name = "Purple", r = 140, g = 50,  b = 200, threshold = 25000 },
    { id = "PINK",   name = "Pink",   r = 220, g = 100, b = 150, threshold = 25000 },
    { id = "WHITE",  name = "White",  r = 230, g = 230, b = 230, threshold = 15000 },
    { id = "BLACK",  name = "Black",  r = 30,  g = 30,  b = 30,  threshold = 12000 },
    { id = "BROWN",  name = "Brown",  r = 140, g = 90,  b = 45,  threshold = 20000 },
    { id = "GRAY",   name = "Gray",   r = 130, g = 130, b = 130, threshold = 15000 },
    { id = "GOLD",   name = "Gold",   r = 200, g = 170, b = 50,  threshold = 20000 },
}

-- Quick lookup
CS.CategoryByID = {}
for _, cat in ipairs(CS.Categories) do
    CS.CategoryByID[cat.id] = cat
end

-- =============================================================================
-- CORE: Parse hex string into RGB tables
-- =============================================================================

local function HexToRGB(hex)
    if not hex or #hex ~= 6 then return nil end
    local r = tonumber(hex:sub(1, 2), 16)
    local g = tonumber(hex:sub(3, 4), 16)
    local b = tonumber(hex:sub(5, 6), 16)
    if r and g and b then
        return { r = r, g = g, b = b }
    end
    return nil
end

function CS:ParseDisplayColors(displayID)
    if self.parseCache[displayID] then
        return self.parseCache[displayID]
    end
    
    if not ETB.ColorData or not ETB.ColorData.data then return nil end
    
    local raw = ETB.ColorData.data[displayID]
    if not raw then return nil end
    
    -- Format: "RRGGBB|RRGGBB|RRGGBB" or "RRGGBB|RRGGBB"
    local parts = {}
    for hex in raw:gmatch("[^|]+") do
        local rgb = HexToRGB(hex)
        if rgb then
            parts[#parts + 1] = rgb
        end
    end
    
    if #parts == 0 then return nil end
    
    -- Cache management
    if self.parseCacheSize >= self.maxCacheSize then
        -- Simple eviction: wipe half
        local count = 0
        for k in pairs(self.parseCache) do
            if count > self.maxCacheSize / 2 then break end
            self.parseCache[k] = nil
            count = count + 1
        end
        self.parseCacheSize = self.parseCacheSize - count
    end
    
    self.parseCache[displayID] = parts
    self.parseCacheSize = self.parseCacheSize + 1
    
    return parts
end

-- =============================================================================
-- PUBLIC API
-- =============================================================================

--- Get RGB colors for a displayID
-- @return table of {r,g,b} tables, or nil
function CS:GetDisplayColors(displayID)
    if not displayID then return nil end
    return self:ParseDisplayColors(displayID)
end

--- Get RGB colors for an itemID (resolves to displayID first)
-- @return table of {r,g,b} tables, or nil
function CS:GetItemColors(itemID)
    if not itemID then return nil end
    
    -- Get displayID from GetItemInfoInstant
    local _, _, _, _, _, _, _, _, _, _, _, _, _, _, displayID = GetItemInfoInstant(itemID)
    
    -- Fallback: try C_TransmogCollection
    if not displayID or displayID == 0 then
        if C_TransmogCollection and C_TransmogCollection.GetItemInfo then
            local appearanceID = C_TransmogCollection.GetItemInfo(itemID)
            if appearanceID then
                -- appearanceID is sometimes the displayID
                local colors = self:ParseDisplayColors(appearanceID)
                if colors then return colors end
            end
        end
        return nil
    end
    
    return self:ParseDisplayColors(displayID)
end

--- Compute squared Euclidean distance between two RGB colors
-- @return number (0 = exact match, 195075 = max distance black↔white)
function CS:ColorDistance(r1, g1, b1, r2, g2, b2)
    return (r1 - r2) ^ 2 + (g1 - g2) ^ 2 + (b1 - b2) ^ 2
end

--- Score how well a displayID matches a target color
-- Returns the MINIMUM distance across all color channels (c1/c2/c3)
-- @return number (lower = better match), or 195075 (max) if no data
function CS:ScoreDisplay(displayID, targetR, targetG, targetB)
    local colors = self:ParseDisplayColors(displayID)
    if not colors then return 195075 end
    
    local minDist = 195075
    for _, c in ipairs(colors) do
        local dist = self:ColorDistance(targetR, targetG, targetB, c.r, c.g, c.b)
        if dist < minDist then
            minDist = dist
        end
    end
    return minDist
end

--- Score how well an itemID matches a target color
-- @return number (lower = better match)
function CS:ScoreItem(itemID, targetR, targetG, targetB)
    if not itemID then return 195075 end
    
    local _, _, _, _, _, _, _, _, _, _, _, _, _, _, displayID = GetItemInfoInstant(itemID)
    if displayID and displayID > 0 then
        return self:ScoreDisplay(displayID, targetR, targetG, targetB)
    end
    return 195075
end

--- Check if a displayID matches a named color category
-- @param categoryID string (e.g. "RED", "BLUE", "GOLD")
-- @return boolean
function CS:DisplayMatchesCategory(displayID, categoryID)
    local cat = self.CategoryByID[categoryID]
    if not cat then return false end
    
    local score = self:ScoreDisplay(displayID, cat.r, cat.g, cat.b)
    return score <= cat.threshold
end

--- Check if an itemID matches a named color category
function CS:ItemMatchesCategory(itemID, categoryID)
    if not itemID then return false end
    local _, _, _, _, _, _, _, _, _, _, _, _, _, _, displayID = GetItemInfoInstant(itemID)
    if displayID and displayID > 0 then
        return self:DisplayMatchesCategory(displayID, categoryID)
    end
    return false
end

--- Get the best-matching color category for a displayID
-- @return categoryID string, or nil
function CS:GetCategoryForDisplay(displayID)
    local colors = self:ParseDisplayColors(displayID)
    if not colors then return nil end
    
    local bestCat = nil
    local bestScore = 195075
    
    for _, cat in ipairs(self.Categories) do
        local score = self:ScoreDisplay(displayID, cat.r, cat.g, cat.b)
        if score < bestScore and score <= cat.threshold then
            bestScore = score
            bestCat = cat.id
        end
    end
    
    return bestCat
end

--- Sort an item list by color similarity to a target
-- Items without color data sort to the end
-- @param items table (array of {itemID=x, displayID=y, ...} or just itemIDs)
-- @param targetR, targetG, targetB numbers (0-255)
-- @return sorted table (modifies in place and returns)
function CS:SortByColor(items, targetR, targetG, targetB)
    -- Pre-compute scores for performance
    local scores = {}
    for i, item in ipairs(items) do
        local displayID
        if type(item) == "table" then
            displayID = item.displayID
            if not displayID and item.itemID then
                local _, _, _, _, _, _, _, _, _, _, _, _, _, _, did = GetItemInfoInstant(item.itemID)
                displayID = did
            end
        elseif type(item) == "number" then
            local _, _, _, _, _, _, _, _, _, _, _, _, _, _, did = GetItemInfoInstant(item)
            displayID = did
        end
        
        if displayID and displayID > 0 then
            scores[i] = self:ScoreDisplay(displayID, targetR, targetG, targetB)
        else
            scores[i] = 195075
        end
    end
    
    -- Sort by score (ascending = closest match first)
    local indices = {}
    for i = 1, #items do indices[i] = i end
    table.sort(indices, function(a, b) return scores[a] < scores[b] end)
    
    local sorted = {}
    for i, idx in ipairs(indices) do
        sorted[i] = items[idx]
    end
    
    -- Copy back
    for i, item in ipairs(sorted) do
        items[i] = item
    end
    
    return items
end

--- Filter an item list to only items matching a color category
-- @param items table (array)
-- @param categoryID string
-- @return filtered table (new array)
function CS:FilterByCategory(items, categoryID)
    local cat = self.CategoryByID[categoryID]
    if not cat then return items end
    
    local filtered = {}
    for _, item in ipairs(items) do
        local displayID
        if type(item) == "table" then
            displayID = item.displayID
            if not displayID and item.itemID then
                local _, _, _, _, _, _, _, _, _, _, _, _, _, _, did = GetItemInfoInstant(item.itemID)
                displayID = did
            end
        elseif type(item) == "number" then
            local _, _, _, _, _, _, _, _, _, _, _, _, _, _, did = GetItemInfoInstant(item)
            displayID = did
        end
        
        if displayID and displayID > 0 then
            local score = self:ScoreDisplay(displayID, cat.r, cat.g, cat.b)
            if score <= cat.threshold then
                filtered[#filtered + 1] = item
            end
        end
    end
    
    return filtered
end

--- Get a color swatch texture color for a displayID (for UI display)
-- Returns the most "interesting" (most saturated) of the 3 colors
-- @return r, g, b (0-1 range) or nil
function CS:GetSwatchColor(displayID)
    local colors = self:ParseDisplayColors(displayID)
    if not colors then return nil end
    
    local bestSat = -1
    local bestR, bestG, bestB = 0.5, 0.5, 0.5
    
    for _, c in ipairs(colors) do
        local maxC = math.max(c.r, c.g, c.b)
        local minC = math.min(c.r, c.g, c.b)
        local sat = (maxC > 0) and ((maxC - minC) / maxC) or 0
        -- Weight by brightness too (avoid pitch-black being "most saturated")
        local lum = (c.r + c.g + c.b) / 3
        local score = sat * 0.7 + (lum / 255) * 0.3
        if score > bestSat then
            bestSat = score
            bestR = c.r / 255
            bestG = c.g / 255
            bestB = c.b / 255
        end
    end
    
    return bestR, bestG, bestB
end

--- Check if color data is available
function CS:HasData()
    return ETB.ColorData and ETB.ColorData.data and ETB.ColorData.count and ETB.ColorData.count > 0
end

--- Get stats
function CS:GetStats()
    local dataCount = ETB.ColorData and ETB.ColorData.count or 0
    return {
        totalDisplayIDs = dataCount,
        cacheSize = self.parseCacheSize,
        categories = #self.Categories,
    }
end

ETB:Debug("ColorSearch", "ColorSearch module loaded")
