--ui/options.lua, main Details Framework options panel and slash commands (frontend).
--builds the options window and the profile-management window once during init (no
--lazy initialization), then shows or hides them on demand.

local _ = nil
local addonName, private = ...

---@type detailsframework
local detailsFramework = DetailsFramework

private.UI = private.UI or {}

--constants
--wide enough for the option list on the left and the embedded FPS chart on the right.
local OPTIONS_PANEL_WIDTH = 720
local OPTIONS_PANEL_HEIGHT = 600
--the group boxes extend ~12px beyond their widgets (padding + margin), so start the menu well in
--from the top-left corner or the first boxes clip the edge and the title bar.
local OPTIONS_MENU_X = 20
local OPTIONS_MENU_Y = -48
local OPTIONS_MENU_BOTTOM_MARGIN = 50
local PROFILE_HOST_WIDTH = 540
local PROFILE_HOST_HEIGHT = 400
local PROFILE_PANEL_WIDTH = 520
local PROFILE_PANEL_HEIGHT = 360
--the Live FPS Test window: a single column of the Live Hide + Live Graphics toggles.
local LIVE_TEST_PANEL_WIDTH = 460
local LIVE_TEST_PANEL_HEIGHT = 560
local LIVE_TEST_MENU_BOTTOM_MARGIN = 50

---@class FPSPP_Options
local Options = {}
private.UI.Options = Options

local getScaledCursorPosition = function()
	local scale = UIParent:GetEffectiveScale()
	local x, y = GetCursorPosition()
	return x / scale, y / scale
end

local anchorPanelToTopLeft = function(panel, left, top)
	local parentWidth = UIParent:GetWidth()
	local parentHeight = UIParent:GetHeight()
	local panelWidth = panel:GetWidth()
	local panelHeight = panel:GetHeight()

	left = math.max(0, math.min(left, parentWidth - panelWidth))
	top = math.max(panelHeight, math.min(top, parentHeight))

	panel:ClearAllPoints()
	panel:SetPoint("topleft", UIParent, "bottomleft", left, top)
end

--gives a panel sane mouse behavior: drag it by the TITLE BAR (not the body). we skip DF's
--MakeDraggable helper and Blizzard's StartMoving path because that path can leave the panel with
--zero anchors while the mouse is held.
local setupPanelInteraction = function(panel)
	local titleBar = panel.TitleBar

	panel:EnableMouse(true)

	titleBar:EnableMouse(true)
	titleBar:RegisterForDrag("LeftButton")
	titleBar:SetScript("OnDragStart", function(self)
		local panelLeft = panel:GetLeft()
		local panelTop = panel:GetTop()
		if not panelLeft or not panelTop then
			panel:SetPoint("center", UIParent, "center", 0, 0)
			panelLeft = panel:GetLeft()
			panelTop = panel:GetTop()
		end

		if not panelLeft or not panelTop then
			return
		end

		local cursorX, cursorY = getScaledCursorPosition()
		self.dragOffsetX = cursorX - panelLeft
		self.dragOffsetY = cursorY - panelTop

		self:SetScript("OnUpdate", function()
			local currentCursorX, currentCursorY = getScaledCursorPosition()
			anchorPanelToTopLeft(panel, currentCursorX - self.dragOffsetX, currentCursorY - self.dragOffsetY)
		end)
	end)
	titleBar:SetScript("OnDragStop", function(self)
		self:SetScript("OnUpdate", nil)
	end)
	titleBar:SetScript("OnHide", function(self)
		self:SetScript("OnUpdate", nil)
	end)
end

--expose the title-bar drag setup so other windows (the detached FPS monitor) get the exact same
--drag behavior without duplicating the "avoid MakeDraggable's zero-anchor path" logic above. any
--panel passed here must have a .TitleBar (CreateRoundedPanel with use_titlebar builds one).
private.UI.SetupPanelDrag = setupPanelInteraction

--builds the standalone options panel and fills it with DF:BuildMenu.
local buildOptionsPanel = function()
	--rounded-corner panel with a title bar (the title bar ships with its own close button).
	local panel = detailsFramework:CreateRoundedPanel(UIParent, "FPSPlusPlusOptionsPanel1", {
		width = OPTIONS_PANEL_WIDTH,
		height = OPTIONS_PANEL_HEIGHT,
		use_titlebar = true,
		title = "FPS ++",
	})
	panel:SetPoint("center", UIParent, "center", 0, 0)
	panel:SetColor(.1, .1, .1, 1)
	panel:SetTitleBarColor(.2, .2, .2, 1)
	panel:SetBorderCornerColor(.2, .2, .2, 1)
	panel:SetRoundness(3)
	--the DF CreateTitleBar builds the title fontstring but never fills it from the 'title' option,
	--so the bar renders blank; push the configured title into it ourselves.
	panel.TitleBar.Text:SetText(panel.options.title)
	setupPanelInteraction(panel)

	local menuOptions = private.UI.Categories:BuildMenuOptions()
	local fontTemplate, dropdownTemplate, switchTemplate, sliderTemplate, buttonTemplate = private.UI.Widgets.GetOptionTemplates()

	--signature verified against buildmenu.lua: the switchIsCheckbox boolean (true)
	--sits between the switch and slider templates so toggles render as checkboxes.
	detailsFramework:BuildMenu(panel, menuOptions, OPTIONS_MENU_X, OPTIONS_MENU_Y, OPTIONS_PANEL_HEIGHT - OPTIONS_MENU_BOTTOM_MARGIN, false, fontTemplate, dropdownTemplate, switchTemplate, true, sliderTemplate, buttonTemplate)

	panel:Hide()
	return panel
end

--builds the profile-management window: DF's stock profile panel hosted inside a
--titled simple panel (CreateProfilePanel returns a bare, unpositioned frame).
local buildProfilePanel = function()
	local host = detailsFramework:CreateSimplePanel(UIParent, PROFILE_HOST_WIDTH, PROFILE_HOST_HEIGHT, "FPS++ Profiles", "FPSPlusPlusProfileHost", {NoScripts = true})
	host:SetPoint("center")
	setupPanelInteraction(host)

	local profilePanel = detailsFramework.SavedVars.CreateProfilePanel(private.addon, "FPSPlusPlusProfilePanel", host, {width = PROFILE_PANEL_WIDTH, height = PROFILE_PANEL_HEIGHT})
	profilePanel:SetPoint("topleft", host, "topleft", 10, -30)
	--CreateProfilePanel returns a HIDDEN frame; show it so its contents appear when the host opens.
	--without this the host window opens empty (the child panel stays hidden inside it).
	profilePanel:Show()
	host.ProfilePanel = profilePanel

	host:Hide()
	return host
end

--builds the standalone "Live FPS Test" window: the Live Hide + Live Graphics toggles, each applying
--immediately (bypassing the context gate). same rounded-panel style as the main options window.
local buildLiveTestPanel = function()
	local panel = detailsFramework:CreateRoundedPanel(UIParent, "FPSPlusPlusLiveTestPanel1", {
		width = LIVE_TEST_PANEL_WIDTH,
		height = LIVE_TEST_PANEL_HEIGHT,
		use_titlebar = true,
		title = "FPS++ Live Test",
	})
	panel:SetPoint("center", UIParent, "center", 0, 0)
	panel:SetColor(.1, .1, .1, 1)
	panel:SetTitleBarColor(.2, .2, .2, 1)
	panel:SetBorderCornerColor(.2, .2, .2, 1)
	panel:SetRoundness(3)
	setupPanelInteraction(panel)

	local menuOptions = private.UI.Categories:BuildLiveTestOptions()
	local fontTemplate, dropdownTemplate, switchTemplate, sliderTemplate, buttonTemplate = private.UI.Widgets.GetOptionTemplates()

	--same BuildMenu signature as the main panel (switchIsCheckbox = true renders toggles as checkboxes).
	detailsFramework:BuildMenu(panel, menuOptions, OPTIONS_MENU_X, OPTIONS_MENU_Y, LIVE_TEST_PANEL_HEIGHT - LIVE_TEST_MENU_BOTTOM_MARGIN, false, fontTemplate, dropdownTemplate, switchTemplate, true, sliderTemplate, buttonTemplate)

	panel:Hide()
	return panel
end

--builds both windows and registers slash commands; called once from OnInit.
function Options:Initialize()
	self.OptionsPanel = buildOptionsPanel()
	self.ProfileHostPanel = buildProfilePanel()
	self.LiveTestPanel = buildLiveTestPanel()
	--both windows are movable and share a strata, so they can overlap. keep the Live FPS Test
	--window at least ten frame levels above the options panel so it always draws on top --
	--raising a frame's level shifts its whole contents (title bar + widgets) by the same amount.
	self.LiveTestPanel:SetFrameLevel(self.OptionsPanel:GetFrameLevel() + 10)
	self:RegisterSlashCommands()
end

--returns the main options panel frame (used by the event panel to anchor to its side).
function Options:GetMainPanel()
	return self.OptionsPanel
end

--shows the options panel and re-syncs every widget (values + each widget's disableif) to the current
--state, so e.g. the profile dropdown shows the active profile and is enabled only when auto-switch is off.
function Options:Open()
	self.OptionsPanel:Show()
	self.OptionsPanel:RefreshOptions()
end

--hides the options panel.
function Options:Close()
	self.OptionsPanel:Hide()
end

--toggles the options panel visibility.
function Options:Toggle()
	local panel = self.OptionsPanel
	if panel:IsShown() then
		panel:Hide()
	else
		panel:Show()
		--re-sync widgets to current state on open (see Options:Open).
		panel:RefreshOptions()

		if panel:GetNumPoints() == 0 then
			panel:SetPoint("center", UIParent, "center", 0, 0)
		end
	end
end

--re-syncs widget values if the panel is open (used after a profile switch).
function Options:RefreshIfOpen()
	local panel = self.OptionsPanel
	if panel:IsShown() then
		panel:RefreshOptions()
	end
end

--shows the Live FPS Test window and re-syncs every toggle to the current live state.
function Options:OpenLiveTestPanel()
	self.LiveTestPanel:Show()
	self.LiveTestPanel:RefreshOptions()

	if self.LiveTestPanel:GetNumPoints() == 0 then
		self.LiveTestPanel:SetPoint("center", UIParent, "center", 0, 0)
	end
end

--re-syncs the Live FPS Test toggles if the window is open (used after a toggle applies, so a
--protected frame blocked in combat shows its true state).
function Options:RefreshLiveTestIfOpen()
	if self.LiveTestPanel:IsShown() then
		self.LiveTestPanel:RefreshOptions()
	end
end

--restores only the live-test overrides (the reduced graphics CVars and the hidden frame groups),
--leaving the configured profile untouched, then refreshes the window.
function Options:RestoreLiveTest()
	private.Core.Optimizer:RestoreLiveCVars()
	private.Core.ManualHide:ShowAllGroups()
	self:RefreshLiveTestIfOpen()
end

--shows the profile-management window and refreshes its contents.
function Options:OpenProfilePanel()
	self.ProfileHostPanel:Show()
	detailsFramework.SavedVars.RefreshProfilePanel(self.ProfileHostPanel.ProfilePanel)
end

--registers the slash commands and their handler; called once from Initialize.
function Options:RegisterSlashCommands()
	--the SLASH_* globals and SlashCmdList are the WoW-mandated slash command mechanism.
	SLASH_FPSPLUSPLUS1 = "/fps" --[[GLOBAL]]
	SLASH_FPSPLUSPLUS2 = "/fpspp" --[[GLOBAL]]
	SLASH_FPSPLUSPLUS3 = "/fpsplusplus" --[[GLOBAL]]

	--keep the handler short: parse the argument and dispatch to a method.
	SlashCmdList["FPSPLUSPLUS"] = function(message)
		local command = strtrim(string.lower(message))
		if command == "reset" or command == "restore" then
			private.Core.Optimizer:RestoreAll()
			private.Print(private.locale["MSG_RESTORED"])
		elseif command == "status" or command == "debug" then
			private.Core.Optimizer:PrintStatus()
		elseif command == "trace" then
			private.Core.Optimizer:InstallHideTrace()
		elseif command == "profiles" then
			Options:OpenProfilePanel()
		else
			Options:Toggle()
		end
	end
end
