-- =============================================================================
-- ETB v12.59.0 — UI/MainFrame/LivePreview.lua
-- =============================================================================
-- Second MainFrame submodule. Owns the live-preview surface: the right-side
-- mannequin showing the outfit being assembled, plus the slot buttons that
-- flank it (left and right columns), plus the "Generate Random Outfit"
-- feature.
--
-- Also owns the small selection-state helpers (UpdateSlotHighlight,
-- UpdateWeaponTypeHighlight, UpdateCurrentViewingLabel) — they live with
-- the slot buttons because they update the same UI surface.
--
-- Public API (all take owner = MainFrame singleton as first arg):
--   LivePreview:UpdateSlotHighlight(owner, selectedID)
--   LivePreview:UpdateWeaponTypeHighlight(owner, selectedType)
--   LivePreview:UpdateCurrentViewingLabel(owner, slotName)
--   LivePreview:CreateSlotButton(owner, parent, slotName, index)
--   LivePreview:UpdateSlotButtons(owner)
--   LivePreview:RebuildLivePreview(owner)
--   LivePreview:AddToLivePreview(owner, item)
--   LivePreview:GetSlotNameForItem(owner, item)
--   LivePreview:GenerateRandomOutfit(owner)
--
-- Owner contract — fields read/written:
--   owner.frame                       (with .slotIcons, .weaponTypeIcons,
--                                      .currentViewingLabel, .leftSlotBtns,
--                                      .rightSlotBtns, .previewModel,
--                                      .previewItemName)
--   owner.livePreviewSlots            (table of slotName -> item)
--
-- Owner methods called (delegators in main file):
--   owner:RefreshItems()                    (ItemGrid cluster, future)
--   owner:UpdateSlotHighlight(categoryID)   (this cluster, self-call OK)
--   owner:UpdateSlotButtons()               (this cluster, self-call OK)
--   owner:RebuildLivePreview()              (this cluster, self-call OK)
--   owner:UpdateCurrentViewingLabel(slot)   (this cluster, self-call OK)
--   owner:GetSlotNameForItem(item)          (this cluster, self-call OK)
--
-- Globals/Blizzard:
--   CreateFrame, GameTooltip, UnitClass, GetItemInfo, GetItemInfoInstant
--   wipe, math.random
--   C_TransmogCollection (GetCategoryAppearances, GetAppearanceSources)
--   ETB.Filtering, ETB.BlizzardData, ETB.ProfileManager, ETB.CATEGORY_INFO
--   ETB.SourcesModal, ETB:GetQualityColor
-- =============================================================================

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

NS.ETB.MainFrame = NS.ETB.MainFrame or ETB.MainFrame or {}
local LivePreview = {}
NS.ETB.MainFrame.LivePreview = LivePreview

-- =============================================================================
-- FILE-LOCAL HELPERS — moved verbatim from MainFrame.lua. Used only inside
-- this cluster's CreateSlotButton method.
-- =============================================================================

-- Slot name to category ID mapping
local SLOT_TO_CATEGORY_MAP = {
    Head = 1,
    Shoulder = 2,
    Back = 3,
    Chest = 4,
    Shirt = 5,
    Tabard = 6,
    Wrist = 7,
    Hands = 8,
    Waist = 9,
    Legs = 10,
    Feet = 11,
    -- MainHand and OffHand handled specially
}

-- Get first usable weapon category for player's class
-- forOffHand: true for off-hand categories, false for main-hand
local function GetFirstUsableWeaponCategory(forOffHand)
    local usableWeapons = ETB.BlizzardData and ETB.BlizzardData:GetPlayerWeaponTypes() or {}

    if forOffHand then
        -- Off-hand categories: 18 (Shields), 19 (Off-Hands/Holdables)
        -- Check shields first
        if usableWeapons["SHIELD"] then
            return 18  -- Shields
        end
        -- Then holdables (most classes can use these)
        if usableWeapons["HOLDABLE"] then
            return 19  -- Off-Hands
        end
        -- Fallback to shields
        return 18
    else
        -- Main hand weapon categories in preferred order for browsing
        -- Order: 1H weapons first (more variety), then 2H, then ranged
        local categoryOrder = {
            -- One-handed melee (most common, good variety)
            { cat = 14, type = "SWORD" },      -- 1H Swords
            { cat = 13, type = "AXE" },        -- 1H Axes
            { cat = 15, type = "MACE" },       -- 1H Maces
            { cat = 16, type = "DAGGER" },     -- Daggers
            { cat = 17, type = "FIST" },       -- Fist Weapons
            -- Two-handed melee
            { cat = 21, type = "SWORD" },      -- 2H Swords
            { cat = 20, type = "AXE" },        -- 2H Axes
            { cat = 22, type = "MACE" },       -- 2H Maces
            { cat = 23, type = "STAFF" },      -- Staves
            { cat = 24, type = "POLEARM" },    -- Polearms
            -- Ranged
            { cat = 25, type = "BOW" },        -- Bows
            { cat = 26, type = "GUN" },        -- Guns
            { cat = 27, type = "CROSSBOW" },   -- Crossbows
            { cat = 12, type = "WAND" },       -- Wands
        }

        for _, entry in ipairs(categoryOrder) do
            if usableWeapons[entry.type] then
                return entry.cat
            end
        end

        -- Fallback to first weapon category
        return 14  -- 1H Swords
    end
end

-- =============================================================================
-- SELECTION-STATE HIGHLIGHTING
-- =============================================================================

function LivePreview:UpdateSlotHighlight(owner, selectedID)
    if not owner.frame then return end
    -- Update all slot buttons (indexed by slot ID)
    for slotID, btn in pairs(owner.frame.slotIcons) do
        if btn and btn.selected then
            btn.selected:SetShown(slotID == selectedID)
        end
    end
end

function LivePreview:UpdateWeaponTypeHighlight(owner, selectedType)
    if not owner.frame then return end
    -- Update all weapon type buttons
    for weaponType, btn in pairs(owner.frame.weaponTypeIcons or {}) do
        if btn and btn.selected then
            -- Highlight if this is the selected type, or if "all" is selected and this is "all"
            local shouldHighlight = (weaponType == selectedType) or (selectedType == nil and weaponType == "all")
            btn.selected:SetShown(shouldHighlight)
        end
    end
end

function LivePreview:UpdateCurrentViewingLabel(owner, slotName)
    if not owner.frame then return end
    local categoryID = ETB.Filtering.filters.category
    local stats = ETB.BlizzardData:GetCollectionStats(categoryID)

    -- Check if viewing as different character
    local profileInfo = ""
    if ETB.ProfileManager then
        local PM = ETB.ProfileManager
        if PM.activeProfileKey and PM.currentCharacterKey and PM.activeProfileKey ~= PM.currentCharacterKey then
            local profile = PM:GetActiveProfile()
            if profile then
                profileInfo = string.format(" |cffff8800[As %s - %s]|r", profile.name, profile.classID)
            end
        end
    end

    owner.frame.currentViewingLabel:SetText(string.format(
        "|cff00ff00%s|r |cff888888%d/%d (%.0f%%)|r%s",
        slotName or "Unknown",
        stats.collected, stats.total, stats.percent,
        profileInfo
    ))
end

-- =============================================================================
-- SLOT BUTTON CONSTRUCTION
-- =============================================================================

function LivePreview:CreateSlotButton(owner, parent, slotName, index)
    local btn = CreateFrame("Button", nil, parent)
    btn:SetSize(55, 22)

    btn.bg = btn:CreateTexture(nil, "BACKGROUND")
    btn.bg:SetAllPoints()
    btn.bg:SetColorTexture(0.15, 0.15, 0.15, 0.8)

    btn.text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    btn.text:SetPoint("LEFT", 3, 0)
    btn.text:SetText(slotName:sub(1, 6))
    btn.text:SetTextColor(0.6, 0.6, 0.6)

    -- Remove/Hide icon (X button)
    btn.removeBtn = CreateFrame("Button", nil, btn)
    btn.removeBtn:SetSize(14, 14)
    btn.removeBtn:SetPoint("RIGHT", -2, 0)
    btn.removeBtn:SetNormalTexture("Interface\\Buttons\\UI-StopButton")
    btn.removeBtn:SetHighlightTexture("Interface\\Buttons\\UI-StopButton", "ADD")
    btn.removeBtn:Hide()
    btn.removeBtn:SetScript("OnClick", function()
        -- Remove this slot from preview
        owner.livePreviewSlots[slotName] = nil
        owner:RebuildLivePreview()
        btn.text:SetTextColor(0.6, 0.6, 0.6)
        btn.bg:SetColorTexture(0.15, 0.15, 0.15, 0.8)
        btn.removeBtn:Hide()
    end)

    -- Click empty slot to filter to that category; Right-click filled slot to see sources
    btn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    btn:SetScript("OnClick", function(self, button)
        if button == "RightButton" then
            -- Right-click: Open SourcesModal for the item in this slot
            local item = owner.livePreviewSlots[slotName]
            if item and ETB.SourcesModal then
                ETB.SourcesModal:Show(item)
            end
            return
        end

        -- Left-click: Only act if slot is empty
        if owner.livePreviewSlots[slotName] then
            return  -- Slot is filled, do nothing (use X button to remove)
        end

        local categoryID

        if slotName == "MainHand" then
            -- Get first usable weapon category for main hand
            categoryID = GetFirstUsableWeaponCategory(false)
        elseif slotName == "OffHand" then
            -- Get first usable off-hand category
            categoryID = GetFirstUsableWeaponCategory(true)
        else
            -- Armor slot - direct mapping
            categoryID = SLOT_TO_CATEGORY_MAP[slotName]
        end

        if categoryID and ETB.Filtering then
            ETB.Filtering:SetCategory(categoryID)
            -- Refresh the item list
            if owner.RefreshItems then
                owner:RefreshItems()
            end
            -- Update UI highlighting
            if owner.UpdateSlotHighlight then
                owner:UpdateSlotHighlight(categoryID)
            end
        end
    end)

    btn:SetScript("OnEnter", function(self)
        self.bg:SetColorTexture(0.25, 0.25, 0.25, 0.8)
        GameTooltip:SetOwner(self, "ANCHOR_LEFT")

        if owner.livePreviewSlots[slotName] then
            local item = owner.livePreviewSlots[slotName]
            GameTooltip:SetText(item.name or "Unknown")
            GameTooltip:AddLine("Click X to remove", 0.7, 0.7, 0.7)
            GameTooltip:AddLine("Right-click for source info", 0, 1, 1)
        else
            -- Empty slot - show hint to click to filter
            GameTooltip:SetText(slotName .. " (Empty)")
            GameTooltip:AddLine("Click to browse " .. slotName .. " items", 0, 1, 1)
            if slotName == "MainHand" or slotName == "OffHand" then
                GameTooltip:AddLine("(Shows weapons your class can use)", 0.7, 0.7, 0.7)
            end
        end
        GameTooltip:Show()
    end)
    btn:SetScript("OnLeave", function(self)
        if not owner.livePreviewSlots[slotName] then
            self.bg:SetColorTexture(0.15, 0.15, 0.15, 0.8)
        else
            self.bg:SetColorTexture(0.1, 0.3, 0.1, 0.8)
        end
        GameTooltip:Hide()
    end)

    btn.slotName = slotName
    return btn
end

-- =============================================================================
-- SLOT BUTTON STATE + MANNEQUIN REBUILD
-- =============================================================================

function LivePreview:UpdateSlotButtons(owner)
    if not owner.frame then return end

    local allBtns = {}
    for name, btn in pairs(owner.frame.leftSlotBtns or {}) do allBtns[name] = btn end
    for name, btn in pairs(owner.frame.rightSlotBtns or {}) do allBtns[name] = btn end

    for slotName, btn in pairs(allBtns) do
        if owner.livePreviewSlots[slotName] then
            btn.text:SetTextColor(0, 1, 0)
            btn.bg:SetColorTexture(0.1, 0.3, 0.1, 0.8)
            btn.removeBtn:Show()
        else
            btn.text:SetTextColor(0.6, 0.6, 0.6)
            btn.bg:SetColorTexture(0.15, 0.15, 0.15, 0.8)
            btn.removeBtn:Hide()
        end
    end
end

function LivePreview:ApplyAutoHideSlots(owner)
    -- Feature 4.4: respect user preference to hide cosmetic/weapon slots
    -- on the preview model. Called after Undress->TryOn cycles so the hides
    -- aren't immediately overwritten by re-equipping logic.
    if not owner.frame or not owner.frame.previewModel then return end
    local model = owner.frame.previewModel
    if not model.UndressSlot then return end  -- API guard for older clients

    local Settings = ETB.Settings
    if not Settings then return end

    -- WoW inventory slot IDs:
    --   4=Shirt, 16=MainHand, 17=OffHand (and 18=Ranged), 19=Tabard
    if Settings:Get("preview.autoHideShirt") then
        pcall(model.UndressSlot, model, 4)
    end
    if Settings:Get("preview.autoHideTabard") then
        pcall(model.UndressSlot, model, 19)
    end
    if Settings:Get("preview.autoHideWeapons") then
        pcall(model.UndressSlot, model, 16)
        pcall(model.UndressSlot, model, 17)
        pcall(model.UndressSlot, model, 18)  -- ranged for hunters
    end
end

function LivePreview:RebuildLivePreview(owner)
    if not owner.frame then return end

    -- v15.8.1: If livePreviewSlots is empty (user hasn't picked any items
    -- yet), Dress the model to restore the player's currently-worn gear
    -- instead of leaving it stripped. Previously a checkbox click on
    -- Hide Tabard/Shirt/Weapons with no items selected would Undress the
    -- model and never re-dress it, leaving the preview "naked". Counting
    -- via next() because owner.livePreviewSlots is a string-keyed hash,
    -- so #t doesn't work.
    local hasAnyTryOnItem = next(owner.livePreviewSlots) ~= nil

    if hasAnyTryOnItem then
        owner.frame.previewModel:Undress()
        for slotName, item in pairs(owner.livePreviewSlots) do
            if item and item.itemID then
                owner.frame.previewModel:TryOn("item:" .. item.itemID)
            end
        end
    else
        -- No items being previewed → show player's current gear as the base.
        -- Dress() restores worn gear without needing SetUnit (which causes
        -- a graphics reset per Construction.lua line 1182 comment).
        owner.frame.previewModel:Dress()
    end

    self:ApplyAutoHideSlots(owner)  -- Feature 4.4
    owner:UpdateSlotButtons()
end

-- =============================================================================
-- ADD TO LIVE PREVIEW (builds outfit)
-- =============================================================================

function LivePreview:AddToLivePreview(owner, item)
    if not owner.frame or not item or not item.itemID then return end

    -- Determine slot name from item
    local slotName = owner:GetSlotNameForItem(item)

    -- Store in slot
    if slotName then
        owner.livePreviewSlots[slotName] = item
    end

    -- Add to model (don't undress)
    owner.frame.previewModel:TryOn("item:" .. item.itemID)
    self:ApplyAutoHideSlots(owner)  -- Feature 4.4

    local color = ETB:GetQualityColor(item.quality)
    owner.frame.previewItemName:SetText(color .. (item.name or "Unknown") .. "|r")

    owner:UpdateSlotButtons()
end

function LivePreview:GetSlotNameForItem(owner, item)
    -- Map category ID to slot name
    local categoryID = ETB.Filtering.filters.category
    local catInfo = ETB.CATEGORY_INFO[categoryID]
    if catInfo then
        return catInfo.name
    end
    return "Unknown"
end

-- =============================================================================
-- RANDOM OUTFIT GENERATOR
-- =============================================================================

function LivePreview:GenerateRandomOutfit(owner)
    local f = owner.frame
    if not f or not f.previewModel then return end

    -- Get player's armor type
    local _, playerClass = UnitClass("player")
    local armorTypes = {
        WARRIOR = "PLATE", PALADIN = "PLATE", DEATHKNIGHT = "PLATE",
        HUNTER = "MAIL", SHAMAN = "MAIL",
        ROGUE = "LEATHER", DRUID = "LEATHER", MONK = "LEATHER",
        MAGE = "CLOTH", WARLOCK = "CLOTH", PRIEST = "CLOTH",
    }
    local playerArmorType = armorTypes[playerClass] or "LEATHER"

    -- Numeric subclass IDs for armor type comparison (GetItemInfoInstant returns these)
    local ARMOR_SUBCLASS = { CLOTH = 1, LEATHER = 2, MAIL = 3, PLATE = 4 }
    local playerArmorSubclass = ARMOR_SUBCLASS[playerArmorType] or 2

    -- Category to preview slot name (matches PREVIEW_SLOTS_LEFT / PREVIEW_SLOTS_RIGHT)
    local catToSlotName = {
        [1] = "Head", [2] = "Shoulder", [3] = "Back", [4] = "Chest", [5] = "Shirt", [6] = "Tabard",
        [7] = "Wrist", [8] = "Hands", [9] = "Waist", [10] = "Legs", [11] = "Feet",
    }

    -- All transmog slots (armor + shirt + tabard)
    local slots = {
        {cat = 1, invSlot = 1},   -- Head
        {cat = 2, invSlot = 3},   -- Shoulder
        {cat = 3, invSlot = 15},  -- Back (cloak)
        {cat = 4, invSlot = 5},   -- Chest
        {cat = 5, invSlot = 4},   -- Shirt
        {cat = 6, invSlot = 19},  -- Tabard
        {cat = 7, invSlot = 9},   -- Wrist
        {cat = 8, invSlot = 10},  -- Hands
        {cat = 9, invSlot = 6},   -- Waist
        {cat = 10, invSlot = 7},  -- Legs
        {cat = 11, invSlot = 8},  -- Feet
    }

    -- Undress first
    f.previewModel:Undress()
    wipe(owner.livePreviewSlots)

    local itemsApplied = 0
    local function addToSlot(slotName, itemID)
        if not slotName or not itemID then return end
        local name, _, quality, _, _, _, _, _, _, icon = GetItemInfo(itemID)
        owner.livePreviewSlots[slotName] = { itemID = itemID, name = name, icon = icon, quality = quality or 1 }
    end

    for _, slotInfo in ipairs(slots) do
        local appearances = {}

        if C_TransmogCollection and C_TransmogCollection.GetCategoryAppearances then
            local catAppearances = C_TransmogCollection.GetCategoryAppearances(slotInfo.cat)
            if catAppearances then
                for _, app in ipairs(catAppearances) do
                    if app.isCollected then
                        local sources = C_TransmogCollection.GetAppearanceSources(app.visualID)
                        if sources and sources[1] then
                            local source = sources[1]
                            local itemID = source.itemID or source.itemId
                            if itemID then
                                local _, _, _, equipLoc, _, classID, subclassID = GetItemInfoInstant(itemID)
                                -- Back(3), Shirt(5), Tabard(6) accept any armor type
                                -- For other slots: classID 4 = Armor, check subclassID matches player
                                -- subclassID: 1=Cloth, 2=Leather, 3=Mail, 4=Plate, 0=Misc/Cosmetic
                                local armorOK = true
                                if slotInfo.cat ~= 3 and slotInfo.cat ~= 5 and slotInfo.cat ~= 6 then
                                    if classID == 4 and subclassID and subclassID > 0 then
                                        armorOK = (subclassID == playerArmorSubclass)
                                    end
                                end
                                if armorOK then
                                    table.insert(appearances, { itemID = itemID, visualID = app.visualID })
                                end
                            end
                        end
                    end
                end
            end
        end

        if #appearances > 0 then
            local randomIndex = math.random(1, #appearances)
            local item = appearances[randomIndex]
            if item.itemID then
                f.previewModel:TryOn("item:" .. item.itemID)
                addToSlot(catToSlotName[slotInfo.cat], item.itemID)
                itemsApplied = itemsApplied + 1
            end
        end
    end

    -- v15.8.4: Filter weapon categories by what the player's class can use.
    -- Previously every class got every weapon type randomly assigned —
    -- Mages got random 2H Axes, Druids got Wands, etc. Now we read
    -- ETB.BlizzardData.CLASS_WEAPONS to determine the allowed weaponType
    -- set for the current class, and trim the candidate category lists.
    --
    -- CATEGORY_INFO[N].weaponType is the key into CLASS_WEAPONS.
    local classWeapons = (ETB.BlizzardData and ETB.BlizzardData.CLASS_WEAPONS
                          and ETB.BlizzardData.CLASS_WEAPONS[playerClass]) or nil

    local function categoryAllowedForClass(categoryID)
        if not classWeapons then return true end  -- defensive: no map → allow all
        local catInfo = ETB.CATEGORY_INFO and ETB.CATEGORY_INFO[categoryID]
        if not catInfo or not catInfo.weaponType then return true end
        return classWeapons[catInfo.weaponType] == true
    end

    -- Filter the main-hand and off-hand category candidate lists. The raw
    -- lists include EVERY weapon category in the game; classWeapons gives
    -- us the per-class subset to randomize over.
    local mainHandCats = {}
    for _, cat in ipairs({12, 13, 14, 15, 16, 17, 20, 21, 22, 23, 24, 25, 26, 27}) do
        if categoryAllowedForClass(cat) then table.insert(mainHandCats, cat) end
    end
    local offHandCats = {}
    for _, cat in ipairs({18, 19}) do
        -- Off-hands need extra logic: SHIELD is a class-restricted weaponType
        -- (handled by categoryAllowedForClass); HOLDABLE (off-hand frills) is
        -- not in CLASS_WEAPONS — only Mages/Warlocks/Priests/Druids/Shamans
        -- can use them per Blizzard rules. We approximate via the CLOTH +
        -- "caster off-hand using" classes.
        if cat == 19 then
            -- Holdables: who can equip them on MoP?
            local holdableClasses = {
                MAGE = true, WARLOCK = true, PRIEST = true,
                DRUID = true, SHAMAN = true, PALADIN = true, MONK = true,
            }
            if holdableClasses[playerClass] then table.insert(offHandCats, cat) end
        elseif categoryAllowedForClass(cat) then
            table.insert(offHandCats, cat)
        end
    end
    local function collectItemIDsFromCategories(catList)
        local out = {}
        if not C_TransmogCollection or not C_TransmogCollection.GetCategoryAppearances then return out end
        for _, categoryID in ipairs(catList) do
            local catAppearances = C_TransmogCollection.GetCategoryAppearances(categoryID)
            if catAppearances then
                for _, app in ipairs(catAppearances) do
                    if app.isCollected then
                        local sources = C_TransmogCollection.GetAppearanceSources(app.visualID)
                        local src = sources[1]
                        local id = src and (src.itemID or src.itemId)
                        if id then
                            table.insert(out, id)
                        end
                    end
                end
            end
        end
        return out
    end
    local mainHandItems = collectItemIDsFromCategories(mainHandCats)
    if #mainHandItems > 0 then
        local itemID = mainHandItems[math.random(1, #mainHandItems)]
        f.previewModel:TryOn("item:" .. itemID)
        addToSlot("MainHand", itemID)
        itemsApplied = itemsApplied + 1
    end
    local offHandItems = collectItemIDsFromCategories(offHandCats)
    if #offHandItems > 0 then
        local itemID = offHandItems[math.random(1, #offHandItems)]
        f.previewModel:TryOn("item:" .. itemID)
        addToSlot("OffHand", itemID)
        itemsApplied = itemsApplied + 1
    end

    if itemsApplied > 0 then
        f.previewItemName:SetText("|cff00ff00Random " .. playerArmorType .. " Outfit|r")
        -- Rebuild model from livePreviewSlots so every applied slot is shown (avoids TryOn order issues)
        owner:RebuildLivePreview()
    else
        f.previewItemName:SetText("|cffff0000No collected items found|r")
        owner:UpdateSlotButtons()
    end
end
