-- HDG_StyleSerializer.lua
-- Import/Export custom styles as encoded strings
-- Format: HDG:1:<base64_payload>

local addonName, privateDB = ...
local HDG = _G.HousingDecorGuide

local StyleSerializer = {}
privateDB.HDG_StyleSerializer = StyleSerializer

-- ============================================================================
-- BASE64 ENCODE/DECODE (from VamoosesDyeStudio)
-- ============================================================================

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

local function 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

local function 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
        local b = b64dec[data:byte(i + 1)] or 0
        local c = b64dec[data:byte(i + 2)] or 0
        local d = b64dec[data:byte(i + 3)] or 0
        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

-- ============================================================================
-- ENCODE / DECODE FACET MAPS
-- ============================================================================

-- Encode a facet map { room = {"kitchen","garden"}, mood = {"cozy"} }
-- into "room=kitchen,garden;mood=cozy"
local function EncodeFacetMap(facetMap)
    if not facetMap or not next(facetMap) then return "" end
    local parts = {}
    local keys = {}
    for k in pairs(facetMap) do keys[#keys + 1] = k end
    table.sort(keys)
    for _, k in ipairs(keys) do
        local vals = facetMap[k]
        if type(vals) == "table" and #vals > 0 then
            parts[#parts + 1] = k .. "=" .. table.concat(vals, ",")
        end
    end
    return table.concat(parts, ";")
end

-- Decode "room=kitchen,garden;mood=cozy" into { room = {"kitchen","garden"}, mood = {"cozy"} }
local function DecodeFacetMap(str)
    if not str or str == "" then return {} end
    local result = {}
    for part in str:gmatch("[^;]+") do
        local facet, valStr = part:match("^([^=]+)=(.+)$")
        if facet and valStr then
            local vals = {}
            for v in valStr:gmatch("[^,]+") do
                vals[#vals + 1] = v
            end
            if #vals > 0 then
                result[facet] = vals
            end
        end
    end
    return result
end

-- ============================================================================
-- EXPORT
-- ============================================================================

function StyleSerializer:Export(styleDef)
    if not styleDef then return nil, "No style definition" end

    -- Pipe-delimited payload (pipes are safe since they get base64-encoded)
    -- Field 2 (iconIdx) and field 3 (colorStr) kept as placeholders for compat
    local payload = table.concat({
        styleDef.displayName or "Unnamed",
        "0",
        "0.5,0.5,0.5",
        styleDef.description or "",
        EncodeFacetMap(styleDef.query),
        EncodeFacetMap(styleDef.boost),
        EncodeFacetMap(styleDef.anti),
    }, "|")

    return "HDG:1:" .. Base64Encode(payload)
end

-- ============================================================================
-- IMPORT
-- ============================================================================

function StyleSerializer:Import(encodedStr)
    if not encodedStr or encodedStr == "" then
        return nil, "Empty string"
    end

    -- Route by version prefix
    if encodedStr:match("^HDG:2:") then
        return self:ImportSnapshot(encodedStr)
    end

    -- Check prefix
    if not encodedStr:match("^HDG:1:") then
        return nil, "Invalid format (expected HDG:1: or HDG:2:...)"
    end

    local b64 = encodedStr:sub(7)
    local ok, payload = pcall(Base64Decode, b64)
    if not ok or not payload or payload == "" then
        return nil, "Failed to decode"
    end

    -- Split by pipe
    local fields = {}
    for field in (payload .. "|"):gmatch("([^|]*)|") do
        fields[#fields + 1] = field
    end

    if #fields < 5 then
        return nil, "Not enough fields (got " .. #fields .. ")"
    end

    local name = fields[1]
    -- fields[2] (iconIdx) and fields[3] (colorStr) are legacy placeholders
    local desc = fields[4] or ""
    local queryStr = fields[5] or ""
    local boostStr = fields[6] or ""
    local antiStr = fields[7] or ""

    -- Validate name
    if not name or name == "" then
        return nil, "Name is empty"
    end
    local CONFIG = privateDB.CONFIG
    local SE = CONFIG and CONFIG.STYLE_EDITOR
    local maxLen = SE and SE.NAME_MAX_LENGTH or 40
    if #name > maxLen then
        name = name:sub(1, maxLen)
    end

    -- Icon/color fields are legacy placeholders; use defaults
    local icon = "Interface\\Icons\\INV_Misc_Book_09"

    -- Decode facet maps
    local query = DecodeFacetMap(queryStr)
    local boost = DecodeFacetMap(boostStr)
    local anti = DecodeFacetMap(antiStr)

    -- Validate facets against vocab (silently drop unknown)
    if HDG_FacetVocab then
        local function FilterValidFacets(facetMap)
            local filtered = {}
            for facetKey, vals in pairs(facetMap) do
                local vocabTable = HDG_FacetVocab[facetKey]
                if vocabTable then
                    -- Build reverse lookup: string value -> true
                    local validVals = {}
                    for _, v in pairs(vocabTable) do
                        validVals[v] = true
                    end
                    local goodVals = {}
                    for _, v in ipairs(vals) do
                        if validVals[v] then goodVals[#goodVals + 1] = v end
                    end
                    if #goodVals > 0 then
                        filtered[facetKey] = goodVals
                    end
                end
            end
            return filtered
        end
        query = FilterValidFacets(query)
        boost = FilterValidFacets(boost)
        anti = FilterValidFacets(anti)
    end

    -- Must have at least one query or boost tag
    if not next(query) and not next(boost) then
        return nil, "No valid tags (need at least one query or boost)"
    end

    -- Handle duplicate names
    if HDG and HDG.Store then
        local customs = HDG.Store:GetState().customStyles
        if customs then
            local baseName = name
            local suffix = 2
            while true do
                local dupe = false
                for _, def in pairs(customs) do
                    if def.displayName == name then dupe = true; break end
                end
                if not dupe then break end
                name = baseName .. " (" .. suffix .. ")"
                suffix = suffix + 1
            end
        end
    end

    local styleDef = {
        displayName = name,
        icon = icon,
        tier = "custom",
        color = { r = 0.5, g = 0.5, b = 0.5 },
        description = desc,
        query = next(query) and query or nil,
        boost = next(boost) and boost or nil,
        anti = next(anti) and anti or nil,
    }

    return styleDef
end

-- ============================================================================
-- SNAPSHOT EXPORT (HDG:2: format)
-- ============================================================================

function StyleSerializer:ExportSnapshot(def)
    if not def or not def.snapshot then return nil, "Not a snapshot" end

    -- Build sorted item list: itemID:placed:stored,...
    local itemParts = {}
    local sortedIDs = {}
    for itemID in pairs(def.snapshot.items or {}) do
        sortedIDs[#sortedIDs + 1] = itemID
    end
    table.sort(sortedIDs)
    for _, itemID in ipairs(sortedIDs) do
        local counts = def.snapshot.items[itemID]
        itemParts[#itemParts + 1] = itemID .. ":" .. (counts.placed or 0) .. ":" .. (counts.stored or 0)
    end

    local payload = table.concat({
        def.displayName or "Snapshot",
        tostring(def.snapshot.capturedAt or 0),
        table.concat(itemParts, ","),
    }, "|")

    return "HDG:2:" .. Base64Encode(payload)
end

-- ============================================================================
-- SNAPSHOT IMPORT (HDG:2: format)
-- ============================================================================

function StyleSerializer:ImportSnapshot(encodedStr)
    if not encodedStr or not encodedStr:match("^HDG:2:") then
        return nil, "Invalid snapshot format"
    end

    local b64 = encodedStr:sub(7)
    local ok, payload = pcall(Base64Decode, b64)
    if not ok or not payload or payload == "" then
        return nil, "Failed to decode snapshot"
    end

    -- Split by pipe: displayName|capturedAt|itemList
    local fields = {}
    for field in (payload .. "|"):gmatch("([^|]*)|") do
        fields[#fields + 1] = field
    end
    if #fields < 3 then
        return nil, "Not enough fields (got " .. #fields .. ")"
    end

    local name = fields[1]
    local capturedAt = tonumber(fields[2]) or 0
    local itemStr = fields[3]

    if not name or name == "" then name = "Imported Snapshot" end
    local CONFIG = privateDB.CONFIG
    local SE = CONFIG and CONFIG.STYLE_EDITOR
    local maxLen = SE and SE.NAME_MAX_LENGTH or 40
    if #name > maxLen then name = name:sub(1, maxLen) end

    -- Parse items: itemID:placed:stored,...
    local items = {}
    if itemStr and itemStr ~= "" then
        for entry in itemStr:gmatch("[^,]+") do
            local idStr, placedStr, storedStr = entry:match("^(%d+):(%d+):(%d+)$")
            if idStr then
                local itemID = tonumber(idStr)
                local placed = tonumber(placedStr) or 0
                local stored = tonumber(storedStr) or 0
                if itemID and (placed > 0 or stored > 0) then
                    items[itemID] = { placed = placed, stored = stored }
                end
            end
        end
    end

    if not next(items) then
        return nil, "No valid items in snapshot"
    end

    -- Handle duplicate names
    if HDG and HDG.Store then
        local customs = HDG.Store:GetState().customStyles
        if customs then
            local baseName = name
            local suffix = 2
            while true do
                local dupe = false
                for _, def in pairs(customs) do
                    if def.displayName == name then dupe = true; break end
                end
                if not dupe then break end
                name = baseName .. " (" .. suffix .. ")"
                suffix = suffix + 1
            end
        end
    end

    -- Count totals for description
    local totalPlaced = 0
    for _, counts in pairs(items) do
        totalPlaced = totalPlaced + (counts.placed or 0)
    end

    local styleDef = {
        displayName = name,
        icon = "Interface\\Icons\\INV_Housing_Blueprint_Rugged",
        tier = "snapshot",
        color = { r = 0.4, g = 0.7, b = 1.0 },
        description = totalPlaced .. " placed (account-wide)",
        snapshot = {
            capturedAt = capturedAt,
            items = items,
        },
    }

    return styleDef
end
