local addonName, privateDB = ...

-- ============================================================================
-- HOUSE TAB MODULE  (v2.38b cockpit shell)
-- House level XP progress and decor collection stats by source/expansion.
-- All widget rendering lives in UI/HouseTab/widgets/. Layout, Picker, Editor,
-- and Aggregator handle composition. This file is a thin shell around the
-- cockpit machinery.
-- ============================================================================

local CONFIG = privateDB.CONFIG
local L      = HDG_L or {}
local HDG_HouseTab = {}
privateDB.HDG_HouseTab = HDG_HouseTab

local HDG       = _G.HousingDecorGuide
local HDG_HT    = privateDB.HDG_HT  -- cockpit namespace (Layout, Resolver, Cards, etc.)
local LayoutStore = privateDB.HDG_HT.LayoutStore

local mainFrame   = nil
local panel       = nil   -- TabPanel result
local cardsByID   = {}
local stickyStrip = nil   -- non-scrolling header strip (level / XP / slots / lumber)
local _subscribed = false  -- one-time subscribe to Aggregator broadcasts

-- Sticky header strip — pinned to TOP of panel.frame, above scrollFrame.
-- Stays visible while the dashboard scrolls. Single line:
--   Level N  ·  X / Y XP  ·  Slots O / M  ·  Lumber count
local STICKY_H = 28

local function FormatNumber(n)
    if BreakUpLargeNumbers then return BreakUpLargeNumbers(n) end
    return tostring(n)
end

local function RefreshStickyStrip(snap)
    if not stickyStrip then return end
    local Theme = privateDB.HDG_HT and privateDB.HDG_HT.Theme
    local cAccent = (Theme and Theme.C_Accent and Theme.C_Accent()) or "|cFFE0B040"
    local cText   = (Theme and Theme.C_Text   and Theme.C_Text())   or "|cFFFFFFFF"
    local cDim    = (Theme and Theme.C_Dim    and Theme.C_Dim())    or "|cFF888888"
    local sep     = "  " .. cDim .. "·|r  "

    local UI = _G.HDG_UI
    local hd = UI and UI.GetCachedHouseData and UI.GetCachedHouseData()
    local parts = {}

    if hd then
        parts[#parts + 1] = string.format("%sLevel|r %s%d|r", cDim, cText, hd.level or 0)
        local maxXP = (hd.thresholds and hd.thresholds[hd.maxLevel]) or hd.thresholds and hd.thresholds[#hd.thresholds] or 0
        local atMax = hd.level and hd.maxLevel and hd.level >= hd.maxLevel
        if atMax then
            parts[#parts + 1] = string.format("%s%s|r %sXP %s(MAX)|r",
                cText, FormatNumber(hd.xp or 0), cDim, cAccent)
        else
            parts[#parts + 1] = string.format("%s%s|r %s/ %s|r %sXP|r",
                cText, FormatNumber(hd.xp or 0),
                cDim,  FormatNumber(maxXP), cDim)
        end
    else
        parts[#parts + 1] = cDim .. "No House Data|r"
    end

    if snap and snap.capacity then
        parts[#parts + 1] = string.format("%sSlots|r %s%s %s/ %s|r",
            cDim, cText, FormatNumber(snap.capacity.owned or 0),
            cDim, FormatNumber(snap.capacity.max or 0))
    end

    if snap and snap.wallet and snap.wallet.lumber then
        local total = 0
        for _, l in ipairs(snap.wallet.lumber) do total = total + (l.count or 0) end
        parts[#parts + 1] = string.format("%sLumber|r %s%s|r",
            cDim, cText, FormatNumber(total))
    end

    if snap and (snap.firstCatchXPLeft or 0) > 0 then
        parts[#parts + 1] = string.format("%s+%s|r %scollectible XP|r",
            cAccent, FormatNumber(snap.firstCatchXPLeft), cDim)
    end

    stickyStrip.text:SetText(table.concat(parts, sep))
end

local function CreateStickyStrip(parentFrame)
    if stickyStrip then return stickyStrip end
    local strip = CreateFrame("Frame", nil, parentFrame, "BackdropTemplate")
    strip:SetHeight(STICKY_H)
    strip:SetPoint("TOPLEFT",  parentFrame, "TOPLEFT",  4, -4)
    strip:SetPoint("TOPRIGHT", parentFrame, "TOPRIGHT", -4, -4)

    local scheme = (HDG and HDG.Theme and HDG.Theme.GetCurrentScheme and HDG.Theme.GetCurrentScheme()) or {
        panel  = { r = 0.10, g = 0.12, b = 0.15 },
        border = { r = 0.30, g = 0.35, b = 0.40 },
        accent = { r = 0.85, g = 0.70, b = 0.30 },
    }
    strip:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
        insets   = { left = 0, right = 0, top = 0, bottom = 0 },
    })
    strip:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.75)
    strip:SetBackdropBorderColor(scheme.border.r, scheme.border.g, scheme.border.b, 0.4)

    local accentBar = strip:CreateTexture(nil, "ARTWORK")
    accentBar:SetTexture("Interface\\Buttons\\WHITE8x8")
    accentBar:SetSize(2, STICKY_H - 8)
    accentBar:SetPoint("LEFT", strip, "LEFT", 6, 0)
    accentBar:SetVertexColor(scheme.accent.r, scheme.accent.g, scheme.accent.b, 0.85)

    strip.text = strip:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    strip.text:SetPoint("LEFT", accentBar, "RIGHT", 8, 0)
    strip.text:SetPoint("RIGHT", strip, "RIGHT", -8, 0)
    strip.text:SetJustifyH("LEFT")
    strip.text:SetWordWrap(false)
    local CockpitTheme = privateDB.HDG_HT and privateDB.HDG_HT.Theme
    if CockpitTheme and CockpitTheme.ApplyFont then
        CockpitTheme.ApplyFont(strip.text, scheme, "small")
    end

    stickyStrip = strip
    return strip
end

-- ----------------------------------------------------------------------------
-- Layout state via Store (SetWidget* setters + LayoutStore facade).
-- HDG_DB.houseTab persistence is owned by Store now.
-- ----------------------------------------------------------------------------
local function GetSV()
    return LayoutStore.GetAll()
end

-- ----------------------------------------------------------------------------
-- Render: build cards into the scrollChild for the current effective layout.
-- Identical responsibilities to TestWindow.Render but anchored to the real
-- HDG main-frame tab.
-- ----------------------------------------------------------------------------
local function ClearCards()
    for _, c in pairs(cardsByID) do c:Hide(); c:SetParent(nil) end
    cardsByID = {}
end

local function Render()
    if not panel or not panel.scrollChild then return end
    if not HDG_HT or not HDG_HT.LayoutConfig then return end
    ClearCards()

    local cfg        = HDG_HT.LayoutConfig
    local effective  = HDG_HT.Resolver.Resolve(cfg, GetSV())
    local placements = HDG_HT.Layout.Compute(effective, cfg.grid)

    local content = panel.scrollChild
    local maxBottom = 0

    for _, w in ipairs(effective) do
        local p = placements[w.id]
        if p then
            -- Title from widget module if registered, else widget id
            local titleStr = w.id
            if HDG_HT.Registry and HDG_HT.Registry.HasWidget(w.id) then
                for _, mod in pairs(privateDB.HDG_HT.Widgets or {}) do
                    if mod.id == w.id then titleStr = mod.title or w.id; break end
                end
            end

            local card = HDG_HT.Cards.Create(content, {
                id     = w.id,
                title  = titleStr,
                width  = p.width,
                height = p.height,
                chrome = w.chrome or "card",
                titleBarHeight = cfg.cards.titleBarHeight,
                bodyPaddingX   = cfg.cards.bodyPaddingX,
                bodyPaddingY   = cfg.cards.bodyPaddingY,
            })
            card:ClearAllPoints()
            card:SetPoint("TOPLEFT", content, "TOPLEFT", p.x, p.y)

            if HDG_HT.Registry and HDG_HT.Registry.HasWidget(w.id) then
                local factory = HDG_HT.Registry.Get(w.id)
                local widget = factory(card.body, w)
                widget:Resize(card.body:GetWidth(), card.body:GetHeight())
                local snap = HDG_HT.Aggregator and HDG_HT.Aggregator.GetSnapshot()
                if snap then widget:Refresh(snap) end
                card._widget = widget
            elseif HDG_HT.Placeholder then
                HDG_HT.Placeholder.Render(card, w, { row = 0 })
            end

            if HDG_HT.Editor and HDG_HT.Editor.IsActive() then
                HDG_HT.Editor.Decorate(card, w.id)
            end

            cardsByID[w.id] = card
            local bottom = -p.y + p.height
            if bottom > maxBottom then maxBottom = bottom end
        end
    end

    content:SetHeight(maxBottom + cfg.grid.padding)
end

-- ----------------------------------------------------------------------------
-- Lazy create the tab panel + Customize/Design Mode buttons. First Show().
-- ----------------------------------------------------------------------------
local function CreateHouseTab()
    if panel then return end
    if not mainFrame then return end

    panel = HDG.UI:TabPanel(mainFrame, { hasHeaderBar = false })
    if not panel.scrollChild then return end

    -- Build the sticky header strip first (28px tall, anchored TOP of panel.frame
    -- at y=-4). Scroll viewport starts beneath it.
    CreateStickyStrip(panel.frame)

    -- Anchor the scrollFrame BELOW the sticky strip. Top inset = 4 (strip top
    -- gap) + STICKY_H (28) + 4 (gap to first card) = 36.
    if panel.scrollFrame then
        panel.scrollFrame:ClearAllPoints()
        panel.scrollFrame:SetPoint("TOPLEFT",     panel.frame, "TOPLEFT",      8,  -(4 + STICKY_H + 4))
        panel.scrollFrame:SetPoint("BOTTOMRIGHT", panel.frame, "BOTTOMRIGHT", -18,   8)
    end

    -- Match content width to the scroll viewport (scrollbar already reserved).
    panel.scrollChild:SetWidth((panel.scrollFrame and panel.scrollFrame:GetWidth())
        or HDG_HT.LayoutConfig.grid.totalWidth)
    if panel.scrollFrame then
        panel.scrollFrame:SetScript("OnSizeChanged", function(_, w)
            panel.scrollChild:SetWidth(w)
            Render()
        end)
    end

    -- ----------------------------------------------------------------------
    -- Customize + Design Mode: small atlas icon buttons floating top-right
    -- of the dashboard. 3-state Blizzard housing atlases:
    --   decor-controls-settings-default / -pressed / -active   (Customise)
    --   decor-controls-inspect-default  / -pressed / -active   (Design Mode)
    -- "active" atlas swaps to the normal slot while the toggle is ON
    -- (picker open / design mode active) so the button reads its state.
    -- ----------------------------------------------------------------------
    local ICON_SIZE = 28

    local function MakeIconButton(parent, atlasBase, tooltipTitle, tooltipBody)
        local btn = CreateFrame("Button", nil, parent)
        btn:SetSize(ICON_SIZE, ICON_SIZE)
        btn._base   = atlasBase
        btn._active = false
        btn:SetNormalAtlas(atlasBase .. "-default")
        btn:SetPushedAtlas(atlasBase .. "-pressed")
        btn:SetHighlightAtlas(atlasBase .. "-active")
        btn._refresh = function(self)
            if self._active then
                self:SetNormalAtlas(self._base .. "-active")
            else
                self:SetNormalAtlas(self._base .. "-default")
            end
        end
        btn:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT")
            GameTooltip:AddLine(tooltipTitle, 1, 0.82, 0.3)
            if tooltipBody then GameTooltip:AddLine(tooltipBody, 0.7, 0.7, 0.7, true) end
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
        return btn
    end

    local customize = MakeIconButton(panel.frame, "decor-controls-settings",
        "Customise Layout", "Toggle the picker drawer to enable / disable widgets, change widths, or reorder.")
    customize:SetPoint("BOTTOMRIGHT", panel.frame, "BOTTOMRIGHT", -6, 6)
    -- Float above any card that scrolls under the icons.
    if panel.scrollFrame then
        customize:SetFrameLevel(panel.scrollFrame:GetFrameLevel() + 10)
    end
    customize:SetScript("OnClick", function()
        if HDG_HT.Picker and HDG_HT.Picker.Toggle then
            HDG_HT.Picker.Toggle(panel.frame, Render)
        end
        customize._active = HDG_HT.Picker and HDG_HT.Picker.IsShown and HDG_HT.Picker.IsShown() or false
        customize:_refresh()
    end)

    local design = MakeIconButton(panel.frame, "decor-controls-inspect",
        "Design Mode", "Drag to reorder cards, drag right edge to resize width, drag bottom edge to resize height. Click the gear in any card title for dimension sliders.")
    design:SetPoint("RIGHT", customize, "LEFT", -4, 0)
    if panel.scrollFrame then
        design:SetFrameLevel(panel.scrollFrame:GetFrameLevel() + 10)
    end
    design:SetScript("OnClick", function()
        if not (HDG_HT.Editor and HDG_HT.Editor.SetActive) then return end
        local newState = not HDG_HT.Editor.IsActive()
        HDG_HT.Editor.SetActive(newState, panel.frame, Render)
        design._active = newState
        design:_refresh()
        Render()
    end)

    -- Subscribe to Aggregator broadcasts so widgets + sticky strip refresh
    -- when the snapshot rebuilds.
    if not _subscribed and HDG_HT.Aggregator and HDG_HT.Aggregator.Subscribe then
        HDG_HT.Aggregator.Subscribe(function(snap)
            if not panel.frame:IsShown() then return end
            RefreshStickyStrip(snap)
            for _, c in pairs(cardsByID) do
                if c._widget and c._widget.Refresh then
                    c._widget:Refresh(snap)
                end
            end
        end)
        _subscribed = true
    end
end

-- ============================================================================
-- PUBLIC API (preserved surface — Init/Show/Hide/Refresh)
-- ============================================================================

function HDG_HouseTab.Init(frame)
    mainFrame = frame
end

function HDG_HouseTab.Show()
    if not panel then CreateHouseTab() end
    if panel and panel.frame then
        panel.frame:Show()
        Render()
        -- Paint the strip with whatever the Aggregator already has cached so
        -- it isn't blank for a frame while we wait for the rebuild broadcast.
        if HDG_HT and HDG_HT.Aggregator and HDG_HT.Aggregator.GetSnapshot then
            RefreshStickyStrip(HDG_HT.Aggregator.GetSnapshot())
        end
        if HDG_HT and HDG_HT.Aggregator and HDG_HT.Aggregator.RequestRebuild then
            HDG_HT.Aggregator.RequestRebuild()
        end
    end
end

function HDG_HouseTab.Hide()
    if panel and panel.frame then panel.frame:Hide() end
end

function HDG_HouseTab.Refresh()
    Render()
end

-- ============================================================================
-- Cache invalidation: HDG_COLLECTION_READY → request fresh snapshot
-- (Aggregator already listens to this and other invalidation events; this
-- is a belt-and-braces hook so the visible tab refreshes immediately when
-- the central owned set changes.)
-- ============================================================================
do
    if HDG and HDG.EventBus then
        HDG.EventBus:Register("HDG_COLLECTION_READY", function()
            if panel and panel.frame and panel.frame:IsShown() then
                Render()
            end
        end)
        -- Re-render cockpit on any layout mutation (Picker/Editor setters
        -- fire HDG_HT_LAYOUT_CHANGED via Store). Render() guards on visibility.
        HDG.EventBus:Register("HDG_HT_LAYOUT_CHANGED", function()
            Render()
        end)
    end
end
