--[[
	Cerebro - Main UI Frame
	Main window and UI management
]]--

local AddonName, PrivateTable = ...
local Cerebro = LibStub("AceAddon-3.0"):GetAddon(AddonName)
local AceGUI = LibStub("AceGUI-3.0")

local UI = Cerebro:NewModule("UI", "AceEvent-3.0")

-- UI State
UI.mainFrame = nil
UI.currentTab = "standings"
UI.standingsSortKey = "pr"
UI.refreshTimer = nil
UI.refreshDelay = 0.3 -- seconds
UI.searchText = {} -- Search state for History/Audit tabs

function UI:OnInitialize()
	-- Init
end

function UI:OnEnable()
	-- Enable
end

--[[=============================================
	Helper Methods (Nil-Safe, RBAC, Refresh)
===============================================]]

function UI:GetModuleSafe(name)
	local mod = Cerebro:GetModule(name, true)  -- silent=true prevents error
	if not mod then
		Cerebro:Debug("UI: Module '" .. name .. "' not available")
	end
	return mod
end

function UI:IsOfficerSafe()
	return Cerebro and Cerebro.IsOfficer and Cerebro:IsOfficer() or false
end

function UI:SafeCall(fn, ...)
	local success, result = pcall(fn, ...)
	if not success then
		Cerebro:Debug("UI SafeCall error: " .. tostring(result))
	end
	return success, result
end

function UI:RequestRefresh()
	if self.refreshTimer then
		Cerebro:CancelTimer(self.refreshTimer)
	end
	self.refreshTimer = Cerebro:ScheduleTimer(function()
		self:RefreshCurrentTab()
		self.refreshTimer = nil
	end, self.refreshDelay)
end

function UI:RefreshCurrentTab()
	if self.mainFrame and self.mainFrame:IsShown() and self.currentTab and self.tabGroup then
		self:SelectTab(self.tabGroup, self.currentTab)
	end
end

function UI:ShowUnavailableMessage(container, featureName)
	local label = AceGUI:Create("Label")
	label:SetText(featureName .. " is currently unavailable.")
	label:SetFullWidth(true)
	container:AddChild(label)
end

--[[=============================================
	Custom Widgets
===============================================]]

-- SimpleHTML Widget for clickable history
--[[=============================================
	Main Window
===============================================]]

function UI:Toggle()
	if self.mainFrame and self.mainFrame:IsShown() then
		self:Hide()
	else
		self:Show()
	end
end

function UI:Update()
	if self.mainFrame and self.mainFrame:IsShown() then
		-- Refresh current tab content instead of full rebuild if possible
		if self.currentTab then
			self:SelectTab(self.tabGroup, self.currentTab)
		else
			self.tabGroup:SelectTab("myprogress")
		end
	end
end

function UI:Show()
	-- Scope verification (Guild Locking)
	local scope = Cerebro:GetAuthorizedGuild()
	local scopeStatus = Cerebro:VerifyGuildScope()
	
	-- Initialize retry counter if needed
	self.scopeRetryCount = self.scopeRetryCount or 0
	local MAX_SCOPE_RETRIES = 3
	
	-- Handle pending state (API not ready)
	if scopeStatus == nil then
		if self.scopeRetryCount < MAX_SCOPE_RETRIES then
			self.scopeRetryCount = self.scopeRetryCount + 1
			Cerebro:Debug("Scope pending, retry " .. self.scopeRetryCount .. "/" .. MAX_SCOPE_RETRIES)
			Cerebro:Print("Loading guild info, please wait...")
			Cerebro:ScheduleTimer(function() self:Show() end, 2)
		else
			Cerebro:Print("Unable to verify guild. Please /reload and try again.")
			self.scopeRetryCount = 0
		end
		return
	end
	
	self.scopeRetryCount = 0  -- Reset on success
	
	-- Handle mismatch state (out of guild)
	if scopeStatus == false then
		self:ShowRestrictedAccess()
		return
	end
	
	-- Bootstrapping: No scope set yet, officer needs to confirm
	if not scope and Cerebro:IsOfficerSafe() then
		self:ShowScopeConfirmation()
		return
	end
	
	-- Normal flow: Show main frame
	-- Check if new UI is enabled
	local useNewUI = Cerebro.db.global.config and 
	                 Cerebro.db.global.config.ui and 
	                 Cerebro.db.global.config.ui.useNewUI
	
	if useNewUI then
		-- Delegate to the new native frame UI (UISandbox style)
		local UISandbox = Cerebro:GetModule("UISandbox", true)
		if UISandbox then
			UISandbox:Show()
			return
		else
			Cerebro:Print("New UI module not available. Using classic UI.")
		end
	end
	
	-- Legacy AceGUI flow
	if not self.mainFrame then
		self:CreateMainFrame()
	else
		-- Rebuild tabs to respect current officer status
		self:RebuildTabs()
	end
	
	-- Update status text with current season info
	self:UpdateStatusText()

	-- Run repair on show to fix missing classes/data
	Cerebro:RepairDatabase()

	self.mainFrame:Show()
	self:RefreshStandings()
	
	-- Start sync status refresh timer (30s = V2 heartbeat cadence)
	self:StartSyncStatusTimer()
	
	-- Trigger onboarding wizard for new players (runs after guild lock is resolved)
	local Onboarding = Cerebro:GetModule("Onboarding", true)
	if Onboarding then
		Onboarding:Show()  -- Show will check onboardingCompleted flag internally
	end
end

-- Show scope confirmation dialog for bootstrapping (officer sets initial scope)
function UI:ShowScopeConfirmation()
	local guildName = GetGuildInfo("player")
	if not guildName then
		Cerebro:Print("Guild info not ready. Please try again in a moment.")
		return
	end
	
	-- Show the confirmation popup with guild name
	StaticPopup_Show("CEREBRO_CONFIRM_SCOPE_LOCK", guildName)
end

-- Show restricted access screen for out-of-guild characters
function UI:ShowRestrictedAccess()
	-- Hide main frame if it exists
	if self.mainFrame then
		self.mainFrame:Hide()
	end
	
	-- Hide previous restricted frame if exists
	if self.restrictedFrame then
		self.restrictedFrame:Release()
		self.restrictedFrame = nil
	end
	
	local scope = Cerebro:GetAuthorizedGuild()
	
	local frame = AceGUI:Create("Frame")
	frame:SetTitle("Cerebro - Restricted Access")
	frame:SetWidth(450)
	frame:SetHeight(250)
	frame:SetLayout("Flow")
	frame:SetCallback("OnClose", function(widget)
		widget:Release()
		self.restrictedFrame = nil
	end)
	
	-- Enable Escape key to close the window
	_G["CerebroRestrictedFrame"] = frame.frame
	tinsert(UISpecialFrames, "CerebroRestrictedFrame")
	
	-- Spacer
	local spacer1 = AceGUI:Create("Label")
	spacer1:SetText(" ")
	spacer1:SetFullWidth(true)
	frame:AddChild(spacer1)
	
	-- Warning icon/header
	local header = AceGUI:Create("Label")
	header:SetText("|cffff4444⚠ Access Restricted|r")
	header:SetFullWidth(true)
	header:SetFont(GameFontNormalLarge:GetFont())
	frame:AddChild(header)
	
	-- Spacer
	local spacer2 = AceGUI:Create("Label")
	spacer2:SetText(" ")
	spacer2:SetFullWidth(true)
	frame:AddChild(spacer2)
	
	-- Message
	local label = AceGUI:Create("Label")
	local guildDisplay = scope and scope.guildName or "Unknown"
	local realmDisplay = scope and scope.realm or "Unknown"
	label:SetText(string.format(
		"This character is not in the authorized guild.\n\n" ..
		"Locked to: |cff00ff00%s|r - |cff888888%s|r\n\n" ..
		"Log in with a character in that guild to access Cerebro.",
		guildDisplay, realmDisplay
	))
	label:SetFullWidth(true)
	frame:AddChild(label)
	
	-- Spacer
	local spacer3 = AceGUI:Create("Label")
	spacer3:SetText(" ")
	spacer3:SetFullWidth(true)
	frame:AddChild(spacer3)
	
	-- Override button (GM or Officer with permission)
	local canOverride = Cerebro:IsGuildMasterSafe() or 
		(Cerebro:IsOfficerSafe() and Cerebro.db.global.config.admin.allowOfficerScopeReset)
	
	if canOverride then
		local btn = AceGUI:Create("Button")
		btn:SetText("Switch Guild (Wipes All Data)")
		btn:SetWidth(250)
		btn:SetCallback("OnClick", function()
			StaticPopup_Show("CEREBRO_CONFIRM_SCOPE_OVERRIDE")
		end)
		frame:AddChild(btn)
	end
	
	frame:Show()
	self.restrictedFrame = frame
end


-- Refresh the footer status text with current version and season info
function UI:UpdateStatusText()
	if not self.mainFrame then return end
	
	local statusParts = {"v" .. Cerebro.version}
	
	-- Add game version with Retail/PTR/Beta label
	local ver = Cerebro:GetGameVersion()
	if ver and ver.version then
		local buildType = ver.isRelease and "Retail" or "PTR/Beta"
		table.insert(statusParts, buildType .. " " .. ver.version)
	end
	
	-- Add season info (spelled out)
	local info = Cerebro:GetSeasonInfo()
	if info then
		if info.isPrepatch then
			table.insert(statusParts, "Pre-Patch")
		elseif info.seasonID and info.seasonID > 0 then
			table.insert(statusParts, "Season " .. info.seasonID)
			if info.week then
				table.insert(statusParts, "Week " .. info.week)
			elseif info.beforeStart then
				table.insert(statusParts, "Starting Soon")
			end
		end
	end
	
	-- Sync status indicator prefix
	local syncPrefix = self:GetSyncStatusText()
	if syncPrefix then
		table.insert(statusParts, syncPrefix)
	end
	
	-- Add roster hash for visual sync comparison across clients
	local commV2 = Cerebro:GetModule("CommunicationV2", true)
	if commV2 and commV2.ComputeRosterHash then
		local rosterHash = commV2:ComputeRosterHash()
		if rosterHash then
			table.insert(statusParts, rosterHash)
		end
	end
	
	self.mainFrame:SetStatusText(table.concat(statusParts, " | "))
end

-- Get colored sync status text for footer
function UI:GetSyncStatusText()
	local commV2 = Cerebro:GetModule("CommunicationV2", true)
	if not commV2 then return nil end
	
	local state, details = commV2:GetSyncStatus()
	
	if state == "synced" then
		return "|cff00ff00Synced|r"
	elseif state == "syncing" then
		return "|cffffff00Syncing...|r"
	elseif state == "diverged" then
		local count = details and details.divergedCount or 0
		return string.format("|cffff4444Diverged (%d)|r", count)
	elseif state == "no_peers" then
		return "|cff888888No Peers|r"
	end
	
	return nil
end

-- Start repeating timer to refresh sync status in footer (30s = V2 heartbeat)
function UI:StartSyncStatusTimer()
	self:StopSyncStatusTimer()
	self.syncStatusTimer = Cerebro:ScheduleRepeatingTimer(function()
		self:UpdateStatusText()
	end, 30)
end

-- Cancel sync status refresh timer
function UI:StopSyncStatusTimer()
	if self.syncStatusTimer then
		Cerebro:CancelTimer(self.syncStatusTimer)
		self.syncStatusTimer = nil
	end
end

function UI:Hide()
	-- Cancel sync status timer
	self:StopSyncStatusTimer()
	
	-- Hide legacy UI
	if self.mainFrame then
		self.mainFrame:Hide()
	end
	if self.restrictedFrame then
		self.restrictedFrame:Release()
		self.restrictedFrame = nil
	end
	
	-- Also hide new UI if active
	local UISandbox = Cerebro:GetModule("UISandbox", true)
	if UISandbox then
		UISandbox:Hide()
	end
end

function UI:CreateMainFrame()
	-- Create main frame using AceGUI
	local frame = AceGUI:Create("Frame")
	frame:SetTitle("Cerebro - EPGP System")
	
	-- Enable Escape key to close the window
	-- Give the underlying frame a global name and register with UISpecialFrames
	_G["CerebroMainFrame"] = frame.frame
	tinsert(UISpecialFrames, "CerebroMainFrame")
	
	-- Build status text with version, game version, and season info
	local statusParts = {"v" .. Cerebro.version}
	
	-- Add game version with Retail/PTR/Beta label
	local ver = Cerebro:GetGameVersion()
	if ver and ver.version then
		local buildType = ver.isRelease and "Retail" or "PTR/Beta"
		table.insert(statusParts, buildType .. " " .. ver.version)
	end
	
	-- Add season info (spelled out)
	local info = Cerebro:GetSeasonInfo()
	if info then
		if info.isPrepatch then
			table.insert(statusParts, "Pre-Patch")
		elseif info.seasonID and info.seasonID > 0 then
			table.insert(statusParts, "Season " .. info.seasonID)
			if info.week then
				table.insert(statusParts, "Week " .. info.week)
			elseif info.beforeStart then
				table.insert(statusParts, "Starting Soon")
			end
		end
	end
	
	frame:SetStatusText(table.concat(statusParts, " | "))
	frame:SetLayout("Fill")
	frame:SetWidth(890)
	frame:SetHeight(550)

	-- Set callbacks
	frame:SetCallback("OnClose", function(widget)
		self:Hide()
	end)
	
	-- Refresh current tab when frame regains focus (fixes desync with Blizzard Options)
	frame.frame:SetScript("OnShow", function()
		if self.currentTab == "weights" or self.currentTab == "settings" then
			Cerebro:ScheduleTimer(function()
				if self.tabGroup and self.currentTab then
					self:SelectTab(self.tabGroup, self.currentTab)
				end
			end, 0.1)
		end
	end)

	-- Create tab group
	local tabGroup = AceGUI:Create("TabGroup")
	tabGroup:SetLayout("Flow")
	
	-- Build tab list with officer-only filtering
	local tabs = self:BuildTabList()
	
	tabGroup:SetTabs(tabs)

	tabGroup:SetCallback("OnGroupSelected", function(container, event, group)
		self:SelectTab(container, group)
	end)

	-- Default to My Progress
	tabGroup:SelectTab("myprogress")

	frame:AddChild(tabGroup)

	self.mainFrame = frame
	self.tabGroup = tabGroup
end

function UI:BuildTabList()
	local tabs = {
		{text="My Progress", value="myprogress"},
		{text="Standings", value="standings"},
		{text="Wishlist", value="wishlist"},
		{text="History", value="history"},
		{text="Settings", value="settings"},  -- Visible to all; non-officers see read-only
	}
	
	-- Add officer-only tabs
	if self:IsOfficerSafe() then
		table.insert(tabs, {text="Guild M+ History", value="raider_audit"})
		table.insert(tabs, {text="System Logs", value="logs"})
		table.insert(tabs, {text="Commands", value="commands"})
	end
	
	return tabs
end

function UI:RebuildTabs()
	if not self.tabGroup then
		return
	end
	
	local tabs = self:BuildTabList()
	self.tabGroup:SetTabs(tabs)
end

function UI:SelectTab(container, tab)
	-- Prevent StandingsFrame from hijacking recycled widgets
	Cerebro:GetModule("StandingsFrame"):Clear()

	container:ReleaseChildren()
	self.currentTab = tab

	if tab == "myprogress" then
		-- Check if module exists (safeguard)
		if Cerebro.modules.MyProgress then
			Cerebro.modules.MyProgress:Draw(container)
		else
			local lbl = AceGUI:Create("Label")
			lbl:SetText("My Progress module not loaded.")
			container:AddChild(lbl)
		end
	elseif tab == "standings" then
		Cerebro:GetModule("StandingsFrame"):Draw(container)
	elseif tab == "wishlist" then
		self:DrawWishlistTab(container)
	elseif tab == "history" then
		self:DrawHistoryTab(container)
	elseif tab == "settings" then
		self:DrawSettingsTab(container)

    elseif tab == "raider_audit" then
        if Cerebro:IsOfficer() then
            self:DrawRaiderAudit(container)
        else
            local label = AceGUI:Create("Label")
            label:SetText("Officer tools are only available to raid officers and leaders.")
            label:SetFullWidth(true)
            container:AddChild(label)
        end
	elseif tab == "logs" then
		if Cerebro:IsOfficer() then
			self:DrawAuditTab(container)
		else
			local label = AceGUI:Create("Label")
			label:SetText("Officer tools are only available to raid officers and leaders.")
			label:SetFullWidth(true)
			container:AddChild(label)
		end
	elseif tab == "commands" then
		if self:IsOfficerSafe() then
			self:DrawCommandsTab(container)
		else
			local label = AceGUI:Create("Label")
			label:SetText("Commands are only available to raid officers and leaders.")
			label:SetFullWidth(true)
			container:AddChild(label)
		end
	end
end

--[[=============================================
	Officer Tab
===============================================]]

function UI:DrawSchemaCategory(container, categoryName, readOnly)
	local schema = _G.UI_ConfigSchema
	if not schema then return end
	local disabled = readOnly or false
	
	-- Get items for this category (using internal helper or loop)
	local items = {}
	for _, item in ipairs(schema:GetItemsSorted()) do
		if item.category == categoryName then
			table.insert(items, item)
		end
	end
	
	if #items == 0 then return end
	
	local catGroup = AceGUI:Create("InlineGroup")
	catGroup:SetTitle(categoryName)
	catGroup:SetFullWidth(true)
	catGroup:SetLayout("Flow")
	container:AddChild(catGroup)
	
	local lastGroup = nil
	
	-- Track widgets with dimmedWhen dependencies for in-place toggling
	-- Key: config key of the toggle, Value: list of {widget, readOnly}
	local dimmedDependents = {}
	
	for _, item in ipairs(items) do
		-- Show all items to everyone (grayed out for non-officers)
		
		-- dimmedWhen: disable this widget when the referenced config toggle is unchecked
		local itemDisabled = disabled
		if not itemDisabled and item.dimmedWhen then
			local toggleVal = schema:GetValue(item.dimmedWhen)
			if not toggleVal then
				itemDisabled = true
			end
		end
		
		-- Sub-group headers
		if item.group and item.group ~= lastGroup then
			local groupLbl = AceGUI:Create("Label")
			groupLbl:SetText("\n|cffFFD700" .. item.group .. "|r")
			groupLbl:SetFullWidth(true)
			catGroup:AddChild(groupLbl)
			lastGroup = item.group
		end
	
		if item.type == "number" then
			local edit = AceGUI:Create("EditBox")
			edit:SetLabel(item.label)
			edit:SetWidth(200)
			
			local val = schema:GetValue(item)
			if item.isPercent and item.percentStorage == "normalized" then
				val = (val or 0) * 100
			end
			
			edit:SetText(tostring(val))
			edit:SetDisabled(itemDisabled)
			edit:SetCallback("OnEnterPressed", function(widget, event, text)
				local num = tonumber(text)
				if num then
					if schema:SetValue(item, num) then
						-- Refresh display to show actual stored value (post-normalization/clamping)
						local stored = schema:GetValue(item)
						if item.isPercent and item.percentStorage == "normalized" then
							stored = (stored or 0) * 100
							end
						widget:SetText(tostring(stored))
						Cerebro:Print(string.format("Updated %s to %s", item.label, tostring(stored)))
					end
				else
					Cerebro:Print("Invalid number")
				end
			end)
			if item.desc then
				edit:SetCallback("OnEnter", function(widget)
					GameTooltip:SetOwner(widget.frame, "ANCHOR_TOPRIGHT")
					GameTooltip:SetText(item.label, 1, 0.82, 0)
					GameTooltip:AddLine(item.desc .. (itemDisabled and "" or " (Press Enter to save)"), 1, 1, 1, true)
					GameTooltip:Show()
				end)
				edit:SetCallback("OnLeave", function() GameTooltip:Hide() end)
			end
			catGroup:AddChild(edit)
			-- Register for dimmedWhen in-place updates
			if item.dimmedWhen then
				dimmedDependents[item.dimmedWhen] = dimmedDependents[item.dimmedWhen] or {}
				table.insert(dimmedDependents[item.dimmedWhen], {widget = edit, readOnly = disabled})
			end

			
		elseif item.type == "bool" then
			local cb = AceGUI:Create("CheckBox")
			cb:SetLabel(item.label)
			cb:SetValue(schema:GetValue(item))
			cb:SetWidth(200)
			cb:SetDisabled(itemDisabled)
			cb:SetCallback("OnValueChanged", function(widget, event, value)
				if schema:SetValue(item, value) then
					Cerebro:Print(string.format("Updated %s to %s", item.label, value and "ON" or "OFF"))
					-- In-place update: toggle dimmed state of dependent widgets
					local deps = dimmedDependents[item.key]
					if deps then
						for _, dep in ipairs(deps) do
							dep.widget:SetDisabled(dep.readOnly or not value)
						end
					end
				end
			end)
			if item.desc then
				cb:SetCallback("OnEnter", function(widget)
					GameTooltip:SetOwner(widget.frame, "ANCHOR_TOPRIGHT")
					GameTooltip:SetText(item.label, 1, 0.82, 0)
					GameTooltip:AddLine(item.desc, 1, 1, 1, true)
					GameTooltip:Show()
				end)
				cb:SetCallback("OnLeave", function(widget) GameTooltip:Hide() end)
			end
			catGroup:AddChild(cb)
			
		elseif item.type == "string" then
			local edit = AceGUI:Create("EditBox")
			edit:SetLabel(item.label)
			edit:SetWidth(200)
			edit:SetText(tostring(schema:GetValue(item) or ""))
			edit:SetDisabled(itemDisabled)
			edit:SetCallback("OnEnterPressed", function(widget, event, text)
				if schema:SetValue(item, text) then
					Cerebro:Print(string.format("Updated %s", item.label))
				end
			end)
			if item.desc then
				edit:SetCallback("OnEnter", function(widget)
					GameTooltip:SetOwner(widget.frame, "ANCHOR_TOPRIGHT")
					GameTooltip:SetText(item.label, 1, 0.82, 0)
					GameTooltip:AddLine(item.desc, 1, 1, 1, true)
					GameTooltip:Show()
				end)
				edit:SetCallback("OnLeave", function(widget) GameTooltip:Hide() end)
			end
			catGroup:AddChild(edit)
			
		elseif item.type == "select" then
			local dd = AceGUI:Create("Dropdown")
			dd:SetLabel(item.label)
			dd:SetWidth(200)
			dd:SetList(item.choices or {})
			dd:SetValue(schema:GetValue(item))
			dd:SetDisabled(itemDisabled)
			dd:SetCallback("OnValueChanged", function(widget, event, value)
				if schema:SetValue(item, value) then
					Cerebro:Print(string.format("Updated %s", item.label))
				end
			end)
			if item.desc then
				dd:SetCallback("OnEnter", function(widget)
					GameTooltip:SetOwner(widget.frame, "ANCHOR_TOPRIGHT")
					GameTooltip:SetText(item.label, 1, 0.82, 0)
					GameTooltip:AddLine(item.desc, 1, 1, 1, true)
					GameTooltip:Show()
				end)
				dd:SetCallback("OnLeave", function(widget) GameTooltip:Hide() end)
			end
			catGroup:AddChild(dd)
		end
	end

	return catGroup
end

function UI:DrawSettingsTab(container)
	local isOfficer = Cerebro:IsOfficerSafe()
	-- Wrapper group to provide proper sizing for ScrollFrame
	local wrapper = AceGUI:Create("SimpleGroup")
	wrapper:SetFullWidth(true)
	wrapper:SetFullHeight(true)
	wrapper:SetLayout("Fill")
	container:AddChild(wrapper)
	
	local scroll = AceGUI:Create("ScrollFrame")
	scroll:SetFullWidth(true)
	scroll:SetFullHeight(true)
	scroll:SetLayout("Flow")
	wrapper:AddChild(scroll)

	-- 1. Session Management (Officer Only)
	if isOfficer then
	local ok1, err1 = pcall(function()
	local sessionGroup = AceGUI:Create("InlineGroup")
	sessionGroup:SetTitle("Session Management")
	sessionGroup:SetFullWidth(true)
	sessionGroup:SetLayout("Flow")
	scroll:AddChild(sessionGroup)

	local session = Cerebro.modules.Session and Cerebro.modules.Session:GetCurrentSession()

	if session then
		local label = AceGUI:Create("Label")
		label:SetText(string.format("|cff00ff00Active Session:|r %s", session.raidName or "Raid"))
		label:SetFullWidth(true)
		sessionGroup:AddChild(label)

		local endBtn = AceGUI:Create("Button")
		endBtn:SetText("End Session")
		endBtn:SetWidth(150)
		endBtn:SetCallback("OnClick", function()
			Cerebro.modules.Session:EndSession()
			self:SelectTab(self.tabGroup, "settings")
		end)
		sessionGroup:AddChild(endBtn)

		-- Test Mode Toggle
		local testCheck = AceGUI:Create("CheckBox")
		testCheck:SetLabel("Enable RCLC Test Awards")
		testCheck:SetDescription("If checked, RCLC 'Test Mode' awards will charge GP and be logged.")
		testCheck:SetValue(Cerebro.db.global.config.epgp.allowTestAwards)
		testCheck:SetCallback("OnValueChanged", function(widget, event, value)
			Cerebro.db.global.config.epgp.allowTestAwards = value
			Cerebro:Print("Test Awards are now " .. (value and "ENABLED" or "DISABLED"))
		end)
		testCheck:SetWidth(200)
		sessionGroup:AddChild(testCheck)

	else
		local startBtn = AceGUI:Create("Button")
		startBtn:SetText("Start Session")
		startBtn:SetWidth(150)
		startBtn:SetCallback("OnClick", function()
			Cerebro.modules.Session:StartSession()
			self:SelectTab(self.tabGroup, "settings")
		end)
		sessionGroup:AddChild(startBtn)
	end

	-- Prep Check
	local prepBtn = AceGUI:Create("Button")
	prepBtn:SetText("Run Prep Check")
	prepBtn:SetWidth(150)
	prepBtn:SetCallback("OnClick", function()
		Cerebro.modules.Preparation:CheckRaid()
	end)
	sessionGroup:AddChild(prepBtn)
	
	-- Decay Button
	local decayBtn = AceGUI:Create("Button")
	decayBtn:SetText("Run Weekly Decay")
	decayBtn:SetWidth(150)
	decayBtn:SetCallback("OnClick", function()
		StaticPopup_Show("CEREBRO_CONFIRM_DECAY")
	end)
	sessionGroup:AddChild(decayBtn)

	-- Calendar Sign-Up EP
	local signupModule = Cerebro:GetModule("CalendarSignup", true)
	if signupModule then
		local signupBtn = AceGUI:Create("Button")
		signupBtn:SetText("Award Sign-Up EP")
		signupBtn:SetWidth(150)
		signupBtn:SetCallback("OnClick", function()
			signupModule:ShowEventPicker(self, container)
		end)
		sessionGroup:AddChild(signupBtn)
	end
	end) -- end pcall session management
	if not ok1 then Cerebro:Debug("Settings: Session Management error: " .. tostring(err1)) end
	end -- end officer-only session management
    
    -- 2. Admit Candidates (Officer Only)
    if isOfficer then
    local ok2, err2 = pcall(function()
    local candidateGroup = AceGUI:Create("InlineGroup")
    candidateGroup:SetTitle("Admit Candidates")
    candidateGroup:SetFullWidth(true)
    candidateGroup:SetLayout("Flow")
    scroll:AddChild(candidateGroup)
    
    -- Helper: Build the candidate widgets (dropdown + admit button or "no pending" label)
    local function BuildCandidateWidgets()
        -- Clear existing children from candidateGroup (keep the group itself)
        candidateGroup:ReleaseChildren()

        -- Re-add scan button
        local scanBtn = AceGUI:Create("Button")
        scanBtn:SetText("Scan Guild for Candidates")
        scanBtn:SetWidth(200)
        scanBtn:SetCallback("OnClick", function()
            if Cerebro.modules.CommunicationV2 then
                Cerebro.modules.CommunicationV2:BroadcastDiscovery()
                Cerebro:Print("Scanning guild for pending candidates...")
                Cerebro:DiscoverGuildGroupMembers()
                -- Delayed rebuild: give responses 3 seconds to arrive
                C_Timer.After(3, function()
                    pcall(BuildCandidateWidgets)
                    Cerebro:Print("Candidate scan complete.")
                end)
            end
        end)
        candidateGroup:AddChild(scanBtn)

        -- Build pending list
        local pendingList = {}
        local visibleRanks = Cerebro.db.global.config and Cerebro.db.global.config.sync
            and Cerebro.db.global.config.sync.visibleRanks
        for name, data in pairs(Cerebro.db.global.players) do
            if data.status == "Pending" then
                local rankVisible = true
                if visibleRanks then
                    local rankIndex = Cerebro:GetPlayerGuildRankIndex(name)
                    if rankIndex == nil or not visibleRanks[rankIndex] then
                        rankVisible = false
                    end
                end
                if rankVisible then
                    -- Delegate class lookup to GetPlayerData (handles sync metadata bump)
                    if not data.class then
                        Cerebro:GetPlayerData(name)
                    end
                    pendingList[name] = string.format("%s (Seen: %s)", name, date("%m/%d", data.lastSeen or 0))
                end
            end
        end

        if next(pendingList) then
            local candidateSelect = AceGUI:Create("Dropdown")
            candidateSelect:SetLabel("Select Candidate")
            candidateSelect:SetWidth(250)
            candidateSelect:SetList(pendingList)
            candidateGroup:AddChild(candidateSelect)
            
            local admitBtn = AceGUI:Create("Button")
            admitBtn:SetText("Admit to Core")
            admitBtn:SetWidth(150)
            admitBtn:SetCallback("OnClick", function()
                local target = candidateSelect:GetValue()
                if target then
                    local data = Cerebro:GetPlayerData(target)
                    data.status = "Core"
                    data.coreAdmittedAt = time()
                    data.lastUpdate = time()
                    
                    -- Class is already populated by GetPlayerData above (line 868)
                    -- which handles guild roster lookup + sync metadata bump
                    
                    Cerebro:Print(string.format("Admitted %s to Core.", Cerebro:ColorPlayerName(target)))
                    Cerebro.Ledger:AddAuditLog("ADMIT_PLAYER", "Promoted " .. target .. " to Core status.")
                    
                    -- V2 no-op: BroadcastPlayerUpdate intentionally discards args.
                    -- Status change will propagate via heartbeat hash-diff (~30s delay).
                    -- Keeping call for forward-compatibility if V2 adds targeted broadcasts.
                    if Cerebro.modules.CommunicationV2 and Cerebro.modules.CommunicationV2.BroadcastPlayerUpdate then
                        Cerebro.modules.CommunicationV2:BroadcastPlayerUpdate(target, {status = "Core", class = data.class})
                    end
                    
                    -- Rebuild this section and refresh standings
                    pcall(BuildCandidateWidgets)
                    if self.RefreshStandings then self:RefreshStandings() end
                end
            end)
            candidateGroup:AddChild(admitBtn)
        else
            local lbl = AceGUI:Create("Label")
            lbl:SetText("No pending candidates.")
            lbl:SetColor(0.5, 0.5, 0.5)
            candidateGroup:AddChild(lbl)
        end

        -- Force AceGUI to recalculate and render the rebuilt widgets
        candidateGroup:DoLayout()
    end

    -- Initial build
    BuildCandidateWidgets()
    end) -- end pcall candidates
    if not ok2 then Cerebro:Debug("Settings: Admit Candidates error: " .. tostring(err2)) end
    end -- end officer-only candidates

	-- 3. Manual EP/GP Adjustment (Officer Only)
	if isOfficer then
	local ok3, err3 = pcall(function()
	local adjGroup = AceGUI:Create("InlineGroup")
	adjGroup:SetTitle("Manual EP/GP Adjustment")
	adjGroup:SetFullWidth(true)
	adjGroup:SetLayout("Flow")
	scroll:AddChild(adjGroup)

	-- Player Select
	local playerSelect = AceGUI:Create("Dropdown")
	playerSelect:SetLabel("Player")
	playerSelect:SetWidth(200)
	local playerList = {}
	for name, _ in pairs(Cerebro.db.global.players) do
		playerList[name] = name
	end
	playerSelect:SetList(playerList)
	adjGroup:AddChild(playerSelect)

	-- Operation
	local opSelect = AceGUI:Create("Dropdown")
	opSelect:SetLabel("Operation")
	opSelect:SetList({
		["add_ep"] = "Add EP (+)",
		["sub_ep"] = "Subtract EP (-)",
		["add_gp"] = "Add GP (+)",
		["sub_gp"] = "Subtract GP (-)",
	})
	opSelect:SetValue("add_ep")
	opSelect:SetWidth(150)
	adjGroup:AddChild(opSelect)

	-- Amount
	local amountBox = AceGUI:Create("EditBox")
	amountBox:SetLabel("Amount")
	amountBox:SetWidth(100)
	adjGroup:AddChild(amountBox)

	-- Reason
	local reasonBox = AceGUI:Create("EditBox")
	reasonBox:SetLabel("Reason")
	reasonBox:SetWidth(200)
	reasonBox:SetText("Manual Adjust")
	adjGroup:AddChild(reasonBox)

	-- Apply
	local applyBtn = AceGUI:Create("Button")
	applyBtn:SetText("Apply")
	applyBtn:SetWidth(100)
	applyBtn:SetCallback("OnClick", function()
		local player = playerSelect:GetValue()
		local op = opSelect:GetValue()
		local amt = tonumber(amountBox:GetText())
		local reason = reasonBox:GetText()

		if player and amt and amt > 0 then
			local epgp = Cerebro:GetModule("EPGP")
			if op == "add_ep" then epgp:AwardEP(player, amt, "OFFICER", reason) end
			if op == "sub_ep" then epgp:AwardEP(player, -amt, "OFFICER", reason) end
			if op == "add_gp" then epgp:ChargeGP(player, amt, "OFFICER", reason) end
			if op == "sub_gp" then epgp:ChargeGP(player, -amt, "OFFICER", reason) end
			
			Cerebro.Ledger:AddAuditLog("MANUAL_ADJUST", string.format("%s %s %d to %s (%s)", op, "amount", amt, player, reason))
			Cerebro:Print("Adjustment applied.")
			self:RefreshStandings()
		else
			Cerebro:Print("Invalid input.")
		end
	end)
	adjGroup:AddChild(applyBtn)

	-- 3.5 Bulk EP Awards (CustomEP templates — awards EP to Standings-selected players)
	local customEP = Cerebro:GetModule("CustomEP", true)
	if customEP then customEP:DrawInto(scroll) end
	end) -- end pcall manual adjust + customEP
	if not ok3 then Cerebro:Debug("Settings: Manual Adjust error: " .. tostring(err3)) end
	end -- end officer-only manual adjust + customEP

	-- 4-8: Officer-only config sections
	if isOfficer then
	local ok4, err4 = pcall(function()
	-- 4. EP Awards
	self:DrawSchemaCategory(scroll, "EP Awards")

	-- 5. GP Modifiers
	self:DrawSchemaCategory(scroll, "GP Modifiers")

	-- 6. Mythic+ Configuration
	self:DrawSchemaCategory(scroll, "Mythic+")
	
	-- 7. Preparation Configuration
	self:DrawSchemaCategory(scroll, "Preparation")

	-- 7.5 Sign-Up EP Configuration (Qualifying RSVP Statuses)
	local signupGroup = AceGUI:Create("InlineGroup")
	signupGroup:SetTitle("Sign-Up EP Configuration")
	signupGroup:SetFullWidth(true)
	signupGroup:SetLayout("Flow")
	scroll:AddChild(signupGroup)

	local statusDesc = AceGUI:Create("Label")
	statusDesc:SetText("|cff888888Calendar RSVP responses that earn Sign-Up EP:|r")
	statusDesc:SetFullWidth(true)
	signupGroup:AddChild(statusDesc)

	-- Force signupStatuses into raw SavedVariables data
	-- AceDB's metatable proxy doesn't persist writes to default subtables
	local epgpConfig = Cerebro.db.global.config.epgp
	if not rawget(epgpConfig, "signupStatuses") then
		local defaults = { [1] = true, [2] = false, [3] = true, [4] = false, [5] = false, [8] = false }
		local existing = epgpConfig.signupStatuses
		if existing then
			for k, v in pairs(existing) do
				defaults[k] = v
			end
		end
		epgpConfig.signupStatuses = defaults
	end
	local signupStatuses = epgpConfig.signupStatuses

	local statusOptions = {
		{ id = 1, label = "Accepted" },
		{ id = 2, label = "Declined" },
		{ id = 3, label = "Confirmed" },
		{ id = 4, label = "Out" },
		{ id = 5, label = "Standby" },
		{ id = 8, label = "Tentative" },
	}

	for _, opt in ipairs(statusOptions) do
		local cb = AceGUI:Create("CheckBox")
		cb:SetLabel(opt.label)
		cb:SetValue(signupStatuses[opt.id] == true)
		cb:SetWidth(120)
		local optId = opt.id
		local optLabel = opt.label
		cb:SetCallback("OnValueChanged", function(_, _, value)
			signupStatuses[optId] = value or false
			Cerebro.db.global.config.epgp.signupStatuses = signupStatuses
			-- Stamp LWW timestamp so this change wins during config sync
			Cerebro.db.global.config.configLastUpdate = Cerebro.db.global.config.configLastUpdate or {}
			Cerebro.db.global.config.configLastUpdate.epgp = GetServerTime and GetServerTime() or time()
			Cerebro:Print(string.format("Sign-Up status '%s' is now %s.",
				optLabel, value and "ENABLED" or "DISABLED"))
			Cerebro.Ledger:AddAuditLog("SIGNUP_CONFIG",
				string.format("%s status '%s'", value and "Enabled" or "Disabled", optLabel))
		end)
		signupGroup:AddChild(cb)
	end

	-- 8. GP Base Values (Slot Weights)
	local gpGroup = AceGUI:Create("InlineGroup")
	gpGroup:SetTitle("GP Base Values (Slot Weights)")
	gpGroup:SetFullWidth(true)
	gpGroup:SetLayout("Flow")
	scroll:AddChild(gpGroup)
	
	local instruction = AceGUI:Create("Label")
	instruction:SetText("Set the default GP cost for each item slot. Press Enter to save.")
	instruction:SetFullWidth(true)
	gpGroup:AddChild(instruction)

	local note = AceGUI:Create("Label")
	note:SetText("|cff888888Note: These defaults are calibrated assuming ~2,000 EP earned weekly on average throughout a tier.|r")
	note:SetFullWidth(true)
	gpGroup:AddChild(note)
	
	local slots = Cerebro.SlotList
	local configWeights = Cerebro.db.global.config.epgp.slotWeights or {}
	
	for _, slot in ipairs(slots) do
		local edit = AceGUI:Create("EditBox")
		edit:SetLabel(slot.label)
		edit:SetWidth(80)
		edit:SetText(tostring(configWeights[slot.key] or 0))
		edit:SetCallback("OnEnterPressed", function(widget, event, text)
			local val = tonumber(text)
			if val and val >= 0 then
				configWeights[slot.key] = val
				Cerebro.Ledger:AddAuditLog("GP_CONFIG", string.format("Updated %s to %d", slot.label, val))
				Cerebro:Print(string.format("Updated %s GP to %d", slot.label, val))
				
				-- Auto-bump config version for slot changes too
				local currentVer = Cerebro.db.global.config.epgp.configVersion or 0
				Cerebro.db.global.config.epgp.configVersion = currentVer + 1
			else
				Cerebro:Print("Invalid value for " .. slot.label)
				edit:SetText(tostring(configWeights[slot.key] or 0))
			end
		end)
		gpGroup:AddChild(edit)
	end
	end) -- end pcall config sections
	if not ok4 then Cerebro:Debug("Settings: Config sections error: " .. tostring(err4)) end
	end -- end officer-only config sections
	
	-- 9. General Configuration (visible to all)
	local ok5, err5 = pcall(function()
	self:DrawSchemaCategory(scroll, "General")
	end)
	if not ok5 then Cerebro:Debug("Settings: General section error: " .. tostring(err5)) end

	-- 10. Admin (officer-only)
	if isOfficer then
	local ok6, err6 = pcall(function()
	local adminGroup = self:DrawSchemaCategory(scroll, "Admin")
	
	-- 11. Standings Visibility (Guild Ranks) - Nested inside Admin section
	if adminGroup then
		local rankHeader = AceGUI:Create("Label")
		rankHeader:SetText("\n|cffFFD700Standings Visibility (Guild Ranks)|r")
		rankHeader:SetFullWidth(true)
		adminGroup:AddChild(rankHeader)
		
		local rankDesc = AceGUI:Create("Label")
		rankDesc:SetText("Select which guild ranks appear in Standings and are included in sync.")
		rankDesc:SetFullWidth(true)
		rankDesc:SetColor(0.7, 0.7, 0.7)
		adminGroup:AddChild(rankDesc)
		
		-- Get current visibleRanks config
		if not Cerebro.db.global.config.sync then Cerebro.db.global.config.sync = {} end
		if not Cerebro.db.global.config.sync.visibleRanks then
			Cerebro.db.global.config.sync.visibleRanks = {}
		end
		local currentVisibleRanks = Cerebro.db.global.config.sync.visibleRanks
		
		-- Auto-detect: if empty, default all guild ranks to visible
		if not next(currentVisibleRanks) then
			local autoNumRanks = GuildControlGetNumRanks and GuildControlGetNumRanks() or 0
			if autoNumRanks > 0 then
				for ri = 0, autoNumRanks - 1 do
					currentVisibleRanks[ri] = true
				end
				Cerebro:Debug("Auto-populated visibleRanks: " .. autoNumRanks .. " ranks set to visible")
			end
		end
		
		local numRanks = GuildControlGetNumRanks and GuildControlGetNumRanks() or 0
		if numRanks > 0 then
			for rankIndex = 0, numRanks - 1 do
				local rankName = GuildControlGetRankName and GuildControlGetRankName(rankIndex + 1) or ("Rank " .. rankIndex)
				local cb = AceGUI:Create("CheckBox")
				cb:SetLabel(string.format("%s (Rank %d)", rankName, rankIndex))
				cb:SetValue(currentVisibleRanks[rankIndex] == true)
				cb:SetWidth(200)
				cb:SetCallback("OnValueChanged", function(_, _, value)
					-- Persistence fix: set to false explicitly instead of nil (AceDB eats nil)
					currentVisibleRanks[rankIndex] = value or false
					-- FIX A: Stamp LWW timestamp so this change wins during config merge
					Cerebro.db.global.config.sync.lastUpdate = GetServerTime and GetServerTime() or time()
					Cerebro:Print(string.format("Rank '%s' (%d) is now %s in Standings.",
						rankName, rankIndex, value and "VISIBLE" or "HIDDEN"))
					Cerebro.Ledger:AddAuditLog("RANK_VISIBILITY",
						string.format("%s rank %d (%s)", value and "Enabled" or "Disabled", rankIndex, rankName))
				end)
				adminGroup:AddChild(cb)
			end
		else
			local noRanksLabel = AceGUI:Create("Label")
			noRanksLabel:SetText("|cffff4444Guild rank data not available. Try reopening this tab.|r")
			noRanksLabel:SetFullWidth(true)
			adminGroup:AddChild(noRanksLabel)
		end

		-- Separator
		local sep = AceGUI:Create("Label")
		sep:SetText("")
		sep:SetFullWidth(true)
		adminGroup:AddChild(sep)

		-- Hide Non-Guildies checkbox (hides players not currently in guild from Standings)
		local hideNonGuildies = Cerebro.db.global.config.sync.hideNonGuildies or false
		local ngCb = AceGUI:Create("CheckBox")
		ngCb:SetLabel("Hide Non-Guildies")
		ngCb:SetDescription("Hide players who are not currently in the guild roster from Standings.")
		ngCb:SetValue(hideNonGuildies)
		ngCb:SetFullWidth(true)
		ngCb:SetCallback("OnValueChanged", function(_, _, value)
			Cerebro.db.global.config.sync.hideNonGuildies = value or false
			-- FIX A: Stamp LWW timestamp so this change wins during config merge
			Cerebro.db.global.config.sync.lastUpdate = GetServerTime and GetServerTime() or time()
			Cerebro:Print(string.format("Non-Guildies are now %s in Standings.",
				value and "|cffff0000HIDDEN|r" or "|cff00ff00VISIBLE|r"))
			Cerebro:RefreshAllUI()
		end)
		adminGroup:AddChild(ngCb)
	end
	end) -- end pcall admin
	if not ok6 then Cerebro:Debug("Settings: Admin section error: " .. tostring(err6)) end
	end -- end officer-only admin

	-- Force layout recalculation
	Cerebro:ScheduleTimer(function()
		if scroll and scroll.frame then
			scroll:DoLayout()
		end
	end, 0.01)
end

--[[=============================================
	History Tab
===============================================]]

function UI:DrawHistoryTab(container)
	-- Search Bar
	local searchGroup = AceGUI:Create("SimpleGroup")
	searchGroup:SetFullWidth(true)
	searchGroup:SetLayout("Flow")
	container:AddChild(searchGroup)
	
	local searchBox = AceGUI:Create("EditBox")
	searchBox:SetLabel("Search (press Enter)")
	searchBox:SetWidth(300)
	searchBox:SetText(self.searchText["history"] or "")
	searchBox:SetCallback("OnEnterPressed", function(widget, event, text)
		self.searchText["history"] = text
		self:RequestRefresh()
	end)
	searchGroup:AddChild(searchBox)
	
	-- Content Area
	local contentGroup = AceGUI:Create("SimpleGroup")
	contentGroup:SetFullWidth(true)
	contentGroup:SetFullHeight(true)
	contentGroup:SetLayout("Fill")
	container:AddChild(contentGroup)

	local historyText = ""
	local list = Cerebro.db.global.transactions or {}
	local searchFilter = (self.searchText["history"] or ""):lower()
	
	if #list == 0 then
		local lbl = AceGUI:Create("Label")
		lbl:SetText("No history found.")
		contentGroup:AddChild(lbl)
		return
	end
	
	-- Show last 100 (filtered)
	local count = 0
	for i = #list, 1, -1 do
		local tx = list[i]
		
		-- Specifics (Item Link, Boss Name, etc)
		local specifics = ""
		if type(tx.details) == "string" then
			specifics = string.format("[%s]", tx.details)
		elseif type(tx.details) == "table" then
			if tx.details.item then
				-- Loot award with item link
				local slotPart = tx.details.slot and string.format("[%s] ", tx.details.slot) or ""
				local gpDelta = ""
				if tx.details.preGP and tx.details.postGP then
					gpDelta = string.format(" (GP: %d → %d)", tx.details.preGP, tx.details.postGP)
				end
				specifics = string.format("%s%s%s", slotPart, tx.details.item, gpDelta)
			elseif tx.details.preGP and tx.details.postGP then
				-- GP charge without item (manual)
				specifics = string.format("(GP: %d → %d)", tx.details.preGP, tx.details.postGP)
			elseif tx.details.reason then
				specifics = string.format("[%s]", tx.details.reason)
			elseif tx.details.mapID then
				specifics = string.format("[M+ Level %s]", tostring(tx.details.level))
			end
		end
		
		local admin = tx.admin or "System"
		
		-- Session Tag
		local sessionTag = ""
		if tx.sessionName and tx.sessionID then
			sessionTag = string.format(" [%s_%s]", tx.sessionName, tx.sessionID)
		elseif tx.sessionName then
			sessionTag = string.format(" [%s]", tx.sessionName)
		end
		
		-- Format: [DATE, TIME] [Session] Player: +/-Amount Type (Category) [Specifics] - By: Admin
		local amountSign = (tx.amount >= 0) and "+" or ""
		
		-- Legacy transaction suffix (missing logicVersion = ingested from old version)
		local legacySuffix = ""
		if not tx.logicVersion then
			legacySuffix = "|cffff8800*|r"  -- Orange asterisk for legacy
		end
		
		-- RAW MODE (Default and only mode)
		local line
		
		-- Special formatting for session events
		if tx.reason == "SESSION_START" or tx.reason == "SESSION_END" then
			local sessionInfo = tx.details and tx.details.sessionInfo or "Session Event"
			line = string.format("|cff999999[%s]|r |cffffaa00%s|r - %s - |cff00ccffBy: %s|r%s",
				date("%m/%d, %H:%M", tx.timestamp),
				tx.reason,
				sessionInfo,
				Cerebro:GetShortPlayerName(admin),
				legacySuffix
			)
		else
			-- Normal transaction format
			line = string.format("|cff999999[%s]|r%s |cffffffff%s|r: %s%d %s |cffaaaaaa(%s)|r %s - |cff00ccffBy: %s|r%s",
				date("%m/%d, %H:%M", tx.timestamp),
				sessionTag,
				Cerebro:GetShortPlayerName(tx.player),
				amountSign,
				tx.amount,
				tx.currency or tx.type or "?",
				tx.reason or "Unknown",
				specifics,
				Cerebro:GetShortPlayerName(admin),
				legacySuffix
			)
		end
		
		-- Apply search filter (case-insensitive)
		if searchFilter == "" or line:lower():find(searchFilter, 1, true) then
			historyText = historyText .. line .. "\n"
			count = count + 1
			if count >= 100 then break end
		end
	end
	
	if count == 0 then
		historyText = "No matching entries found."
	end
	
	local editBox = AceGUI:Create("MultiLineEditBox")
	editBox:SetLabel("")
	editBox:SetFullWidth(true)
	editBox:SetFullHeight(true)
	editBox:DisableButton(true) -- Hide the "Accept" button
	editBox:SetText(historyText)
	
	-- Make Read-Only (Revert on change)
	editBox:SetCallback("OnTextChanged", function(widget)
		if widget:GetText() ~= historyText then
			widget:SetText(historyText)
		end
	end)
	
	contentGroup:AddChild(editBox)
end

--[[=============================================
	Audit Tab
===============================================]]

function UI:DrawAuditTab(container)
	-- Search Bar
	local searchGroup = AceGUI:Create("SimpleGroup")
	searchGroup:SetFullWidth(true)
	searchGroup:SetLayout("Flow")
	container:AddChild(searchGroup)
	
	local searchBox = AceGUI:Create("EditBox")
	searchBox:SetLabel("Search (press Enter)")
	searchBox:SetWidth(300)
	searchBox:SetText(self.searchText["audit"] or "")
	searchBox:SetCallback("OnEnterPressed", function(widget, event, text)
		self.searchText["audit"] = text
		self:RequestRefresh()
	end)
	searchGroup:AddChild(searchBox)
	
	-- Content Area
	local contentGroup = AceGUI:Create("SimpleGroup")
	contentGroup:SetFullWidth(true)
	contentGroup:SetFullHeight(true)
	contentGroup:SetLayout("Fill")
	container:AddChild(contentGroup)
	
	local editBox = AceGUI:Create("MultiLineEditBox")
	editBox:SetLabel("")
	editBox:SetFullWidth(true)
	editBox:SetFullHeight(true)
	editBox:DisableButton(true)
	contentGroup:AddChild(editBox)

	local auditText = ""
	local list = Cerebro.db.global.audit or {}
	local searchFilter = (self.searchText["audit"] or ""):lower()
	
	if #list == 0 then
		editBox:SetText("No audit logs found.")
		return
	end
	
	-- Show last 100 (filtered)
	local count = 0
	local currentEpoch = Cerebro.db.global.resetEpoch or 0
	for i = #list, 1, -1 do
		local entry = list[i]
		
		-- Epoch marker: show [Epoch N] for entries from previous epochs
		local epochTag = ""
		if entry.epoch and entry.epoch ~= currentEpoch then
			epochTag = string.format("|cff888888[Epoch %d]|r ", entry.epoch)
		end
		
		-- Format: [DATE TIME] [Epoch N] Admin: ACTION - Details
		local line = string.format("[%s] %s|cff00ccff%s|r: |cffffaa00%s|r - %s",
			date("%m/%d %H:%M", entry.timestamp),
			epochTag,
			Cerebro:GetShortPlayerName(entry.admin),
			entry.action or "ACTION",
			entry.details or ""
		)
		
		-- Apply search filter (case-insensitive)
		if searchFilter == "" or line:lower():find(searchFilter, 1, true) then
			auditText = auditText .. line .. "\n"
			count = count + 1
			if count >= 100 then break end
		end
	end
	
	if count == 0 then
		auditText = "No matching entries found."
	end
	
	editBox:SetText(auditText)
	
	-- Make Read-Only (Revert on change)
	editBox:SetCallback("OnTextChanged", function(widget)
		if widget:GetText() ~= auditText then
			widget:SetText(auditText)
		end
	end)
end

--[[=============================================
	Guild M+ History Tab (formerly Raider Audit)
	Shows all guild M+ runs pushed from raiders to officers
	Uses same visual style as My Progress tab
==============================================]]

-- Track native frames for cleanup
UI.guildMplusNativeFrames = UI.guildMplusNativeFrames or {}

local function CleanupGuildMplusFrames()
	for _, frame in ipairs(UI.guildMplusNativeFrames or {}) do
		if frame and frame.Hide then
			frame:Hide()
			frame:SetParent(nil)
			frame:ClearAllPoints()
		end
	end
	UI.guildMplusNativeFrames = {}
end

local function TrackGuildMplusFrame(frame)
	if frame then
		table.insert(UI.guildMplusNativeFrames, frame)
	end
	return frame
end

local GUILD_MPLUS_ROW_HEIGHT = 36

-- Dungeon lookup table (same as My Progress)
local DUNGEON_NAMES = {
	[2660] = "Ara-Kara",
	[2662] = "The Dawnbreaker",
	[2651] = "The Stonevault",
	[2649] = "Priory",
	[2669] = "Floodgate",
	[2773] = "Eco-Dome",
	[2287] = "Halls of Atonement",
	[2441] = "Tazavesh: Streets",
	[375] = "Mists",
	[376] = "Necrotic Wake",
	[377] = "De Other Side",
	[378] = "Halls of Atonement",
	[379] = "Plaguefall",
}

local function GetDungeonDisplayName(run)
	if run.mapName and run.mapName ~= "" then
		return run.mapName
	end
	if run.mapID and DUNGEON_NAMES[run.mapID] then
		return DUNGEON_NAMES[run.mapID]
	end
	if run.mapID then
		local name = C_ChallengeMode.GetMapUIInfo(run.mapID)
		if name then return name end
	end
	return "Unknown"
end

-- Filter state
UI.guildMplusFilters = UI.guildMplusFilters or { player = "", dungeon = "", level = "", result = "" }

function UI:DrawRaiderAudit(container)
	CleanupGuildMplusFrames()
	
	local runs = Cerebro.db.global.guildMplusRuns or {}
	
	-- Build unique values for dropdowns
	local players = { [""] = "All Players" }
	local dungeons = { [""] = "All Dungeons" }
	local levels = { [""] = "All Levels" }
	local results = { [""] = "All Results", ["success"] = "Timed", ["fail"] = "Untimed" }
	
	for _, run in ipairs(runs) do
		local p = Cerebro:GetFullPlayerName(run.uploadedBy or "Unknown")
		players[p] = p
		local d = GetDungeonDisplayName(run)
		dungeons[d] = d
		local l = "+" .. (run.level or 0)
		levels[l] = l
	end
	
	-- Filter bar (compact)
	local filterGroup = AceGUI:Create("SimpleGroup")
	filterGroup:SetFullWidth(true)
	filterGroup:SetLayout("Flow")
	container:AddChild(filterGroup)
	
	-- Player dropdown
	local playerDD = AceGUI:Create("Dropdown")
	playerDD:SetLabel("")
	playerDD:SetWidth(150)
	playerDD:SetList(players)
	playerDD:SetValue(self.guildMplusFilters.player or "")
	playerDD:SetCallback("OnValueChanged", function(_, _, val)
		self.guildMplusFilters.player = val
		self:SelectTab(self.tabGroup, "raider_audit")
	end)
	filterGroup:AddChild(playerDD)
	
	-- Dungeon dropdown
	local dungeonDD = AceGUI:Create("Dropdown")
	dungeonDD:SetLabel("")
	dungeonDD:SetWidth(120)
	dungeonDD:SetList(dungeons)
	dungeonDD:SetValue(self.guildMplusFilters.dungeon or "")
	dungeonDD:SetCallback("OnValueChanged", function(_, _, val)
		self.guildMplusFilters.dungeon = val
		self:SelectTab(self.tabGroup, "raider_audit")
	end)
	filterGroup:AddChild(dungeonDD)
	
	-- Level dropdown
	local levelDD = AceGUI:Create("Dropdown")
	levelDD:SetLabel("")
	levelDD:SetWidth(70)
	levelDD:SetList(levels)
	levelDD:SetValue(self.guildMplusFilters.level or "")
	levelDD:SetCallback("OnValueChanged", function(_, _, val)
		self.guildMplusFilters.level = val
		self:SelectTab(self.tabGroup, "raider_audit")
	end)
	filterGroup:AddChild(levelDD)
	
	-- Result dropdown
	local resultDD = AceGUI:Create("Dropdown")
	resultDD:SetLabel("")
	resultDD:SetWidth(90)
	resultDD:SetList(results)
	resultDD:SetValue(self.guildMplusFilters.result or "")
	resultDD:SetCallback("OnValueChanged", function(_, _, val)
		self.guildMplusFilters.result = val
		self:SelectTab(self.tabGroup, "raider_audit")
	end)
	filterGroup:AddChild(resultDD)
	
	-- Apply filters
	local filtered = {}
	for _, run in ipairs(runs) do
		local pass = true
		
		-- Player filter
		if self.guildMplusFilters.player ~= "" then
			local p = Cerebro:GetFullPlayerName(run.uploadedBy or "Unknown")
			if p ~= self.guildMplusFilters.player then pass = false end
		end
		
		-- Dungeon filter
		if pass and self.guildMplusFilters.dungeon ~= "" then
			local d = GetDungeonDisplayName(run)
			if d ~= self.guildMplusFilters.dungeon then pass = false end
		end
		
		-- Level filter
		if pass and self.guildMplusFilters.level ~= "" then
			local l = "+" .. (run.level or 0)
			if l ~= self.guildMplusFilters.level then pass = false end
		end
		
		-- Result filter
		if pass and self.guildMplusFilters.result ~= "" then
			if self.guildMplusFilters.result == "success" and not run.success then pass = false end
			if self.guildMplusFilters.result == "fail" and run.success then pass = false end
		end
		
		if pass then table.insert(filtered, run) end
	end
	
	-- Record count (native frame, right-aligned)
	-- Create a small frame to hold the font string (so it can be tracked for cleanup)
	local countFrame = TrackGuildMplusFrame(CreateFrame("Frame", nil, filterGroup.frame))
	countFrame:SetSize(120, 20)
	countFrame:SetPoint("RIGHT", filterGroup.frame, "RIGHT", -20, 0)
	local countFS = countFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
	countFS:SetAllPoints()
	countFS:SetText("|cff888888" .. #filtered .. " of " .. #runs .. " runs|r")
	countFS:SetJustifyH("RIGHT")
	
	-- Main content area
	local mainGroup = AceGUI:Create("InlineGroup")
	mainGroup:SetTitle("")
	mainGroup:SetFullWidth(true)
	mainGroup:SetFullHeight(true)
	mainGroup:SetLayout("Fill")
	container:AddChild(mainGroup)
	
	mainGroup:SetCallback("OnRelease", function()
		CleanupGuildMplusFrames()
	end)
	
	local scroll = AceGUI:Create("ScrollFrame")
	scroll:SetLayout("Flow")
	scroll:SetFullWidth(true)
	scroll:SetFullHeight(true)
	mainGroup:AddChild(scroll)
	
	-- Empty state
	if #filtered == 0 then
		local lbl = AceGUI:Create("Label")
		if #runs == 0 then
			lbl:SetText("\n\nNo guild M+ runs recorded yet.\n\nRuns will appear here as guild members complete M+ dungeons with the addon installed.")
		else
			lbl:SetText("\n\nNo runs match the selected filters.")
		end
		lbl:SetColor(0.5, 0.5, 0.5)
		lbl:SetFullWidth(true)
		scroll:AddChild(lbl)
		return
	end
	
	-- Sort by timestamp descending
	table.sort(filtered, function(a, b)
		return (a.timestamp or 0) > (b.timestamp or 0)
	end)
	
	-- Header row
	local headerWrapper = AceGUI:Create("SimpleGroup")
	headerWrapper:SetLayout("Fill")
	headerWrapper:SetFullWidth(true)
	headerWrapper:SetHeight(24)
	scroll:AddChild(headerWrapper)
	
	local hf = TrackGuildMplusFrame(CreateFrame("Frame", nil, headerWrapper.frame))
	hf:SetAllPoints()
	
	local fsPlayer = hf:CreateFontString(nil, "OVERLAY", "GameFontNormal")
	fsPlayer:SetPoint("LEFT", 10, 0)
	fsPlayer:SetText("|cffFFD700Player|r")
	
	local fsLevel = hf:CreateFontString(nil, "OVERLAY", "GameFontNormal")
	fsLevel:SetPoint("LEFT", 180, 0)
	fsLevel:SetText("|cffFFD700Level / Dungeon|r")
	
	local fsParty = hf:CreateFontString(nil, "OVERLAY", "GameFontNormal")
	fsParty:SetPoint("LEFT", 380, 0)
	fsParty:SetText("|cffFFD700Party|r")
	
	local fsResult = hf:CreateFontString(nil, "OVERLAY", "GameFontNormal")
	fsResult:SetPoint("RIGHT", -10, 0)
	fsResult:SetText("|cffFFD700Result|r")
	
	-- Data rows
	local displayCount = math.min(#filtered, 100)
	
	for i = 1, displayCount do
		local run = filtered[i]
		local isStripe = (i % 2 == 0)
		
		local wrapper = AceGUI:Create("SimpleGroup")
		wrapper:SetLayout("Fill")
		wrapper:SetFullWidth(true)
		wrapper:SetHeight(GUILD_MPLUS_ROW_HEIGHT)
		scroll:AddChild(wrapper)
		
		local f = TrackGuildMplusFrame(CreateFrame("Frame", nil, wrapper.frame, "BackdropTemplate"))
		f:SetAllPoints()
		
		if isStripe then
			local bg = f:CreateTexture(nil, "BACKGROUND")
			bg:SetAllPoints()
			bg:SetColorTexture(1, 1, 1, 0.05)
		end
		
		-- A. Player Name (full Name-Realm with class color like Standings)
		local rawName = run.uploadedBy or "Unknown"
		-- Normalize to full Name-Realm format
		local fullName = Cerebro:GetFullPlayerName(rawName)
		
		-- Try to find class from: 1) group data (uploader in party), 2) players database
		local playerClass = nil
		
		-- Check group data first - uploader should be in the party
		if run.group then
			for _, member in ipairs(run.group) do
				local memberName = member.name or ""
				-- Match by short name (without realm)
				if memberName:find("^" .. rawName) or rawName:find("^" .. Cerebro:GetShortPlayerName(memberName)) then
					playerClass = member.class and string.upper(member.class)
					break
				end
			end
		end
		
		-- Fallback to players database
		if not playerClass then
			local playerData = Cerebro.db.global.players and Cerebro.db.global.players[fullName]
			playerClass = playerData and playerData.class
		end
		
		local classColor = "|cffFFFFFF"  -- Default white
		if playerClass then
			local cc = RAID_CLASS_COLORS and RAID_CLASS_COLORS[playerClass]
			if cc then
				classColor = "|c" .. cc.colorStr
			end
		end
		local playerFS = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
		playerFS:SetPoint("LEFT", 10, 0)
		playerFS:SetWidth(160)
		playerFS:SetText(classColor .. fullName .. "|r")
		playerFS:SetJustifyH("LEFT")
		
		-- B. Level Badge
		local level = run.level or 0
		local levelColor = "|cffFFFFFF"
		if level >= 20 then levelColor = "|cffFF8000"
		elseif level >= 15 then levelColor = "|cffA335EE"
		elseif level >= 10 then levelColor = "|cff0070DD"
		end
		
		local lvlFS = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
		lvlFS:SetPoint("LEFT", 180, 0)
		lvlFS:SetText(levelColor .. "+" .. level .. "|r")
		
		-- C. Dungeon Name (using lookup)
		local mapName = GetDungeonDisplayName(run)
		
		local nameFS = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
		nameFS:SetPoint("LEFT", 225, 0)
		nameFS:SetWidth(140)
		nameFS:SetText(mapName)
		nameFS:SetJustifyH("LEFT")
		nameFS:SetWordWrap(false)
		
		-- D. Party Icons
		local startX = 380
		local iconSize = 24
		
		local roleOrder = { TANK = 1, HEALER = 2, HEALS = 2, DAMAGER = 3, DPS = 3, DMG = 3 }
		local sortedGroup = {}
		for _, m in ipairs(run.group or {}) do table.insert(sortedGroup, m) end
		table.sort(sortedGroup, function(a, b)
			return (roleOrder[a.role] or 4) < (roleOrder[b.role] or 4)
		end)
		
		for j, member in ipairs(sortedGroup) do
			local iframe = TrackGuildMplusFrame(CreateFrame("Frame", nil, f))
			iframe:SetSize(iconSize, iconSize)
			iframe:SetPoint("LEFT", startX + ((j - 1) * (iconSize + 4)), 0)
			
			local bgCircle = iframe:CreateTexture(nil, "BACKGROUND")
			bgCircle:SetAllPoints()
			bgCircle:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask")
			
			local classKey = string.upper(member.class or "PRIEST")
			local cc = RAID_CLASS_COLORS and RAID_CLASS_COLORS[classKey]
			if cc then
				bgCircle:SetVertexColor(cc.r, cc.g, cc.b, 1)
			else
				bgCircle:SetVertexColor(0.5, 0.5, 0.5, 1)
			end
			
			local roleIcon = iframe:CreateTexture(nil, "ARTWORK")
			roleIcon:SetSize(15, 15)
			roleIcon:SetPoint("CENTER")
			
			local role = member.role
			if role == "TANK" then
				roleIcon:SetAtlas("roleicon-tiny-tank")
			elseif role == "HEALER" or role == "HEALS" then
				roleIcon:SetAtlas("roleicon-tiny-healer")
			else
				roleIcon:SetAtlas("roleicon-tiny-dps")
			end
			
			iframe:SetScript("OnEnter", function(self)
				GameTooltip:SetOwner(self, "ANCHOR_TOP")
				local colorStr = member.class and RAID_CLASS_COLORS and RAID_CLASS_COLORS[string.upper(member.class)]
					and "|c" .. RAID_CLASS_COLORS[string.upper(member.class)].colorStr or "|cffFFFFFF"
				GameTooltip:AddLine(colorStr .. (member.name or "Unknown") .. "|r")
				GameTooltip:Show()
			end)
			iframe:SetScript("OnLeave", function() GameTooltip:Hide() end)
		end
		
		-- E. Result
		local statusIcon = run.success
			and "|TInterface\\RaidFrame\\ReadyCheck-Ready:20|t"
			or "|TInterface\\RaidFrame\\ReadyCheck-NotReady:20|t"
		
		local statusFS = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
		statusFS:SetPoint("RIGHT", -10, 0)
		statusFS:SetText(statusIcon)
		
		-- F. EP Award (show actual awarded amount; fall back to totalEP for received/legacy runs)
		local displayEP = run.awardedEP or run.totalEP or 0
		local capSuffix = run.wasCapped and " |cffff8800(Capped)|r" or ""
		local epFS = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
		epFS:SetPoint("RIGHT", statusFS, "LEFT", -10, 0)
		epFS:SetText("|cff00ff00+" .. displayEP .. " EP|r" .. capSuffix)
		
		-- G. Time
		local mins = math.floor((run.duration or 0) / 60)
		local secs = (run.duration or 0) % 60
		local timeStr = string.format("%d:%02d", mins, secs)
		if run.upgrade and run.upgrade > 0 then
			timeStr = timeStr .. " |cff00ff00(+" .. run.upgrade .. ")|r"
		end
		
		local timeFS = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
		timeFS:SetPoint("RIGHT", epFS, "LEFT", -20, 0)
		timeFS:SetText(timeStr)
	end
	
	if #filtered > displayCount then
		local moreLabel = AceGUI:Create("Label")
		moreLabel:SetText(string.format("\n|cff888888Showing %d of %d runs.|r", displayCount, #filtered))
		moreLabel:SetFullWidth(true)
		scroll:AddChild(moreLabel)
	end
end

--[[=============================================
	Wishlist Tab
===============================================]]

function UI:DrawWishlistTab(container)
	-- NOTE: Wishlist module exists (Modules/Wishlist.lua) but is not fully integrated.
	-- This tab is a placeholder for potential future implementation.
	-- When ready: wire up Wishlist:GetList(), add/remove/priority UI, and auto-bid config.
	local label = AceGUI:Create("Label")
	label:SetText("Wishlist management is not yet available.\n\nThis feature is planned for a future update.")
	label:SetFullWidth(true)
	container:AddChild(label)
end


--[[=============================================
	Helpers
===============================================]]

function UI:ShowStandings()
	self:Show()
	if self.tabGroup then
		self.tabGroup:SelectTab("standings")
	end
end

function UI:ShowHistory()
	self:Show()
	if self.tabGroup then
		self.tabGroup:SelectTab("history")
	end
end

function UI:ShowWishlist()
	self:Show()
	if self.tabGroup then
		self.tabGroup:SelectTab("wishlist")
	end
end

function UI:ShowConfig()
	LibStub("AceConfigDialog-3.0"):Open("Cerebro")
end

function UI:RefreshStandings()
	Cerebro:GetModule("StandingsFrame"):Refresh()
end

--[[=============================================
	Commands Tab (Command Registry)
=============================================]]

function UI:DrawCommandsTab(container)
	if not _G.UI_CommandRegistry then
		self:ShowUnavailableMessage(container, "Command Registry")
		return
	end
	
	-- Wrapper group to provide proper sizing for ScrollFrame (OfficerTab Pattern)
	local wrapper = AceGUI:Create("SimpleGroup")
	wrapper:SetFullWidth(true)
	wrapper:SetFullHeight(true)
	wrapper:SetLayout("Fill")
	container:AddChild(wrapper)

	local scroll = AceGUI:Create("ScrollFrame")
	scroll:SetFullWidth(true)
	scroll:SetFullHeight(true)
	scroll:SetLayout("Flow")
	wrapper:AddChild(scroll)
	
	-- Header (Inside Scroll)
	local header = AceGUI:Create("Heading")
	header:SetText("Command Center")
	header:SetFullWidth(true)
	scroll:AddChild(header)
	
	-- Search box (Inside Scroll)
	local searchBox = AceGUI:Create("EditBox")
	searchBox:SetLabel("Search Commands")
	searchBox:SetWidth(300)
	searchBox:DisableButton(true)
	searchBox:SetCallback("OnTextChanged", function(widget, event, text)
		self:RefreshCommandsTab(scroll, text)
	end)
	scroll:AddChild(searchBox)
	
	-- Spacer
	local spacer = AceGUI:Create("Label")
	spacer:SetText("\n")
	spacer:SetFullWidth(true)
	scroll:AddChild(spacer)
	
	-- Command list container (InlineGroup mirrors sessionGroup in OfficerTab)
	local cmdListContainer = AceGUI:Create("InlineGroup")
	cmdListContainer:SetTitle("Commands")
	cmdListContainer:SetFullWidth(true)
	cmdListContainer:SetLayout("Flow")
	scroll:AddChild(cmdListContainer)
	
	-- Store reference for refresh
	scroll.commandListContainer = cmdListContainer
	
	-- Render commands
	self:RenderCommandList(cmdListContainer, "")
	
	-- Force layout recalculation after all children are added
	-- This fixes the scrollbar not appearing until window resize
	Cerebro:ScheduleTimer(function()
		if scroll and scroll.frame then
			scroll:DoLayout()
		end
	end, 0.01)
end

function UI:RefreshCommandsTab(scrollFrame, searchText)
	-- Get the command list container
	local cmdList = scrollFrame.commandListContainer
	if not cmdList then return end
	
	-- Clear only the command list (preserves Header, SearchBox, Spacer)
	cmdList:ReleaseChildren()
	
	-- Re-render commands
	self:RenderCommandList(cmdList, searchText or "")
end

function UI:RenderCommandList(container, searchText)
	if not _G.UI_CommandRegistry then
		return
	end
	
	local commands = _G.UI_CommandRegistry:GetFiltered(searchText)
	
	for _, cmd in ipairs(commands) do
		local cmdGroup = AceGUI:Create("InlineGroup")
		cmdGroup:SetTitle(cmd.displayName or cmd.id)
		cmdGroup:SetFullWidth(true)
		cmdGroup:SetLayout("Flow")
		container:AddChild(cmdGroup)
		
		-- Description
		local desc = AceGUI:Create("Label")
		desc:SetText((cmd.description or "") .. (cmd.slash and ("\n|cffaaaaaa" .. cmd.slash .. "|r") or ""))
		desc:SetFullWidth(true)
		cmdGroup:AddChild(desc)
		
		-- Run button
		local runBtn = AceGUI:Create("Button")
		runBtn:SetText("Run")
		runBtn:SetWidth(100)
		runBtn:SetCallback("OnClick", function()
			-- Click-time RBAC check
			if cmd.officerOnly and not self:IsOfficerSafe() then
				Cerebro:Print("This command requires officer permissions.")
				return
			end
			
			local success, err = _G.UI_CommandRegistry:Execute(cmd.id, {})
			if not success then
				Cerebro:Print("Command failed: " .. tostring(err))
			end
		end)
		cmdGroup:AddChild(runBtn)
	end
	
	if #commands == 0 then
		local noResults = AceGUI:Create("Label")
		noResults:SetText("No commands found matching search.")
		noResults:SetColor(0.7, 0.7, 0.7)
		noResults:SetFullWidth(true)
		container:AddChild(noResults)
	end
	-- Bottom Spacer to prevent cutoff
	local bottomSpacer = AceGUI:Create("Label")
	bottomSpacer:SetText(" ")
	bottomSpacer:SetFullWidth(true)
	container:AddChild(bottomSpacer)
end


-- End of File