-- =============================================================================
-- NativeSavedOutfitsTab.lua
-- Adds a native "ETB Saved Outfits" tab to Blizzard's transmogrifier (5.5.4+).
--
-- Shows the player's saved outfits as a 3x3 grid of model cards (same renderer
-- as the ETB Sets tab). Left-click loads an outfit onto the model; right-click
-- deletes it. Outfits are saved per character in ETB_SavedOutfits and are
-- populated by right-clicking a set in the ETB Sets tab (and, later, by the
-- Transmog Planner).
--
-- Public API (for the Sets tab / Planner to call):
--   ETB.NativeSavedOutfits:Add(name, items)        -- ETB set item format
--   ETB.NativeSavedOutfits:AddSourceMap(name, map)  -- { [slot] = sourceID }
--   ETB.NativeSavedOutfits:GetAll()
--   ETB.NativeSavedOutfits:Delete(index)
-- =============================================================================

local ADDON_NAME, NS = ...
local ETB = NS.ETB
local NC = ETB.NativeCard

local M = {}
ETB.NativeSavedOutfits = M

local state = { mounted = false, content = nil, rows = {}, page = 1, wc = nil, tabID = nil }

local COLS, ROWS, GAP, PAD = 3, 3, 12, 12
local HEADER_H, FOOTER_H = 40, 40
local _gen = 0

-- -----------------------------------------------------------------------------
-- Data source: the Transmog UI's own outfits. The native tab is a view onto the
-- same outfits the Transmog UI / Planner manages -- character outfits live in
-- ETBTUI_SavedOutfits, account outfits come from the profile manager. Each
-- outfit's items are keyed by slot NAME ("Head", "Chest", ...) with a sourceID;
-- we convert to the numeric { [invSlot] = sourceID } map NativeCard expects.
-- -----------------------------------------------------------------------------
local SLOT_NAME_TO_INV = {
    Head = 1, Shoulder = 3, Back = 15, Chest = 5, Shirt = 4, Tabard = 19,
    Wrist = 9, Hands = 10, Waist = 6, Legs = 7, Feet = 8, MainHand = 16, OffHand = 17,
}
local INV_TO_SLOT_NAME = {}
for n, id in pairs(SLOT_NAME_TO_INV) do INV_TO_SLOT_NAME[id] = n end

local function pieceSourceID(itemData)
    if type(itemData) ~= "table" then return nil end
    if itemData.sourceID and itemData.sourceID > 0 then return itemData.sourceID end
    if itemData.itemID and ETB.CollectionAPI and ETB.CollectionAPI.GetAppearance then
        local _, sid = ETB.CollectionAPI:GetAppearance(itemData.itemID)
        return sid
    end
    return nil
end

local function outfitToSourceMap(data)
    local map = {}
    if type(data) ~= "table" or type(data.items) ~= "table" then return map end
    for slotName, itemData in pairs(data.items) do
        local inv = SLOT_NAME_TO_INV[slotName]
        local sid = pieceSourceID(itemData)
        if inv and sid and sid > 0 then map[inv] = sid end
    end
    return map
end

-- Build the display list, merging character + account outfits (dedup by id).
function M:GetAll()
    local seen, entries = {}, {}
    local function add(id, data, isAccount)
        if not (id and type(data) == "table") or seen[id] then return end
        seen[id] = true
        entries[#entries + 1] = {
            id = id, isAccount = isAccount, name = data.name or "Outfit",
            items = outfitToSourceMap(data),
            created = data.createdAt or data.created or 0,
        }
    end
    if type(ETBTUI_SavedOutfits) == "table" then
        for id, data in pairs(ETBTUI_SavedOutfits) do add(id, data, false) end
    end
    if ETB.ProfileManager and ETB.ProfileManager.GetOutfitsForCurrentClass then
        local ok, usable = pcall(function() return ETB.ProfileManager:GetOutfitsForCurrentClass() end)
        if ok and type(usable) == "table" then
            for id, data in pairs(usable) do add(id, data, true) end
        end
    end
    table.sort(entries, function(a, b) return (a.created or 0) > (b.created or 0) end)
    return entries
end

-- Save an ETB set (numeric-slot items) as a Transmog UI outfit, so it shows up
-- both here and in the Planner. Used by the ETB Sets tab's right-click.
function M:SaveSetAsOutfit(name, items)
    if type(items) ~= "table" then return false end
    ETBTUI_SavedOutfits = (type(ETBTUI_SavedOutfits) == "table") and ETBTUI_SavedOutfits or {}
    local outItems, count = {}, 0
    for slot, piece in pairs(items) do
        local slotName = INV_TO_SLOT_NAME[tonumber(slot)]
        if slotName then
            local itemID = (type(piece) == "table" and (piece.itemID or piece[1]))
                or (type(piece) == "number" and piece) or nil
            local sourceID = (type(piece) == "table" and (piece.sourceID or piece[2])) or nil
            if not sourceID and itemID and ETB.CollectionAPI and ETB.CollectionAPI.GetAppearance then
                local _, sid = ETB.CollectionAPI:GetAppearance(itemID)
                sourceID = sid
            end
            if itemID and sourceID and sourceID > 0 then
                local iname, _, _, _, _, _, _, _, _, icon = GetItemInfo(itemID)
                outItems[slotName] = { itemID = itemID, sourceID = sourceID, name = iname, icon = icon }
                count = count + 1
            end
        end
    end
    if count == 0 then return false end
    local id = "outfit_" .. time() .. "_" .. math.random(1000, 9999)
    local outfit = { id = id, name = name or "Outfit", items = outItems, created = time(), pieceCount = count }
    if ETB.OutfitCompatibility and ETB.OutfitCompatibility.EnrichOutfitItems then
        pcall(function() ETB.OutfitCompatibility:EnrichOutfitItems(outfit) end)
    end
    ETBTUI_SavedOutfits[id] = outfit
    if state.mounted then self:Refresh() end
    return true
end

function M:Delete(entry)
    if not entry then return end
    if entry.id and type(ETBTUI_SavedOutfits) == "table" and ETBTUI_SavedOutfits[entry.id] then
        ETBTUI_SavedOutfits[entry.id] = nil
        if state.mounted then self:Refresh() end
    else
        print("|cff66ccffETB|r: Manage account outfits from the Transmog Planner.")
    end
end

-- -----------------------------------------------------------------------------
-- Cards
-- -----------------------------------------------------------------------------
local function showTip(card, o)
    GameTooltip:SetOwner(card, "ANCHOR_RIGHT")
    GameTooltip:AddLine(o.name or "Outfit", 1, 0.82, 0)
    GameTooltip:AddLine("Left-click: load onto the model.", 0.9, 0.9, 0.9)
    GameTooltip:AddLine("Right-click: delete.", 0.9, 0.4, 0.4)
    GameTooltip:Show()
end

local function applyOutfit(o)
    if NC and NC.ApplyOutfit(o.items) then
        print(string.format("|cff66ccffETB|r: Loaded |cffffd100%s|r onto the model \226\128\148 hit Apply to confirm.", o.name or "outfit"))
    else
        print("|cff66ccffETB|r: Couldn't load that outfit.")
    end
end

local function acquireCard(parent, i)
    local card = state.rows[i]
    if card then return card end
    card = CreateFrame("Button", nil, parent)

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

    card.border = CreateFrame("Frame", nil, card, BackdropTemplateMixin and "BackdropTemplate" or nil)
    card.border:SetAllPoints()
    if card.border.SetBackdrop then
        card.border:SetBackdrop({ edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1 })
        card.border:SetBackdropBorderColor(0.50, 0.42, 0.22, 1)
    end

    if NC and NC.AttachModel then NC.AttachModel(card) end

    card.labelBg = card:CreateTexture(nil, "ARTWORK")
    card.labelBg:SetPoint("BOTTOMLEFT", 1, 1)
    card.labelBg:SetPoint("BOTTOMRIGHT", -1, 1)
    card.labelBg:SetHeight(18)
    card.labelBg:SetColorTexture(0, 0, 0, 0.6)

    card.label = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    card.label:SetPoint("BOTTOMLEFT", 3, 3)
    card.label:SetPoint("BOTTOMRIGHT", -3, 3)
    card.label:SetJustifyH("CENTER")
    card.label:SetWordWrap(false)

    card:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    card:SetScript("OnEnter", function(self)
        if self.border.SetBackdropBorderColor then self.border:SetBackdropBorderColor(1, 0.82, 0, 1) end
        if self.outfit then showTip(self, self.outfit) end
    end)
    card:SetScript("OnLeave", function(self)
        if self.border.SetBackdropBorderColor then self.border:SetBackdropBorderColor(0.50, 0.42, 0.22, 1) end
        GameTooltip:Hide()
    end)
    card:SetScript("OnClick", function(self, btn)
        if not self.outfit then return end
        if btn == "RightButton" then
            M:Delete(self.outfit)
        else
            applyOutfit(self.outfit)
        end
    end)

    state.rows[i] = card
    return card
end

function M:Refresh()
    local f = state.content
    if not f then return end
    if not f:IsShown() then return end
    local w, h = f:GetWidth() or 0, f:GetHeight() or 0
    if w <= 1 or h <= 1 then
        if C_Timer and C_Timer.After then C_Timer.After(0.05, function() M:Refresh() end) end
        return
    end
    local outfits = self:GetAll()

    local usableW = w - 2 * PAD
    local usableH = h - HEADER_H - FOOTER_H
    local cardW = math.floor((usableW - (COLS - 1) * GAP) / COLS)
    local cardH = math.floor((usableH - (ROWS - 1) * GAP) / ROWS)
    local perPage = COLS * ROWS

    local totalPages = math.max(1, math.ceil(#outfits / perPage))
    state.page = math.min(math.max(1, state.page or 1), totalPages)
    local page = state.page

    _gen = _gen + 1
    local gen = _gen
    local isValid = function() return _gen == gen end
    local startIdx = (page - 1) * perPage

    for slot = 1, perPage do
        local idx = startIdx + slot
        local o = outfits[idx]
        local card = state.rows[slot] or acquireCard(f, slot)
        if o then
            local col = (slot - 1) % COLS
            local rowN = math.floor((slot - 1) / COLS)
            card:SetSize(cardW, cardH)
            card:ClearAllPoints()
            card:SetPoint("TOPLEFT", f, "TOPLEFT",
                PAD + col * (cardW + GAP),
                -(HEADER_H + rowN * (cardH + GAP)))
            card.label:SetText(o.name or "Outfit")
            card.outfit = o
            card:Show()
            if NC and NC.DressCard then
                NC.DressCard(card, o.items, { idx = slot, isValid = isValid })
            end
        else
            card:Hide()
        end
    end
    for slot = perPage + 1, #state.rows do
        if state.rows[slot] then state.rows[slot]:Hide() end
    end

    if f.pageText then f.pageText:SetText(string.format("Page %d/%d", page, totalPages)) end
    if f.prevBtn then f.prevBtn:SetEnabled(page > 1) end
    if f.nextBtn then f.nextBtn:SetEnabled(page < totalPages) end
    if f.empty then f.empty:SetShown(#outfits == 0) end
end

function M:SetPage(p)
    state.page = p
    self:Refresh()
end

-- -----------------------------------------------------------------------------
-- Content frame
-- -----------------------------------------------------------------------------
local function buildContent(parent)
    local f = CreateFrame("Frame", nil, parent)
    f:SetAllPoints(parent)

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

    f.title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    f.title:SetPoint("TOPLEFT", 10, -8)
    f.title:SetText("ETB Saved Outfits")

    f.hint = f:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    f.hint:SetPoint("LEFT", f.title, "RIGHT", 10, 0)
    f.hint:SetText("Left-click to load, right-click to delete.")

    f.empty = f:CreateFontString(nil, "OVERLAY", "GameFontDisable")
    f.empty:SetPoint("CENTER")
    f.empty:SetText("No saved outfits yet.\nBuild them in the Transmog UI, or right-click a set in ETB Sets.")
    f.empty:Hide()

    f.prevBtn = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    f.prevBtn:SetSize(28, 24)
    f.prevBtn:SetPoint("BOTTOM", -60, 6)
    f.prevBtn:SetText("<")
    f.prevBtn:SetScript("OnClick", function() M:SetPage(math.max(1, (state.page or 1) - 1)) end)

    f.nextBtn = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    f.nextBtn:SetSize(28, 24)
    f.nextBtn:SetPoint("BOTTOM", 60, 6)
    f.nextBtn:SetText(">")
    f.nextBtn:SetScript("OnClick", function() M:SetPage((state.page or 1) + 1) end)

    f.pageText = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    f.pageText:SetPoint("BOTTOM", 0, 10)
    f.pageText:SetText("Page 1/1")

    f:SetScript("OnShow", function() M:Refresh() end)
    return f
end

-- -----------------------------------------------------------------------------
-- Mount + wiring
-- -----------------------------------------------------------------------------
function M:EnsureMounted()
    if state.mounted then return true end
    if C_AddOns and C_AddOns.LoadAddOn then
        pcall(C_AddOns.LoadAddOn, "Blizzard_TransmogShared")
        pcall(C_AddOns.LoadAddOn, "Blizzard_Transmog")
    end
    local tf = _G.TransmogFrame
    local wc = tf and tf.WardrobeCollection
    if not (wc and wc.TabContent and type(wc.AddNamedTab) == "function") then
        return false
    end
    state.wc = wc
    state.content = buildContent(wc.TabContent)
    state.tabID = wc:AddNamedTab("ETB Saved Outfits", state.content)
    state.mounted = true
    return true
end

function M:Initialize()
    local ev = CreateFrame("Frame")
    ev:RegisterEvent("TRANSMOGRIFY_OPEN")
    ev:SetScript("OnEvent", function()
        if C_Timer and C_Timer.After then
            C_Timer.After(0.2, function() M:EnsureMounted() end)
            C_Timer.After(0.7, function() M:EnsureMounted() end)
        else
            M:EnsureMounted()
        end
    end)
end

do
    local boot = CreateFrame("Frame")
    boot:RegisterEvent("PLAYER_LOGIN")
    boot:SetScript("OnEvent", function()
        local function initNativeSavedOutfits()
            if M.Initialize then M:Initialize() end
        end
        if ETB.RunWhenStartupIdle then
            ETB:RunWhenStartupIdle("nativeSavedOutfits.init", initNativeSavedOutfits, { delay = 2, interval = 2, maxWait = 180 })
        else
            initNativeSavedOutfits()
        end
    end)
end
