-- VDS_MainFrame
-- ============================================================================
-- Window chrome -- FloatingWidget shape lifted from the donor's
-- VDS_Framework.lua (movable + panel bg texture + edge shadows + close
-- button + position save). The panels INSIDE the window are declared
-- entirely in LayoutConfig + instantiated by Layout:BuildAll.
--
-- The Store subscriber here owns:
--   - BindingEngine repaint per dispatch
--   - Visibility reconciler (sole caller of f:Show/Hide; SSoT =
--     state.session.ui.windowShown)
--   - Layout recompute on view change (currentView selector flips ->
--     re-Apply placements + window size).

VDS = VDS or {}
VDS.MainFrame = VDS.MainFrame or {}

local M = VDS.MainFrame
local WHITE = "Interface\\Buttons\\WHITE8x8"

function M:Build()
    if self.frame then return self.frame end
    if not _G.CreateFrame then return nil end

    local f = CreateFrame("Frame", "VamoosesDyeStudio_MainFrame", _G.UIParent)
    -- MEDIUM strata: peer to character sheet / spellbook / housing editor so
    -- they layer normally instead of VDS always covering them. SetToplevel
    -- still pops VDS to the top of its strata on click for normal focus UX.
    f:SetFrameStrata("MEDIUM")
    f:SetToplevel(true)
    f:SetClampedToScreen(true)
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:Hide()
    tinsert(_G.UISpecialFrames, "VamoosesDyeStudio_MainFrame")

        -- ===== Background texture =====
    local bg = f:CreateTexture(nil, "BACKGROUND", nil, -8)
    bg:SetAllPoints()
    bg:SetTexture("Interface\\AddOns\\VamoosesDyeStudio\\textures\\vds_panel_bg")
    bg:SetTexCoord(0, 1, 0, 1)
    VDS.Theme:Register(bg, "WindowBg")
    f._bg = bg

    -- ===== Edge shadows =====
    local edges = {
        { "TOPLEFT", "TOPRIGHT",       0, -1, false },
        { "BOTTOMLEFT", "BOTTOMRIGHT", 0,  1, false },
        { "TOPLEFT", "BOTTOMLEFT",     1,  0, true  },
        { "TOPRIGHT", "BOTTOMRIGHT",  -1,  0, true  },
    }
    for _, e in ipairs(edges) do
        local s = f:CreateTexture(nil, "BACKGROUND", nil, -7)
        s:SetTexture(WHITE)
        VDS.Theme:Register(s, "WindowEdge")
        s:SetPoint(e[1], e[3], e[4])
        s:SetPoint(e[2], e[3], e[4])
        if e[5] then s:SetWidth(1) else s:SetHeight(1) end
    end

    -- ===== Drag handlers + position persistence =====
    f:SetScript("OnDragStart", function(self) self:StartMoving() end)
    f:SetScript("OnDragStop", function(self2)
        self2:StopMovingOrSizing()
        -- SSoT for subsequent layouts: title panel screen position, not the
        -- frame anchor (refreshLayout re-anchors from titleAnchor + placement offset).
        local tp = self2.panels and self2.panels.titlePanel  -- exception(nullable): panels absent before first BuildAll
        if tp and tp:GetLeft() and tp:GetTop() then
            M._titleAnchor = { x = tp:GetLeft(), y = tp:GetTop() }
            VDS.Store:Dispatch({
                type    = VDS.Constants.ACTIONS.PANEL_POSITION_SAVE,
                payload = { titleX = M._titleAnchor.x, titleY = M._titleAnchor.y },
            })
        end
    end)

    -- Transient anchor: only visible for ~1 frame before first refreshLayout
    -- runs (or until saved titleX/Y is restored).
    f:SetPoint("RIGHT", _G.UIParent, "RIGHT", -50, 0)

    -- ===== Close button (top-right) =====
    -- XMarksTheSpot atlas; grey idle -> hover. Dispatches MAIN_WINDOW_TOGGLE.
    -- Initial anchor is outer frame; refreshLayout re-anchors to titlePanel
    -- so the X stays on the main panel across canvas/config side-panel views.
    local closeBtn = CreateFrame("Button", nil, f)
    closeBtn:SetSize(22, 22)
    closeBtn:SetPoint("TOPRIGHT", -6, -6)
    closeBtn:SetFrameLevel(f:GetFrameLevel() + 10)
    self._closeBtn = closeBtn
    local closeIcon = closeBtn:CreateTexture(nil, "ARTWORK")
    closeIcon:SetSize(18, 18)
    closeIcon:SetPoint("CENTER")
    closeIcon:SetAtlas(VDS.Constants.ATLAS.CLOSE_X)
    VDS.Theme:Register(closeIcon, "CloseIcon", { hover = false })
    closeBtn:SetScript("OnEnter", function()
        VDS.Theme:SetState(closeIcon, { hover = true })
        VDS.Theme:Apply(closeIcon, "CloseIcon")
    end)
    closeBtn:SetScript("OnLeave", function()
        VDS.Theme:SetState(closeIcon, { hover = false })
        VDS.Theme:Apply(closeIcon, "CloseIcon")
    end)
    VDS.TooltipEngine:Attach(closeBtn, VDS.TooltipRecipes.Close)
    closeBtn:SetScript("OnClick", function()
        VDS.Store:Dispatch({ type = VDS.Constants.ACTIONS.MAIN_WINDOW_TOGGLE })
    end)

    self.frame = f

    -- LayoutConfig validation: cell-name typos produce invisible widgets with no error (cookbook 17).
    -- AssertValid raises so a bad config crashes at boot instead of silently
    -- printing to the debug log nobody reads.
    VDS.Layout:AssertValid(VDS.LayoutConfig)

    -- ===== Build LayoutConfig panel tree =====
    VDS.Layout:BuildAll(f, VDS.LayoutConfig)

    -- Default "frame" kind is transparent (donor pattern); explicit chrome
    -- overrides registered here. Donor parallel: VDS_Framework.lua:563.
    local PANEL_SKINS = { detailPanel = "DetailStrip" }
    for panelId, role in pairs(PANEL_SKINS) do
        local panel = f.panels and f.panels[panelId]
        if panel then VDS.Theme:Register(panel, role) end
    end

    -- ===== BindingEngine wire (no paint yet) =====
    VDS.BindingEngine:Build(f, VDS.LayoutConfig)

    -- Compute -> Apply (HDGR_MainFrame:240-248 pattern).
    local function refreshLayout()
        local state = VDS.Store:GetState()
        local view  = VDS.Selectors:Call("currentView", state, {})

        local intrinsics
        if f.widgets then
            intrinsics = {}
            for id, widget in pairs(f.widgets) do
                if widget._intrinsicWidth or widget._intrinsicHeight then
                    intrinsics[id] = {
                        width  = widget._intrinsicWidth,
                        height = widget._intrinsicHeight,
                    }
                end
            end
        end
        -- ADR-025 pipeline: Normalize -> Compute -> Apply. Normalize resolves
        -- all shorthand (padding tokens, "auto"/"content" sizes, visible
        -- selectors, per-view cell maps, dynamic-track selectors) into a
        -- canonical config. Compute is pure over that canonical input.
        local normalized = VDS.Layout:Normalize(VDS.LayoutConfig, {
            view       = view,
            state      = state,
            intrinsics = intrinsics or {},
        })
        local placements, frameW, frameH = VDS.Layout:Compute(normalized)
        if frameW and frameH then f:SetSize(frameW, frameH) end
        f.placements = placements
        VDS.Layout:Apply(f, placements)

        -- Pin close X to titlePanel, not outer frame -- canvas/config side panels
        -- grow the outer bounds and drag the X off the main panel.
        -- Re-anchored every refresh (titlePanel created during BuildAll, after closeBtn).
        if self._closeBtn and f.panels and f.panels.titlePanel then  -- exception(nullable): titlePanel created during BuildAll, after closeBtn
            self._closeBtn:ClearAllPoints()
            self._closeBtn:SetPoint("TOPRIGHT", f.panels.titlePanel, "TOPRIGHT", -6, -2)
        end

        -- Keep titlePanel.TOPLEFT invariant across view changes by back-computing
        -- frame.TOPLEFT from the title panel's placement offset:
        --   frame.LEFT = anchor.x - region.x
        --   frame.TOP  = anchor.y + region.y  (region.y is downward)
        -- Captured on first paint or restored from SavedVars; updated on drag.
        local titleR = placements and placements.titlePanel  -- exception(nullable): placements may omit a panel pre-solve
        if f:IsShown() and titleR then
            if not self._titleAnchor then
                local saved = VDS.Store:GetState().account.panelPosition  -- top-level only: first-paint title anchor restore
                if saved and saved.titleX and saved.titleY then  -- exception(boundary): SavedVars position absent until first drag
                    self._titleAnchor = { x = saved.titleX, y = saved.titleY }
                else
                    local tp = f.panels and f.panels.titlePanel  -- exception(nullable): panels absent before first BuildAll
                    if tp and tp:GetLeft() and tp:GetTop() then
                        self._titleAnchor = { x = tp:GetLeft(), y = tp:GetTop() }
                    end
                end
            end
            if self._titleAnchor then
                local fL = self._titleAnchor.x - titleR.x
                local fT = self._titleAnchor.y + titleR.y
                f:ClearAllPoints()
                f:SetPoint("TOPLEFT", _G.UIParent, "BOTTOMLEFT", fL, fT)
            end
        end
    end

    -- Boot: Layout -> Apply -> Layout (re-flow after visibleWhen hides).
    refreshLayout()
    VDS.BindingEngine:Apply(f, VDS.Store:GetState(), {}, "*")
    refreshLayout()

    -- Controllers wire OnClick / OnTextChanged to LayoutConfig widgets.
    -- Row factories attach clicks directly (call Controller methods inline).
    VDS.Controller_Title:Wire(f)
    VDS.Controller_Search:Wire(f)
    VDS.Controller_Detail:Wire(f)
    VDS.Controller_Canvas:Wire(f)
    VDS.Controller_Live:Wire(f)
    VDS.Controller_Minimap:Wire(f)
    VDS.Controller_Browse:Wire(f)
    VDS.Controller_Warehouse:Wire(f)
    VDS.Controller_Harmony:Wire(f)

    -- Store subscriber: mirrors boot order (refreshLayout -> Apply -> refreshLayout).
    -- Leading refresh sizes panels before dispatchers read widget widths (tabBar);
    -- trailing refresh re-flows after visibleWhen changes from Apply.
    -- Visibility transitions BEFORE Apply so BindingEngine default-skip
    -- sees the correct IsShown() state. If we Apply first and Show after,
    -- bound widgets would skip (parent hidden) then become visible with
    -- stale state. Phase 0 of async-resolver migration depends on this order.
    VDS.Store:Subscribe(function(actionType, invalidation)
        local shown = VDS.Store:GetState().session.ui.windowShown
        -- Visibility reconciler: owns Show/Hide. Runs FIRST so widget IsShown()
        -- is correct by the time BindingEngine:Apply walks bound widgets.
        if shown and not f:IsShown() then
            f:Show()
            -- One-time title color-cycle on first open per session (_titleAnimPlayed not saved).
            if not VDS._titleAnimPlayed then
                VDS._titleAnimPlayed = true
                local title = f.widgets and f.widgets["title.text"]
                if title and title.StartCycleAnimation then  -- exception(optional): StartCycleAnimation is kind-specific; title widget optional
                    title:StartCycleAnimation(3.0)
                end
            end
        elseif not shown and f:IsShown() then
            f:Hide()
        end
        if shown then refreshLayout() end          -- size panels before BindingEngine reads widget widths
        VDS.BindingEngine:Apply(f, VDS.Store:GetState(), { actionType = actionType }, invalidation)
        if shown then refreshLayout() end          -- re-flow after visibleWhen changes from Apply
    end)

    return f
end

function M:Show()
    self:Build()
    if not VDS.Store:GetState().session.ui.windowShown then
        VDS.Store:Dispatch({ type = VDS.Constants.ACTIONS.MAIN_WINDOW_TOGGLE })
    end
end

function M:Hide()
    if not self.frame then return end
    if VDS.Store:GetState().session.ui.windowShown then
        VDS.Store:Dispatch({ type = VDS.Constants.ACTIONS.MAIN_WINDOW_TOGGLE })
    end
end

function M:Toggle()
    self:Build()
    VDS.Store:Dispatch({ type = VDS.Constants.ACTIONS.MAIN_WINDOW_TOGGLE })
end
