-- VDS_StaticData
-- ============================================================================
-- Pure data / lookup helpers. Lifted from the non-event surface of the donor's
-- VDS_DyeData.lua. No state mutation, no Blizzard events, no dispatch -- this
-- module is callable from selectors (it's all pure data + helpers).
--
-- Event-driven ownership scanning lives in Modules/VDS_DyeOwnershipObserver.

VDS = VDS or {}
VDS.StaticData = VDS.StaticData or {}

local SD = VDS.StaticData

-- ============================================================================
-- Color groups (hue-based palette buckets)
-- ============================================================================

SD.COLOR_GROUP_ORDER = {
    "Red", "Orange", "Yellow", "Green", "Teal", "Blue",
    "Purple", "Pink", "Brown", "Grey", "Black", "White",
}

SD.COLOR_GROUP_RGB = {
    Red    = { r = 1.00, g = 0.15, b = 0.15 },
    Orange = { r = 1.00, g = 0.55, b = 0.10 },
    Yellow = { r = 1.00, g = 0.95, b = 0.15 },
    Green  = { r = 0.20, g = 0.90, b = 0.20 },
    Teal   = { r = 0.10, g = 0.90, b = 0.85 },
    Blue   = { r = 0.25, g = 0.40, b = 1.00 },
    Purple = { r = 0.65, g = 0.25, b = 1.00 },
    Pink   = { r = 1.00, g = 0.45, b = 0.70 },
    Brown  = { r = 0.65, g = 0.40, b = 0.15 },
    Grey   = { r = 0.55, g = 0.55, b = 0.55 },
    Black  = { r = 0.18, g = 0.18, b = 0.18 },
    White  = { r = 0.95, g = 0.95, b = 0.95 },
}

-- Name -> group map in Data/VDS_ColorMapDB.lua (community-verified by Kiyo; donor VDS_DyeData.lua:121-196).
-- HSV classifier below is the fallback for future Blizzard dyes.
-- __index proxy lets tests dofile this module before Data globals are populated.
SD.COLOR_NAME_MAP = setmetatable({}, {
    __index = function(_, name)
        local db = _G.VDS_ColorMapDB
        return db and db.byName and db.byName[name] or nil  -- exception(boundary): tests dofile this module before Data globals load
    end,
})

-- ============================================================================
-- RGB <-> HSV (used to classify dyes without an explicit name -> group entry)
-- ============================================================================

local function RGBtoHSV(r, g, b)
    local max = math.max(r, g, b)
    local min = math.min(r, g, b)
    local delta = max - min
    local h, s, v = 0, 0, max
    if max > 0 then s = delta / max end
    if delta > 0 then
        if max == r then
            h = 60 * (((g - b) / delta) % 6)
        elseif max == g then
            h = 60 * ((b - r) / delta + 2)
        else
            h = 60 * ((r - g) / delta + 4)
        end
    end
    if h < 0 then h = h + 360 end
    return h, s, v
end

function SD.ClassifyByHSV(r, g, b)
    local h, s, v = RGBtoHSV(r, g, b)
    if v < 0.20 then return "Black" end
    if s < 0.12 and v > 0.70 then return "White" end
    if s < 0.15 then return "Grey" end
    if h >= 10 and h < 45 and v < 0.55 then return "Brown" end
    if h < 15 or h >= 340 then return "Red" end
    if h < 40 then return "Orange" end
    if h < 70 then return "Yellow" end
    if h < 165 then return "Green" end
    if h < 200 then return "Teal" end
    if h < 265 then return "Blue" end
    if h < 300 then return "Purple" end
    return "Pink"
end

-- Public classifier: explicit map first, HSV fallback.
function SD.Classify(name, r, g, b)
    if name and SD.COLOR_NAME_MAP[name] then return SD.COLOR_NAME_MAP[name] end
    return SD.ClassifyByHSV(r, g, b)
end

-- ============================================================================
-- Crafting facade (Data/VDS_DyeCraftingDB.lua + VDS_DecorDyeSlotsDB.lua load before this per .toc)
-- ============================================================================

function SD.GetPigmentForDye(dyeItemID)
    local pigmentID = _G.VDS_DyeCraftingDB.dyeRecipes[dyeItemID]
    if not pigmentID then return nil end                          -- boundary: unknown dye = nil
    return pigmentID, _G.VDS_DyeCraftingDB.pigments[pigmentID]
end

function SD.GetHerbsForDye(dyeItemID)
    local pigmentID = _G.VDS_DyeCraftingDB.dyeRecipes[dyeItemID]
    if not pigmentID then return nil end                          -- boundary: unknown dye = nil
    return _G.VDS_DyeCraftingDB.pigments[pigmentID].herbs        -- strict: pigmentID resolved => entry exists
end

-- Returns { pigmentID, herbItemIDs, qtyPerCraft } or nil (no recipe = silently skipped in craft mode).
function SD.GetDyeRecipe(dyeItemID)
    local db = _G.VDS_DyeCraftingDB
    if not db or not db.dyeRecipes then return nil end
    local pigmentID = db.dyeRecipes[dyeItemID]
    if not pigmentID then return nil end
    local pigment = db.pigments[pigmentID]
    if not pigment then return nil end
    return {
        pigmentID   = pigmentID,
        herbItemIDs = pigment.herbs or {},
        qtyPerCraft = pigment.herbQty or 1,  -- exception(optional): herbQty optional in pigment DB; default 1/craft
    }
end

-- Returns { itemID, unitPrice } or nil. Selectors re-fire on reads invalidation, picking up any Auctionator cache updates.
function SD.PickCheapestHerb(herbItemIDs)
    if type(herbItemIDs) ~= "table" or #herbItemIDs == 0 then return nil end
    local bestID, bestPrice
    for _, itemID in ipairs(herbItemIDs) do
        local price = SD.GetAuctionPrice(itemID)
        if price and (not bestPrice or price < bestPrice) then
            bestID, bestPrice = itemID, price
        end
    end
    if bestID then return { itemID = bestID, unitPrice = bestPrice } end
    return nil
end

function SD.GetDecorChannelSet(decorID)
    return _G.VDS_DecorDyeSlotsDB[decorID]
end

function SD.GetDecorChannelCount(decorID)
    local slots = _G.VDS_DecorDyeSlotsDB[decorID]
    return slots and #slots or nil                                -- boundary: unknown decor = nil
end

-- Raw pigments map: each entry = { name, category, herbQty, herbs = {itemIDs} }.
function SD.GetPigments()
    local db = _G.VDS_DyeCraftingDB
    return db and db.pigments or nil  -- exception(boundary): shipped Data global may be absent (tests, partial install)
end

-- 1-based expansion index (1=Classic..12=Midnight) -> {abbr, color}. Mirrors HDGR_Constants.EXPANSION_DATA.
function SD.GetHerbExpansion(expIdx)
    return _G.VDS_DyeCraftingDB.herbExpansions[expIdx]
end

-- ============================================================================
-- Auction House price facade -- boundary: third-party addon, gated by IsAddOnLoaded. Returns copper.
-- ============================================================================

local _auctionatorLoaded  -- tri-state nil/true/false; nil = not yet probed
local function isAuctionatorLoaded()
    if _auctionatorLoaded == nil then
        _auctionatorLoaded = _G.C_AddOns.IsAddOnLoaded("Auctionator") and true or false
    end
    return _auctionatorLoaded
end

function SD.GetAuctionPrice(itemID)
    if not itemID or not isAuctionatorLoaded() then return nil end
    local A = _G.Auctionator and _G.Auctionator.API and _G.Auctionator.API.v1  -- exception(boundary): Auctionator optional addon
    if not (A and A.GetAuctionPriceByItemID) then return nil end  -- exception(boundary): Auctionator API surface varies by version
    local ok, price = pcall(A.GetAuctionPriceByItemID, "VamoosesDyeStudio", itemID)  -- exception(fire-forget): API throw = no price; nil is the contract
    return ok and price or nil
end

function SD.FormatGold(copper)
    if not copper or copper <= 0 then return nil end
    local gold = math.floor(copper / 10000)
    if gold >= 1 then return gold .. "g" end
    local silver = math.floor((copper % 10000) / 100)
    return silver .. "s"
end

-- ============================================================================
-- Base64 encode/decode -- ported verbatim from VDS_DyeData.lua:828-873 for import/export parity.
-- ============================================================================

local b64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
local b64enc, b64dec = {}, {}
for i = 1, 64 do
    local c = b64chars:sub(i, i)
    b64enc[i - 1] = c
    b64dec[c:byte()] = i - 1
end

function SD.Base64Encode(data)
    local out = {}
    local len = #data
    for i = 1, len, 3 do
        local a = data:byte(i)
        local b = i + 1 <= len and data:byte(i + 1) or 0
        local c = i + 2 <= len and data:byte(i + 2) or 0
        out[#out + 1] = b64enc[bit.rshift(a, 2)]
        out[#out + 1] = b64enc[bit.bor(bit.lshift(bit.band(a, 3), 4), bit.rshift(b, 4))]
        out[#out + 1] = (i + 1 <= len) and b64enc[bit.bor(bit.lshift(bit.band(b, 15), 2), bit.rshift(c, 6))] or "="
        out[#out + 1] = (i + 2 <= len) and b64enc[bit.band(c, 63)] or "="
    end
    return table.concat(out)
end

function SD.Base64Decode(data)
    local out = {}
    data = data:gsub("[^A-Za-z0-9+/=]", "")
    for i = 1, #data, 4 do
        local a = b64dec[data:byte(i)] or 0  -- exception(boundary): user-pasted import string; malformed bytes decode to 0
        local b = b64dec[data:byte(i + 1)] or 0  -- exception(boundary): user-pasted import string
        local c = b64dec[data:byte(i + 2)] or 0  -- exception(boundary): user-pasted import string
        local d = b64dec[data:byte(i + 3)] or 0  -- exception(boundary): user-pasted import string
        out[#out + 1] = string.char(bit.bor(bit.lshift(a, 2), bit.rshift(b, 4)))
        if data:sub(i + 2, i + 2) ~= "=" then
            out[#out + 1] = string.char(bit.band(bit.bor(bit.lshift(b, 4), bit.rshift(c, 2)), 0xFF))
        end
        if data:sub(i + 3, i + 3) ~= "=" then
            out[#out + 1] = string.char(bit.band(bit.bor(bit.lshift(c, 6), d), 0xFF))
        end
    end
    return table.concat(out)
end

-- ============================================================================
-- Themed + class palettes (Data/VDS_PaletteDB.lua). Used by the Theme tab.
-- ============================================================================
function SD.GetThemedPalettes() return _G.VDS_PaletteDB.THEMED_PALETTES end
function SD.GetClassPalettes()  return _G.VDS_PaletteDB.CLASS_PALETTES  end

-- Themed first, then class palettes.
function SD.GetPaletteByName(name)
    for _, p in ipairs(_G.VDS_PaletteDB.THEMED_PALETTES) do
        if p.name == name then return p end
    end
    for _, p in ipairs(_G.VDS_PaletteDB.CLASS_PALETTES) do
        if p.name == name then return p end
    end
end

-- Returns { pigment, pigItemID, expIdx } or nil.
function SD.GetHerbPigmentEntry(herbID)
    if not herbID then return nil end
    local pigments = _G.VDS_DyeCraftingDB.pigments
    if not pigments then return nil end
    for pigItemID, pig in pairs(pigments) do
        for expIdx, h in ipairs(pig.herbs or {}) do
            if h == herbID then
                return { pigment = pig, pigItemID = pigItemID, expIdx = expIdx }
            end
        end
    end
end

-- Count of dyes using a given pigment.
function SD.GetDyeCountForPigment(pigItemID)
    if not pigItemID then return 0 end
    local recipes = _G.VDS_DyeCraftingDB.dyeRecipes
    if not recipes then return 0 end
    local n = 0
    for _, pID in pairs(recipes) do
        if pID == pigItemID then n = n + 1 end
    end
    return n
end

-- Reverses dyeRecipes (dyeItemID -> pigmentItemID) into per-pigment list ordered by COLOR_GROUP_ORDER.
function SD.GetDyesByPigment()
    local recipes = _G.VDS_DyeCraftingDB.dyeRecipes or {}
    local pigments = _G.VDS_DyeCraftingDB.pigments or {}
    local byCat = {}  -- [category] = { dyeItemID, dyeItemID, ... }
    for dyeItemID, pigmentItemID in pairs(recipes) do
        local pig = pigments[pigmentItemID]
        if pig and pig.category then  -- exception(nullable): pigment lookup can miss for unmapped recipes
            byCat[pig.category] = byCat[pig.category] or {}
            local list = byCat[pig.category]
            list[#list + 1] = dyeItemID
        end
    end
    return byCat
end

-- Keyed by specific pigmentItemID (not category): one category can map to multiple pigments;
-- herbs tab must show only dyes for the pigment THIS herb contributes to.
function SD.GetDyesByPigmentItem()
    local recipes = _G.VDS_DyeCraftingDB.dyeRecipes or {}
    local byPig = {}  -- [pigmentItemID] = { dyeItemID, dyeItemID, ... }
    for dyeItemID, pigmentItemID in pairs(recipes) do
        byPig[pigmentItemID] = byPig[pigmentItemID] or {}
        local list = byPig[pigmentItemID]
        list[#list + 1] = dyeItemID
    end
    return byPig
end
