--[[
============================================================
Decor Vendor Addon
© 2026 MidniteDestiny. All Rights Reserved.
============================================================

This file is part of the Decor Vendor addon.

All code, structure, and design are the intellectual
property of MidniteDestiny unless otherwise stated.

You may NOT:
• Copy, reproduce, or redistribute this code
• Modify and redistribute this code
• Use this code in other addons or projects

without explicit permission from the author.

This addon is distributed for personal use only.

============================================================
]]

local addonName, dv = ...

dv.filters = dv.filters or {}
dv.collapsedFilters = dv.collapsedFilters or {}
dv.collapsedFilterSections = dv.collapsedFilterSections or {}

-------------------------------------------------
-- Filter Defaults
-------------------------------------------------

dv.filters.vendors = dv.filters.vendors or {
    expansions = {},
    factions = {},
    recipes = {},
    categories = {
        none = false,
        reputation = false,
        renown = false,
        subfaction = false,
        friendship = false,
        recipe = false,
        race = false,
        neighborhood = false,
        Promo = false,
    },
}

dv.filters.quests = dv.filters.quests or {
    expansions = {},
    categories = {},
    factions = {},
}

dv.filters.bossdrops = dv.filters.bossdrops or {
    expansions = {},
    categories = {},
}

dv.filters.professions = dv.filters.professions or {
    professions = {},
    expansions = {},
}

dv.filters.achievements = dv.filters.achievements or {
    groups = {},
    factions = {},
}

dv.filters.requirements = dv.filters.requirements or {
    types = {},
    showLocked = true,
    showUnlocked = true,
}

-------------------------------------------------
-- Shared Helpers
-------------------------------------------------

local function GetCatalogSizing()
    local C = dv.CONSTANTS or {}
    return C.CatalogSizing or {}
end

local function GetSidebarWidth()
    local CatSizing = GetCatalogSizing()
    return CatSizing.SidebarWidth or 200
end

local function RefreshFromSidebar()
    dv.filtersJustChanged = true

    if dv.scrollFrame then
        dv.scrollFrame:SetVerticalScroll(0)
    end

    if BuildVendorUI then
        BuildVendorUI()
    elseif dv.BuildVendorUI then
        dv.BuildVendorUI()
    end
end

local function ClearFrameChildren(frame)
    if not frame then
        return
    end

    for _, child in ipairs({ frame:GetChildren() }) do
        child:Hide()
        child:SetParent(nil)
    end
end

local function AnySelected(tbl)
    if not tbl then
        return false
    end

    for _, selected in pairs(tbl) do
        if selected then
            return true
        end
    end

    return false
end

local function SortedKeys(tbl)
    local keys = {}

    for key in pairs(tbl or {}) do
        table.insert(keys, key)
    end

    table.sort(keys)

    return keys
end

local function OrderedFactionList(seen)
    local order = { "alliance", "horde", "neutral" }
    local result = {}

    for _, faction in ipairs(order) do
        if seen[faction] then
            table.insert(result, faction)
        end
    end

    for _, faction in ipairs(SortedKeys(seen)) do
        if faction ~= "alliance" and faction ~= "horde" and faction ~= "neutral" then
            table.insert(result, faction)
        end
    end

    return result
end

local function GetFactionColor(faction)
    faction = faction and string.lower(faction)

    if faction == "alliance" then
        return { 0.35, 0.65, 1.0, 1 }

    elseif faction == "horde" then
        return { 1.0, 0.25, 0.25, 1 }

    elseif faction == "neutral" then
        return { 0.20, 1.0, 0.35, 1 }
    end

    return nil
end

local function FormatFactionLabel(faction)
    faction = tostring(faction or "")

    local labels = {
        alliance = "Alliance",
        horde = "Horde",
        neutral = "Neutral",
    }

    return labels[string.lower(faction)] or faction:gsub("^%l", string.upper)
end

local function ColorLabel(label, colorTable, key)
    if dv.ColorizeLabel then
        return dv.ColorizeLabel(label, colorTable, key)
    end

    return tostring(label or "Unknown")
end

local function ColorizeExpansionName(expansionName)
    expansionName = expansionName or "Unknown"

    local hex =
        dv.ExpansionColors
        and (
            dv.ExpansionColors[expansionName]
            or dv.ExpansionColors.Unknown
        )
        or "888888"

    return "|cff" .. hex .. tostring(expansionName) .. "|r"
end
-------------------------------------------------
-- Compatibility Helper
-------------------------------------------------

function dv.ItemPassesRequirements(itemID)
    local data =
        (dv.ActiveItems and dv.ActiveItems[itemID])
        or (dv.decorItem and dv.decorItem[itemID])

    if not data then
        return true
    end

    local tab = dv.currentTab or "vendors"
    local filters = dv.filters and dv.filters[tab]

    if not filters then
        return true
    end

    local expansions = filters.expansions
    local factions = filters.factions

    if expansions and AnySelected(expansions) and data.expansion then
        if not expansions[data.expansion] then
            return false
        end
    end

    if factions and AnySelected(factions) and data.faction then
        if not factions[string.lower(data.faction)] then
            return false
        end
    end

    return true
end

-------------------------------------------------
-- Sidebar Container Reset
-------------------------------------------------

function dv.ResetSidebarFilters()
    local sidebar = dv.sidebar

    if not sidebar then
        return
    end

    local sidebarWidth = GetSidebarWidth()

    -- Reuse the Gallery-style container created by UI/MainFrame.lua.
    -- Do NOT destroy/recreate the old way, or the anchors fight the new sidebar.
    dv.sidebarFilters =
        dv.sidebarFilters
        or sidebar.filtersContainer
        or sidebar.filterContent

    if not dv.sidebarFilters then
        dv.sidebarFilters = CreateFrame("Frame", "DV_SidebarFilters", sidebar)

        sidebar.filterContent = dv.sidebarFilters
        sidebar.filtersContainer = dv.sidebarFilters
    end

    ClearFrameChildren(dv.sidebarFilters)

    dv.sidebarFilters:ClearAllPoints()
    dv.sidebarFilters:SetPoint("TOPLEFT", sidebar, "TOPLEFT", 8, -8)
    dv.sidebarFilters:SetPoint("TOPRIGHT", sidebar, "TOPRIGHT", -6, -8)
    dv.sidebarFilters:SetPoint("BOTTOMRIGHT", sidebar, "BOTTOMRIGHT", -6, 12)
    dv.sidebarFilters:SetWidth(sidebarWidth - 14)

    if dv.sidebarFilters.SetClipsChildren then
        dv.sidebarFilters:SetClipsChildren(true)
    end

    dv.sidebarFilters:Show()
end

-------------------------------------------------
-- Gallery-style Sidebar Widgets
-------------------------------------------------

local function IsSectionCollapsed(sectionKey, defaultCollapsed)
    if dv.collapsedFilterSections[sectionKey] == nil then
        dv.collapsedFilterSections[sectionKey] = defaultCollapsed == true
    end

    return dv.collapsedFilterSections[sectionKey]
end

local function CreateSectionHeader(parent, sectionKey, title, yOffset, defaultCollapsed)
    local collapsed = IsSectionCollapsed(sectionKey, defaultCollapsed)

    local header = CreateFrame("Button", nil, parent)
    header:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, yOffset)
    header:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -2, yOffset)
    header:SetHeight(20)

    header.bg = header:CreateTexture(nil, "BACKGROUND")
    header.bg:SetAllPoints()
    header.bg:SetColorTexture(0, 0, 0, 0)

    header.text = header:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    header.text:SetPoint("LEFT", header, "LEFT", 0, 0)
    header.text:SetFont(STANDARD_TEXT_FONT, 12, "OUTLINE")
    header.text:SetText("|cffffd100" .. title .. "|r")

    header.arrow = header:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    header.arrow:SetPoint("RIGHT", header, "RIGHT", -2, 0)
    header.arrow:SetFont(STANDARD_TEXT_FONT, 12, "OUTLINE")
    header.arrow:SetText(collapsed and "|cffffd100+|r" or "|cffffd100-|r")

    header.line = header:CreateTexture(nil, "ARTWORK")
    header.line:SetColorTexture(1, 0.82, 0, 0.45)
    header.line:SetHeight(1)
    header.line:SetPoint("BOTTOMLEFT", header, "BOTTOMLEFT", 0, -2)
    header.line:SetPoint("BOTTOMRIGHT", header, "BOTTOMRIGHT", 0, -2)

    header:SetScript("OnEnter", function(self)
        self.bg:SetColorTexture(1, 0.82, 0, 0.08)
        self.text:SetText("|cffffffff" .. title .. "|r")
    end)

    header:SetScript("OnLeave", function(self)
        self.bg:SetColorTexture(0, 0, 0, 0)
        self.text:SetText("|cffffd100" .. title .. "|r")
    end)

    header:SetScript("OnClick", function()
        dv.collapsedFilterSections[sectionKey] = not dv.collapsedFilterSections[sectionKey]

        if dv.BuildCurrentSidebarFilters then
            dv.BuildCurrentSidebarFilters()
        end

        PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
    end)

    return yOffset - 26, collapsed
end

local function CreateSidebarCheckbox(parent, yOffset, labelText, checked, onClick, color)
    local check = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
    check:SetSize(20, 20)
    check:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, yOffset + 4)
    check:SetChecked(checked and true or false)

    -- Hide the template label if it exists. We use our own label so rebuilds stay clean.
    if check.Text then
        check.Text:SetText("")
    end

    if check.text then
        check.text:SetText("")
    end

    check.label = check:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    check.label:SetPoint("LEFT", check, "RIGHT", 2, 0)
    check.label:SetWidth(GetSidebarWidth() - 42)
    check.label:SetJustifyH("LEFT")
    check.label:SetFont(STANDARD_TEXT_FONT, 11, "OUTLINE")
    check.label:SetText(labelText)

    if color then
        check.label:SetTextColor(color[1], color[2], color[3], color[4] or 1)
    else
        check.label:SetTextColor(0.84, 0.84, 0.84, 1)
    end

    check:SetScript("OnClick", function(self)
        if onClick then
            onClick(self:GetChecked() and true or false)
        end

        PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
    end)

    return yOffset - 18, check
end

local function AddSection(parent, contextKey, sectionKey, title, yOffset, options, selectedTable, defaultCollapsed)
    local fullKey = contextKey .. "." .. sectionKey
    local collapsed

    yOffset, collapsed = CreateSectionHeader(parent, fullKey, title, yOffset, defaultCollapsed)

    if collapsed then
        return yOffset - 4
    end

    selectedTable = selectedTable or {}

    for _, option in ipairs(options or {}) do
        local key = option.key
        local checked = selectedTable[key] == true

        yOffset = CreateSidebarCheckbox(
            parent,
            yOffset,
            option.label or tostring(key),
            checked,
            function(isChecked)
                selectedTable[key] = isChecked and true or false
                RefreshFromSidebar()
            end,
            option.color
        )
    end

    return yOffset - 8
end



-------------------------------------------------
-- Option Builders
-------------------------------------------------

local function GetExpansionOptionsFromGroups(groups)
    local seen = {}

    for _, group in ipairs(groups or {}) do
        if group.expansion then
            seen[group.expansion] = true
        end
    end

    local options = {}

    for _, expansion in ipairs(dv.EXPANSION_ORDER or {}) do
        if seen[expansion] then
            table.insert(options, {
                key = expansion,
                label = ColorizeExpansionName(expansion),
            })
        end
    end

    for _, expansion in ipairs(SortedKeys(seen)) do
        local alreadyAdded = false

        for _, option in ipairs(options) do
            if option.key == expansion then
                alreadyAdded = true
                break
            end
        end

        if not alreadyAdded then
            table.insert(options, {
                key = expansion,
                label = ColorizeExpansionName(expansion),
            })
        end
    end

    return options
end

local function GetQuestExpansionOptions()
    local seen = {}

    for _, group in ipairs(dv.quests or {}) do
        for _, quest in ipairs(group.quests or {}) do
            if quest.expansion then
                seen[quest.expansion] = true
            end
        end
    end

    return GetExpansionOptionsFromGroupsFromSeen(seen)
end

function GetExpansionOptionsFromGroupsFromSeen(seen)
    local options = {}

    for _, expansion in ipairs(dv.EXPANSION_ORDER or {}) do
        if seen[expansion] then
            table.insert(options, {
                key = expansion,
                label = ColorizeExpansionName(expansion),
            })
        end
    end

    for _, expansion in ipairs(SortedKeys(seen)) do
        local alreadyAdded = false

        for _, option in ipairs(options) do
            if option.key == expansion then
                alreadyAdded = true
                break
            end
        end

        if not alreadyAdded then
            table.insert(options, {
                key = expansion,
                label = ColorizeExpansionName(expansion),
            })
        end
    end

    return options
end

local function GetProfessionExpansionOptions()
    local seen = {}

    for _, group in ipairs(dv.professions or {}) do
        for _, item in ipairs(group.items or {}) do
            if item.expansion then
                seen[item.expansion] = true
            end
        end
    end

    return GetExpansionOptionsFromGroupsFromSeen(seen)
end

local function GetProfessionCategoryOptions()
    local seen = {}

    for _, group in ipairs(dv.professions or {}) do
        for _, item in ipairs(group.items or {}) do
            if item.category then
                seen[item.category] = true
            end
        end
    end

    local options = {}
    local used = {}

    for _, professionName in ipairs(dv.Profession_Order or {}) do
        if seen[professionName] then
            table.insert(options, {
                key = professionName,
                label = ColorLabel(professionName, dv.LabelColors and dv.LabelColors.Professions, professionName),
            })

            used[professionName] = true
        end
    end

    for _, professionName in ipairs(SortedKeys(seen)) do
        if not used[professionName] then
            table.insert(options, {
                key = professionName,
                label = ColorLabel(professionName, dv.LabelColors and dv.LabelColors.Professions, professionName),
            })
        end
    end

    return options
end

local function GetQuestFactionOptions()
    local seen = {}

    for _, group in ipairs(dv.quests or {}) do
        for _, quest in ipairs(group.quests or {}) do
            if quest.faction then
                seen[string.lower(quest.faction)] = true
            end
        end
    end

    local options = {}

    for _, faction in ipairs(OrderedFactionList(seen)) do
        table.insert(options, {
            key = faction, -- keep lowercase for filtering
            label = FormatFactionLabel(faction),
            color = GetFactionColor(faction),
        })
    end

    return options
end

local function GetAchievementFactionOptions()
    local seen = {}

    for _, group in ipairs(dv.achievements or {}) do
        for _, achievement in ipairs(group.achievements or {}) do
            if achievement.faction then
                seen[string.lower(achievement.faction)] = true
            end
        end
    end

    local options = {}

    for _, faction in ipairs(OrderedFactionList(seen)) do
        table.insert(options, {
            key = faction, -- keep lowercase for filtering
            label = FormatFactionLabel(faction),
            color = GetFactionColor(faction),
        })
    end

    return options
end

local function GetAchievementGroupOptions()
    local options = {}

    for _, group in ipairs(dv.achievements or {}) do
        if group.name then
            table.insert(options, {
                key = group.name,
                label = ColorLabel(group.name, dv.LabelColors and dv.LabelColors.AchievementCategories, group.name),
            })
        end
    end

    return options
end

-------------------------------------------------
-- Sidebar Builders
-------------------------------------------------

function dv.BuildVendorFilters()
    dv.ResetSidebarFilters()

    local parent = dv.sidebarFilters
    local filters = dv.filters.vendors

    local y = -4

    y =
        AddSection(
        parent,
        "vendors",
        "expansions",
        "Expansions",
        y,
        GetExpansionOptionsFromGroups(dv.npcs),
        filters.expansions,
        false
    )

    y =
        AddSection(
        parent,
        "vendors",
        "faction",
        "Faction",
        y,
        {
            {key = "alliance", label = "Alliance", color = GetFactionColor("alliance")},
            {key = "horde", label = "Horde", color = GetFactionColor("horde")},
            {key = "neutral", label = "Neutral", color = GetFactionColor("neutral")}
        },
        filters.factions,
        false
    )

    y =
        AddSection(
        parent,
        "vendors",
        "requirements",
        "Unlocks",
        y,
        {
            {key = "none", label = "No Requirement"},
            {key = "reputation", label = "Reputation"},
            {key = "renown", label = "Renown"},
            {key = "subfaction", label = "Subfaction"},
            {key = "friendship", label = "Friendship"},
            {key = "race", label = "Race Locked"},
            {key = "neighborhood", label = "Neighborhood / Housing"},
            {key = "Promo", label = "Promo"}
        },
        filters.categories,
        true
    )

    parent:SetHeight(math.abs(y) + 20)
end

function dv.BuildQuestFilters()
    dv.ResetSidebarFilters()

    local parent = dv.sidebarFilters
    local filters = dv.filters.quests

    local y = -4

    y = AddSection(
        parent,
        "quests",
        "expansions",
        "Expansions",
        y,
        GetQuestExpansionOptions(),
        filters.expansions,
        false
    )

    y = AddSection(
        parent,
        "quests",
        "faction",
        "Faction",
        y,
        GetQuestFactionOptions(),
        filters.factions,
        false
    )

    y = AddSection(
        parent,
        "quests",
        "categories",
        "Unlocks",
        y,
        {
            { key = "race", label = "Race Locked" },
            { key = "spec", label = "Spec Locked" },
        },
        filters.categories,
        true
    )

    parent:SetHeight(math.abs(y) + 20)
end

function dv.BuildProfessionFilters()
    dv.ResetSidebarFilters()

    local parent = dv.sidebarFilters
    local filters = dv.filters.professions

    local y = -4

    y = AddSection(
        parent,
        "professions",
        "expansions",
        "Expansions",
        y,
        GetProfessionExpansionOptions(),
        filters.expansions,
        false
    )

    y = AddSection(
        parent,
        "professions",
        "professions",
        "Professions",
        y,
        GetProfessionCategoryOptions(),
        filters.professions,
        false
    )

    parent:SetHeight(math.abs(y) + 20)
end

function dv.BuildAchievementFilters()
    dv.ResetSidebarFilters()

    local parent = dv.sidebarFilters
    local filters = dv.filters.achievements

    local y = -4

    y = AddSection(
        parent,
        "achievements",
        "categories",
        "Categories",
        y,
        GetAchievementGroupOptions(),
        filters.groups,
        false
    )

    y = AddSection(
        parent,
        "achievements",
        "faction",
        "Faction",
        y,
        GetAchievementFactionOptions(),
        filters.factions,
        false
    )

    parent:SetHeight(math.abs(y) + 20)
end

function dv.BuildBossDropFilters()
    dv.ResetSidebarFilters()

    local parent = dv.sidebarFilters
    local filters = dv.filters.bossdrops

    local y = -4

    y = AddSection(
        parent,
        "bossdrops",
        "expansions",
        "Expansions",
        y,
        GetExpansionOptionsFromGroups(dv.bossdrops),
        filters.expansions,
        false
    )

    y = AddSection(
        parent,
        "bossdrops",
        "categories",
        "Categories",
        y,
        {
{ key = "rare", label = ColorLabel("Rares", dv.LabelColors and dv.LabelColors.BossCategories, "rare") },
{ key = "dungeon", label = ColorLabel("Dungeons", dv.LabelColors and dv.LabelColors.BossCategories, "dungeon") },
{ key = "delve", label = ColorLabel("Delves", dv.LabelColors and dv.LabelColors.BossCategories, "delve") },
{ key = "renown", label = ColorLabel("Renown", dv.LabelColors and dv.LabelColors.BossCategories, "renown") },
{ key = "event", label = ColorLabel("Events", dv.LabelColors and dv.LabelColors.BossCategories, "event") },
{ key = "raid", label = ColorLabel("Raids", dv.LabelColors and dv.LabelColors.BossCategories, "raid") },
{ key = "daily", label = ColorLabel("Dailies", dv.LabelColors and dv.LabelColors.BossCategories, "daily") },
{ key = "decor", label = ColorLabel("Decor Specialties", dv.LabelColors and dv.LabelColors.BossCategories, "decor") },
        },
        filters.categories,
        false
    )

    parent:SetHeight(math.abs(y) + 20)
end

-------------------------------------------------
-- Current Tab Router
-------------------------------------------------

function dv.BuildCurrentSidebarFilters()
    if dv.currentTab == "vendors" and dv.BuildVendorFilters then
        dv.BuildVendorFilters()

    elseif dv.currentTab == "professions" and dv.BuildProfessionFilters then
        dv.BuildProfessionFilters()

    elseif dv.currentTab == "quests" and dv.BuildQuestFilters then
        dv.BuildQuestFilters()

    elseif dv.currentTab == "achievements" and dv.BuildAchievementFilters then
        dv.BuildAchievementFilters()

    elseif dv.currentTab == "bossdrops" and dv.BuildBossDropFilters then
        dv.BuildBossDropFilters()
    end
end