-- Window composition step 1 (HDG-ADR-025): proves layout.lua can compose a
-- window from its `slots` map. STEP 1 is fill-only -- the `fill` slot resolves
-- to a view and delegates to the existing Compute/GetViewDimensions path, so
-- existing views render identically. This test pins:
--   * the `windows` data shape (main.slots.fill == "@view")
--   * Layout:ResolveFillView -- @view -> active view, fallback on unset/retired,
--     fixed string -> itself, unknown window -> loud error
--   * Layout:ComposeWindow returns placements (a window renders from slots)
--   * Layout:ComposeWindowDimensions delegates to GetViewDimensions(fillView)
--
-- Load mirrors test_layoutconfig_smoke (full LayoutConfig + Layout). `decor` is
-- a static view (no dynamic tracks) so GetViewDimensions/Compute on it need no
-- per-tab selectors beyond the base registry.

dofile("wow_mock.lua")
dofile("../Core/HDGR_SchemeConstants.lua")
dofile("../Core/HDGR_Constants.lua")
dofile("../Core/HDGR_Format.lua")
dofile("../Core/HDGR_Paths.lua")
dofile("../Core/HDGR_Log.lua")
dofile("../Core/HDGR_Locale.lua")
dofile("../Core/HDGR_TableUtils.lua")
dofile("../Core/HDGR_Memo.lua")
dofile("../Core/HDGR_BlizzardEvents.lua")
dofile("../Core/HDGR_Environment.lua")
dofile("../Core/HDGR_WidgetTypes.lua")
dofile("../Core/HDGR_TooltipEngine.lua")
dofile("../Core/HDGR_TooltipRecipes.lua")
dofile("../Core/HDGR_Modules.lua")
dofile("../Core/HDGR_Middleware.lua")
dofile("../Core/HDGR_Store.lua")
dofile("../UI/HDGR_Theme.lua")
dofile("../UI/HDGR_LayoutConfig.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Decor.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Acquisition.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Recipes.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Alts.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Mogul.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Styles.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Trainers.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_HouseTab.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Debug.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Config.lua")
dofile("../UI/HDGR_Window.lua")
dofile("../UI/HDGR_Selectors.lua")
dofile("../UI/HDGR_Selectors_Lumber.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Lumber.lua")
dofile("../UI/HDGR_Selectors_Shopping.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Shopping.lua")
dofile("../UI/HDGR_Selectors_Zone.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Zone.lua")
dofile("../UI/LayoutConfig/HDGR_LayoutConfig_Nav.lua")
dofile("../UI/HDGR_Components.lua")
dofile("../UI/HDGR_Layout.lua")

HDG.WidgetTypes:Flush()

local function assertTrue(v, label) if not v then error(label, 2) end end
local function assertEq(a, b, label)
    if a ~= b then error(label .. ": expected " .. tostring(b) .. ", got " .. tostring(a), 2) end
end

local LC     = HDG.LayoutConfig
local Layout = HDG.Layout

-- A live-state stand-in for the ResolveFillView resolver, which reads only
-- state.account.ui.view -- pass it directly (no Store) so we control the view.
local function stateWithView(view)
    return { account = { ui = { view = view } } }
end

-- A full default state for the Compute-backed assertions (ComposeWindow runs a
-- real Compute, which evaluates every panel's shouldRender selectors -- those
-- dive into account/session slots a minimal stub doesn't carry).
HDG.Store:LoadFromSavedVariables()
local realState = HDG.Store:GetState()
realState.account.ui.view = "decor"

-- ===== Windows data shape =====
assertTrue(LC.windows, "windows table present")
assertTrue(LC.windows.main, "main window present")
assertTrue(LC.windows.main.slots, "main has slots map")
assertEq(LC.windows.main.slots.fill, "@view", "main.slots.fill is the @view content slot")

-- ===== ResolveFillView: @view -> active view =====
assertEq(Layout:ResolveFillView(LC, "main", stateWithView("decor")), "decor",
    "@view resolves to the active view (decor)")
assertEq(Layout:ResolveFillView(LC, "main", stateWithView("mogul")), "mogul",
    "@view resolves to the active view (mogul)")

-- ===== ResolveFillView: fallback on unset / retired view =====
assertEq(Layout:ResolveFillView(LC, "main", stateWithView("noSuchView")), LC.window.defaultView,
    "@view falls back to defaultView when the view name is unknown")
assertEq(Layout:ResolveFillView(LC, "main", stateWithView(nil)), LC.window.defaultView,
    "@view falls back to defaultView when the view is unset")

-- ===== ResolveFillView: a fixed (non-@view) fill returns itself =====
-- A satellite window's fill is a literal view name -- not state-driven.
LC.windows.__test_fixed = { slots = { fill = "decor" } }
assertEq(Layout:ResolveFillView(LC, "__test_fixed", stateWithView("mogul")), "decor",
    "a fixed fill returns its literal view regardless of state.view")
LC.windows.__test_fixed = nil

-- ===== ResolveFillView: unknown window fails loud =====
local okUnknown = pcall(function() Layout:ResolveFillView(LC, "noSuchWindow", stateWithView("decor")) end)
assertTrue(not okUnknown, "ResolveFillView errors on an unknown window name")

-- ===== ComposeWindow: a window renders from its slots =====
-- decor is static -> Compute needs no per-tab selectors. Placements is the
-- id->rect map Layout:Apply consumes; a non-empty table proves composition ran.
local placements = Layout:ComposeWindow(LC, "main", { state = realState })
assertTrue(type(placements) == "table", "ComposeWindow returns a placements table")
assertTrue(next(placements) ~= nil, "ComposeWindow placements is non-empty (the view composed)")

-- ===== ComposeWindowDimensions: main = chrome(top) + fill + status(bottom) =====
-- `main` slots = { top="chrome", bottom="status", fill="@view" }. Width is the
-- fill view's width (no left/right); height sums the chrome + fill + status
-- slot heights. (Holds before AND after the per-view chrome/status strip --
-- whatever the fill view's current height, main adds the two slot heights.)
local cw, ch = Layout:ComposeWindowDimensions(LC, "main", realState)
local fillW, fillH = Layout:GetViewDimensions(LC, "decor")
local _, chromeH   = Layout:GetViewDimensions(LC, "chrome")
local _, statusH   = Layout:GetViewDimensions(LC, "status")
local navW         = (Layout:GetViewDimensions(LC, "nav"))
assertEq(cw, navW + fillW, "main width == nav (left slot) + fill view width")
assertEq(ch, fillH + chromeH + statusH, "main height == chrome + fill + status slot heights")
assertTrue(type(cw) == "number" and cw > 0, "composed width is a positive number")
assertTrue(type(ch) == "number" and ch > 0, "composed height is a positive number")

-- ===== Slot grid (step 3a): top/bottom/left/right offsets + corner overlay =====
-- Synthetic edge views (fixed dims, no content) + a test window composing them
-- around the decor fill. Proves the window sizes from slot extents and the fill
-- placements translate into the body region.
LC.window.views.__t_title = { standalone = true, width = 100, height = 20, columns = { 100 }, rows = { 20 }, cells = {} }
LC.window.views.__t_status = { standalone = true, width = 100, height = 14, columns = { 100 }, rows = { 14 }, cells = {} }
LC.window.views.__t_left   = { standalone = true, width = 50,  height = 30, columns = { 50 },  rows = { 30 }, cells = {} }
LC.window.views.__t_close  = { standalone = true, width = 16,  height = 16, columns = { 16 },  rows = { 16 }, cells = {} }

local decorW, decorH = Layout:GetViewDimensions(LC, "decor")

-- top + bottom only: W unchanged, H grows by 20 + 14.
LC.windows.__t_win = { slots = { top = "__t_title", bottom = "__t_status", fill = "decor" } }
local tbW, tbH = Layout:ComposeWindowDimensions(LC, "__t_win", realState)
assertEq(tbW, decorW, "top/bottom slots leave width = fill width")
assertEq(tbH, decorH + 20 + 14, "top/bottom slots add their heights to fill height")

-- The slot origin offsets the fill's CELLS (panel positions only); sections +
-- widgets are parent-relative and inherit the shift via their moved panel, so
-- their placement values must NOT change (the dead-band regression: offsetting
-- them too pushed content down inside each panel). Pick one shared PANEL id and
-- one shared section/widget id.
local decorP = Layout:Compute(LC, { view = "decor", state = realState })
local winP   = Layout:ComposeWindow(LC, "__t_win", { state = realState })
local panelId, childId
for id in pairs(decorP) do
    if winP[id] then
        if LC.panels[id] and not panelId then panelId = id
        elseif not LC.panels[id] and not childId then childId = id end
    end
end
assertTrue(panelId ~= nil, "fill view contributes a panel placement to the composed window")
assertTrue(childId ~= nil, "fill view contributes a section/widget placement")
assertEq(winP[panelId].y - decorP[panelId].y, 20, "fill PANEL offset down by top slot height")
assertEq(winP[panelId].x - decorP[panelId].x, 0,  "fill PANEL not offset horizontally (no left slot)")
assertEq(winP[childId].y - decorP[childId].y, 0, "section/widget is parent-relative -- NOT offset (no double-shift)")
assertEq(winP[childId].x - decorP[childId].x, 0, "section/widget x is parent-relative -- NOT offset")

-- left slot: W grows by 50; the fill's PANELS shift right by 50 (children follow).
LC.windows.__t_win.slots = { left = "__t_left", fill = "decor" }
local lW = (Layout:ComposeWindowDimensions(LC, "__t_win", realState))
assertEq(lW, decorW + 50, "left slot adds its width to the window width")
local winPL = Layout:ComposeWindow(LC, "__t_win", { state = realState })
assertEq(winPL[panelId].x - decorP[panelId].x, 50, "fill PANEL shifts right by left slot width")
assertEq(winPL[childId].x - decorP[childId].x, 0,  "section/widget x stays parent-relative under a left slot")

-- corner is an overlay -> does not change the window size.
LC.windows.__t_win.slots = { top = "__t_title", corner = "__t_close", fill = "decor" }
local cwW, cwH = Layout:ComposeWindowDimensions(LC, "__t_win", realState)
assertEq(cwW, decorW, "corner overlay leaves width = fill width")
assertEq(cwH, decorH + 20, "corner overlay does not add to height (only the top slot does)")
assertTrue(type(Layout:ComposeWindow(LC, "__t_win", { state = realState })) == "table",
    "ComposeWindow with a corner slot returns placements without error")

-- Cleanup synthetic entries so later assertions / other tests are unaffected.
LC.windows.__t_win = nil
LC.window.views.__t_title, LC.window.views.__t_status = nil, nil
LC.window.views.__t_left, LC.window.views.__t_close = nil, nil

print("test_window_compose: PASS")
