local _, ns = ...
local SlickUI = ns.SlickUI

local Movers = {
	Registered = {},
	GridLines = {},
}
SlickUI.Movers = Movers

local function RaiseEditModeControl(frame)
	-- Mover overlays and externally discovered frames can also occupy
	-- FULLSCREEN_DIALOG. TOOLTIP plus an explicit level keeps the edit-mode
	-- controls readable and clickable regardless of creation order.
	frame:SetFrameStrata("TOOLTIP")
	frame:SetFrameLevel(1000)
end

local function RoundToStep(value, step)
	return math.floor(value / step + 0.5) * step
end

local function GetEffectiveSize(frame)
	local parentScale = UIParent:GetEffectiveScale()
	local scale = frame:GetEffectiveScale() / parentScale
	return frame:GetWidth() * scale, frame:GetHeight() * scale
end

local function GetRelativeCenter(frame)
	local centerX, centerY = frame:GetCenter()
	local parentX, parentY = UIParent:GetCenter()
	if not centerX or not centerY or not parentX or not parentY then return nil end
	-- GetCenter() is expressed in the region's effective coordinate space.
	-- Convert scaled frames back into UIParent coordinates before persisting the
	-- anchor, otherwise a non-1.0 unit-frame scale jumps when it is re-anchored.
	local parentScale = UIParent:GetEffectiveScale()
	local frameScale = frame:GetEffectiveScale()
	local scaleRatio = frameScale / parentScale
	return centerX * scaleRatio - parentX, centerY * scaleRatio - parentY
end

local function FindNearestOffset(candidates, threshold)
	local best
	for _, offset in ipairs(candidates) do
		if math.abs(offset) <= threshold and (not best or math.abs(offset) < math.abs(best)) then
			best = offset
		end
	end
	return best or 0
end

local function ApplyPosition(frame, key)
	local position = SlickUI.DB.positions[key] or SlickUI.Defaults.positions[key]
	frame:ClearAllPoints()
	frame:SetPoint("CENTER", UIParent, "CENTER", position.x, position.y)
end

local function SavePosition(key, x, y)
	if not x or not y then return end
	SlickUI.DB.positions[key] = SlickUI.DB.positions[key] or {}
	SlickUI.DB.positions[key].x = x
	SlickUI.DB.positions[key].y = y
end

function Movers:CreateGrid()
	if self.Grid then return self.Grid end
	local grid = CreateFrame("Frame", "SlickUIEditModeGrid", UIParent)
	grid:SetAllPoints(UIParent)
	grid:SetFrameStrata("BACKGROUND")
	grid:SetFrameLevel(1)
	grid:EnableMouse(false)
	grid:Hide()
	self.Grid = grid
	return grid
end

function Movers:RefreshGrid()
	local grid = self:CreateGrid()
	for _, line in ipairs(self.GridLines) do line:Hide() end
	if SlickUI.DB.locked or not SlickUI.DB.editMode.snapToGrid then
		grid:Hide()
		return
	end

	local step = math.max(8, SlickUI.DB.editMode.gridSize or 20)
	local width, height = UIParent:GetWidth(), UIParent:GetHeight()
	local lineIndex = 0
	local function AcquireLine()
		lineIndex = lineIndex + 1
		local line = self.GridLines[lineIndex]
		if not line then
			line = grid:CreateTexture(nil, "BACKGROUND")
			line:SetColorTexture(1.00, 0.67, 0.12, 0.16)
			self.GridLines[lineIndex] = line
		end
		line:ClearAllPoints()
		line:Show()
		return line
	end

	local x = 0
	while x <= width / 2 do
		for _, signedX in ipairs(x == 0 and { 0 } or { x, -x }) do
			local line = AcquireLine()
			line:SetPoint("TOP", grid, "TOP", signedX, 0)
			line:SetPoint("BOTTOM", grid, "BOTTOM", signedX, 0)
			line:SetWidth(x == 0 and 2 or 1)
			line:SetColorTexture(1.00, 0.67, 0.12, x == 0 and 0.42 or 0.16)
		end
		x = x + step
	end

	local y = 0
	while y <= height / 2 do
		for _, signedY in ipairs(y == 0 and { 0 } or { y, -y }) do
			local line = AcquireLine()
			line:SetPoint("LEFT", grid, "LEFT", 0, signedY)
			line:SetPoint("RIGHT", grid, "RIGHT", 0, signedY)
			line:SetHeight(y == 0 and 2 or 1)
			line:SetColorTexture(1.00, 0.67, 0.12, y == 0 and 0.42 or 0.16)
		end
		y = y + step
	end
	grid:Show()
end

function Movers:CreateExitButton()
	if self.ExitButton then return self.ExitButton end
	local button = CreateFrame("Button", "SlickUICloseEditModeButton", UIParent, "UIPanelButtonTemplate")
	button:SetSize(170, 30)
	button:SetPoint("TOP", UIParent, "TOP", 0, -34)
	RaiseEditModeControl(button)
	button:SetText("Close SlickUI Edit Mode")
	if button:GetFontString() then SlickUI.Theme:SetFont(button:GetFontString(), 11) end
	button:SetScript("OnClick", function() Movers:SetLocked(true) end)
	button:Hide()
	self.ExitButton = button
	return button
end

function Movers:OpenBlizzardEditMode()
	if InCombatLockdown() then
		SlickUI:Print("Blizzard Edit Mode cannot be opened during combat.")
		return
	end
	-- End SlickUI editing first so its overlays and grid cannot compete with
	-- Blizzard's selection handles or alter the layout while Blizzard saves it.
	self:SetLocked(true)
	if SlickUI.ActionBars and not SlickUI.ActionBars:BeginBlizzardEditMode() then return end
	C_Timer.After(0, function()
		if C_AddOns and C_AddOns.LoadAddOn then pcall(C_AddOns.LoadAddOn, "Blizzard_EditMode") end
		if EditModeManagerFrame then
			if not EditModeManagerFrame.SlickUIActionBarHandoffHooked then
				EditModeManagerFrame:HookScript("OnHide", function()
					if SlickUI.ActionBars then SlickUI.ActionBars:EndBlizzardEditMode() end
				end)
				EditModeManagerFrame.SlickUIActionBarHandoffHooked = true
			end
			ShowUIPanel(EditModeManagerFrame)
		else
			if SlickUI.ActionBars then SlickUI.ActionBars:EndBlizzardEditMode() end
			SlickUI:Print("Blizzard Edit Mode is not available yet.")
		end
	end)
end

function Movers:CreateBlizzardEditButton()
	if self.BlizzardEditButton then return self.BlizzardEditButton end
	local button = CreateFrame("Button", "SlickUIOpenBlizzardEditModeButton", UIParent, "UIPanelButtonTemplate")
	button:SetSize(170, 30)
	button:SetPoint("RIGHT", self:CreateExitButton(), "LEFT", -10, 0)
	RaiseEditModeControl(button)
	button:SetText("Blizzard Edit Mode")
	if button:GetFontString() then SlickUI.Theme:SetFont(button:GetFontString(), 11) end
	button:SetScript("OnClick", function() Movers:OpenBlizzardEditMode() end)
	button:Hide()
	self.BlizzardEditButton = button
	return button
end

function Movers:RefreshSnapButtons()
	local edit = SlickUI.DB and SlickUI.DB.editMode
	if not edit then return end
	if self.GridSnapButton then
		self.GridSnapButton:SetText("Grid Snap: " .. (edit.snapToGrid and "On" or "Off"))
	end
	if self.FrameSnapButton then
		self.FrameSnapButton:SetText("Frame Snap: " .. (edit.snapToFrames and "On" or "Off"))
	end
end

function Movers:CreateSnapButtons()
	if self.GridSnapButton and self.FrameSnapButton then
		return self.GridSnapButton, self.FrameSnapButton
	end

	local gridButton = CreateFrame("Button", "SlickUIGridSnapButton", UIParent, "UIPanelButtonTemplate")
	gridButton:SetSize(125, 30)
	gridButton:SetPoint("RIGHT", self:CreateBlizzardEditButton(), "LEFT", -10, 0)
	RaiseEditModeControl(gridButton)
	if gridButton:GetFontString() then SlickUI.Theme:SetFont(gridButton:GetFontString(), 10) end
	gridButton:SetScript("OnClick", function()
		Movers:SetGridEnabled(not SlickUI.DB.editMode.snapToGrid)
	end)
	gridButton:Hide()
	self.GridSnapButton = gridButton

	local frameButton = CreateFrame("Button", "SlickUIFrameSnapButton", UIParent, "UIPanelButtonTemplate")
	frameButton:SetSize(125, 30)
	frameButton:SetPoint("RIGHT", gridButton, "LEFT", -10, 0)
	RaiseEditModeControl(frameButton)
	if frameButton:GetFontString() then SlickUI.Theme:SetFont(frameButton:GetFontString(), 10) end
	frameButton:SetScript("OnClick", function()
		Movers:SetFrameSnapEnabled(not SlickUI.DB.editMode.snapToFrames)
	end)
	frameButton:Hide()
	self.FrameSnapButton = frameButton
	self:RefreshSnapButtons()
	return gridButton, frameButton
end

function Movers:IsMoverOverlayVisible(key)
	local hidden = SlickUI.DB and SlickUI.DB.editMode and SlickUI.DB.editMode.hiddenMovers
	return not hidden or hidden[key] ~= true
end

function Movers:SetMoverOverlayVisible(key, visible)
	SlickUI.DB.editMode.hiddenMovers = SlickUI.DB.editMode.hiddenMovers or {}
	if visible then
		SlickUI.DB.editMode.hiddenMovers[key] = nil
	else
		SlickUI.DB.editMode.hiddenMovers[key] = true
	end
	local frame = self.Registered[key]
	if frame and frame.SlickMoverOverlay then
		frame.SlickMoverOverlay:EnableMouse(not SlickUI.DB.locked and visible)
		frame.SlickMoverOverlay:SetShown(not SlickUI.DB.locked and visible)
	end
	if SlickUI.ExternalFrames then
		local entry = SlickUI.ExternalFrames.Frames[key]
		if entry and entry.overlay then
			entry.overlay:EnableMouse(not SlickUI.DB.locked and visible)
			entry.overlay:SetShown(not SlickUI.DB.locked and visible)
		end
		if key == "MinimapCluster" and SlickUI.ExternalFrames.MinimapProxy then
			SlickUI.ExternalFrames.MinimapProxy:SetShown(not SlickUI.DB.locked and visible)
		end
		if key == "Blizzard Alerts" and SlickUI.ExternalFrames.AlertAnchor then
			SlickUI.ExternalFrames.AlertAnchor:EnableMouse(not SlickUI.DB.locked and visible)
			SlickUI.ExternalFrames.AlertAnchor:SetShown(not SlickUI.DB.locked and visible)
		end
	end
end

function Movers:GetMoverOverlayNames()
	local names, seen = {}, {}
	local function Add(key)
		if not seen[key] then names[#names + 1], seen[key] = key, true end
	end
	for key in pairs(self.Registered) do Add(key) end
	if SlickUI.ExternalFrames then
		for key in pairs(SlickUI.ExternalFrames.Frames) do Add(key) end
		if SlickUI.ExternalFrames.MinimapProxy then Add("MinimapCluster") end
		if SlickUI.ExternalFrames.AlertAnchor then Add("Blizzard Alerts") end
	end
	table.sort(names)
	return names
end

function Movers:CreateVisibilityButton()
	if self.VisibilityButton then return self.VisibilityButton end
	local button = CreateFrame("Button", "SlickUIMoverVisibilityButton", UIParent, "UIPanelButtonTemplate")
	button:SetSize(160, 30)
	button:SetPoint("LEFT", self:CreateExitButton(), "RIGHT", 10, 0)
	RaiseEditModeControl(button)
	button:SetText("Mover Visibility")
	if button:GetFontString() then SlickUI.Theme:SetFont(button:GetFontString(), 10) end
	local panel = CreateFrame("Frame", "SlickUIMoverVisibilityPanel", UIParent, "BackdropTemplate")
	panel:SetSize(300, 430)
	panel:SetPoint("TOP", button, "BOTTOM", 0, -4)
	-- Mover overlays use FULLSCREEN_DIALOG. TOOLTIP keeps this control panel
	-- readable and clickable above every overlay, including external movers.
	panel:SetFrameStrata("TOOLTIP")
	panel:SetFrameLevel(1000)
	panel:SetClampedToScreen(true)
	panel:SetBackdrop({ bgFile = SlickUI.Theme.Texture, edgeFile = SlickUI.Theme.Texture, edgeSize = 2 })
	panel:SetBackdropColor(0.02, 0.025, 0.04, 0.98)
	panel:SetBackdropBorderColor(1, 0.67, 0.12, 1)
	local title = SlickUI.Theme:CreateText(panel, 11)
	title:SetPoint("TOPLEFT", 10, -9)
	title:SetText("Temporarily show movers")
	title:SetTextColor(1, 0.82, 0.12)

	local scroll = CreateFrame("ScrollFrame", nil, panel, "UIPanelScrollFrameTemplate")
	scroll:SetPoint("TOPLEFT", 7, -30)
	scroll:SetPoint("BOTTOMRIGHT", -28, 8)
	local child = CreateFrame("Frame", nil, scroll)
	child:SetSize(252, 1)
	scroll:SetScrollChild(child)
	panel.Rows = {}
	panel.ScrollFrame = scroll
	panel.ScrollChild = child
	local function ScrollVisibilityList(_, delta)
		local maximum = math.max(0, child:GetHeight() - scroll:GetHeight())
		scroll:SetVerticalScroll(math.max(0, math.min(maximum, scroll:GetVerticalScroll() - (delta * 24))))
	end
	for _, wheelFrame in ipairs({ panel, scroll, child }) do
		wheelFrame:EnableMouseWheel(true)
		wheelFrame:SetScript("OnMouseWheel", ScrollVisibilityList)
	end
	panel.ScrollWheel = ScrollVisibilityList
	panel:Hide()
	self.VisibilityPanel = panel
	button:SetScript("OnClick", function()
		if panel:IsShown() then
			panel:Hide()
		else
			Movers:RefreshVisibilityPanel()
			panel:Show()
		end
	end)
	button:Hide()
	self.VisibilityButton = button
	return button
end

function Movers:RefreshVisibilityPanel()
	local panel = self.VisibilityPanel
	if not panel then return end
	local names = self:GetMoverOverlayNames()
	for index, key in ipairs(names) do
		local row = panel.Rows[index]
		if not row then
			row = CreateFrame("Button", nil, panel.ScrollChild)
			row:SetSize(248, 20)
			row:SetPoint("TOPLEFT", 0, -((index - 1) * 20))
			local check = CreateFrame("CheckButton", nil, row, "UICheckButtonTemplate")
			check:SetSize(18, 18)
			check:SetPoint("LEFT", 1, 0)
			check:SetHitRectInsets(0, 0, 0, 0)
			local text = SlickUI.Theme:CreateText(row, 9)
			text:SetPoint("LEFT", check, "RIGHT", 3, 0)
			text:SetPoint("RIGHT", row, "RIGHT", -3, 0)
			text:SetJustifyH("LEFT")
			text:SetWordWrap(false)
			row.Check = check
			row.Text = text
			local function ToggleMover(self)
				local owner = self.MoverRow or self
				if not owner.MoverKey then return end
				local visible = not Movers:IsMoverOverlayVisible(owner.MoverKey)
				Movers:SetMoverOverlayVisible(owner.MoverKey, visible)
				owner.Check:SetChecked(visible)
			end
			row:SetScript("OnClick", function(self)
				ToggleMover(self)
			end)
			check.MoverRow = row
			check:SetScript("OnClick", ToggleMover)
			row:EnableMouseWheel(true)
			row:SetScript("OnMouseWheel", panel.ScrollWheel)
			check:EnableMouseWheel(true)
			check:SetScript("OnMouseWheel", panel.ScrollWheel)
			panel.Rows[index] = row
		end
		row.MoverKey = key
		row.Text:SetText(key)
		row.Check:SetChecked(self:IsMoverOverlayVisible(key))
		row:Show()
	end
	for index = #names + 1, #panel.Rows do panel.Rows[index]:Hide() end
	panel.ScrollChild:SetHeight(math.max(panel.ScrollFrame:GetHeight(), #names * 20))
	local maximum = math.max(0, panel.ScrollChild:GetHeight() - panel.ScrollFrame:GetHeight())
	panel.ScrollFrame:SetVerticalScroll(math.min(panel.ScrollFrame:GetVerticalScroll(), maximum))
end

function Movers:HideSettingsWindow()
	if SettingsPanel and SettingsPanel:IsShown() then
		if SettingsPanel.Close then
			SettingsPanel:Close(true)
		else
			SettingsPanel:Hide()
		end
	elseif InterfaceOptionsFrame and InterfaceOptionsFrame:IsShown() then
		HideUIPanel(InterfaceOptionsFrame)
	end
end

function Movers:SnapFrame(frame, key, droppedX, droppedY)
	local x, y = droppedX, droppedY
	if not x or not y then x, y = GetRelativeCenter(frame) end
	if not x or not y then return end
	local edit = SlickUI.DB.editMode
	local gridSize = math.max(8, edit.gridSize or 20)
	if edit.snapToGrid then
		x, y = RoundToStep(x, gridSize), RoundToStep(y, gridSize)
	end

	if edit.snapToFrames then
		local width, height = GetEffectiveSize(frame)
		local threshold = math.max(6, math.min(16, gridSize / 2))
		local bestXOffset, bestYOffset
		for otherKey, other in pairs(self.Registered) do
			if otherKey ~= key and other:IsShown() then
				local otherX, otherY = GetRelativeCenter(other)
				if otherX and otherY then
					local otherWidth, otherHeight = GetEffectiveSize(other)
					local left, right = x - width / 2, x + width / 2
					local top, bottom = y + height / 2, y - height / 2
					local otherLeft, otherRight = otherX - otherWidth / 2, otherX + otherWidth / 2
					local otherTop, otherBottom = otherY + otherHeight / 2, otherY - otherHeight / 2
					local xOffset = FindNearestOffset({ otherLeft - left, otherRight - right, otherRight - left, otherLeft - right, otherX - x }, threshold)
					local yOffset = FindNearestOffset({ otherTop - top, otherBottom - bottom, otherBottom - top, otherTop - bottom, otherY - y }, threshold)
					if xOffset ~= 0 and (not bestXOffset or math.abs(xOffset) < math.abs(bestXOffset)) then bestXOffset = xOffset end
					if yOffset ~= 0 and (not bestYOffset or math.abs(yOffset) < math.abs(bestYOffset)) then bestYOffset = yOffset end
				end
			end
		end
		x = x + (bestXOffset or 0)
		y = y + (bestYOffset or 0)
	end

	frame:ClearAllPoints()
	frame:SetPoint("CENTER", UIParent, "CENTER", x, y)
	-- Persist the exact anchor we just applied. Re-reading GetCenter() here can
	-- introduce another scale conversion after WoW replaces its temporary move
	-- anchor, which made scaled unit frames appear to keep moving after release.
	SavePosition(key, x, y)
end

local function BeginFrameDrag(frame)
	local x, y = GetRelativeCenter(frame)
	local cursorX, cursorY = GetCursorPosition()
	local parentScale = UIParent:GetEffectiveScale()
	if not x or not y or not cursorX or not cursorY or not parentScale or parentScale == 0 then return false end
	frame.SlickDragStartX = x
	frame.SlickDragStartY = y
	frame.SlickDragCursorX = cursorX / parentScale
	frame.SlickDragCursorY = cursorY / parentScale
	frame:StartMoving()
	return true
end

local function FinishFrameDrag(frame, key)
	frame:StopMovingOrSizing()
	if InCombatLockdown() then return end
	local cursorX, cursorY = GetCursorPosition()
	local parentScale = UIParent:GetEffectiveScale()
	local x, y
	if frame.SlickDragStartX and frame.SlickDragStartY and frame.SlickDragCursorX and frame.SlickDragCursorY
		and cursorX and cursorY and parentScale and parentScale ~= 0 then
		x = frame.SlickDragStartX + cursorX / parentScale - frame.SlickDragCursorX
		y = frame.SlickDragStartY + cursorY / parentScale - frame.SlickDragCursorY
	end
	frame.SlickDragStartX, frame.SlickDragStartY = nil, nil
	frame.SlickDragCursorX, frame.SlickDragCursorY = nil, nil
	Movers:SnapFrame(frame, key, x, y)
end

function Movers:Register(frame, key, label)
	self.Registered[key] = frame
	frame:SetMovable(true)
	frame:SetClampedToScreen(true)
	frame:EnableMouse(true)
	frame:RegisterForDrag("LeftButton")
	frame:SetScript("OnDragStart", function(self)
		if SlickUI.DB.locked or InCombatLockdown() then
			return
		end

		BeginFrameDrag(self)
	end)
	frame:SetScript("OnDragStop", function(self)
		FinishFrameDrag(self, key)
	end)

	local overlay = CreateFrame("Frame", nil, frame)
	overlay:SetAllPoints(frame)
	overlay:SetFrameLevel(frame:GetFrameLevel() + 20)
	overlay:EnableMouse(false)
	overlay:RegisterForDrag("LeftButton")
	overlay:SetScript("OnDragStart", function()
		if SlickUI.DB.locked or InCombatLockdown() then return end
		BeginFrameDrag(frame)
	end)
	overlay:SetScript("OnDragStop", function()
		FinishFrameDrag(frame, key)
	end)

	local shade = overlay:CreateTexture(nil, "BACKGROUND")
	shade:SetAllPoints()
	-- Keep the full frame draggable without layering a heavy translucent fill
	-- over every mover. Dense overlapping fills create substantial GPU overdraw.
	shade:SetColorTexture(1, 0.65, 0.08, 0.06)

	local text = SlickUI.Theme:CreateText(overlay, 12)
	text:SetPoint("CENTER")
	text:SetText(label)
	text:SetTextColor(unpack(SlickUI.Theme.Colors.goldText))

	frame.SlickMoverOverlay = overlay
	frame.SlickMoverKey = key
	ApplyPosition(frame, key)
	local conditionVisible = not frame.SlickMoverShouldShow or frame:SlickMoverShouldShow()
	local moverVisible = not SlickUI.DB.locked and conditionVisible and self:IsMoverOverlayVisible(key)
	overlay:EnableMouse(moverVisible)
	overlay:SetShown(moverVisible)
	if frame.SlickMoverOnly then
		frame:EnableMouse(not SlickUI.DB.locked)
		frame:SetFrameStrata(SlickUI.DB.locked and "LOW" or "DIALOG")
	end
end

function Movers:SetLocked(locked)
	if not SlickUI:IsAvailableOutOfCombat("Frame locking") then
		return
	end

	SlickUI.DB.locked = locked and true or false
	if SlickUI.ActionBars then SlickUI.ActionBars:SetSlickEditMode(not SlickUI.DB.locked) end
	-- Registered is the authoritative list of SlickUI-owned movable frames.
	-- UnitFrames only contains unit frames and a few legacy anchors, so using it
	-- here left registered windows such as the quest tracker, flight timer, and
	-- group controls locked while SlickUI Edit Mode was active.
	for _, frame in pairs(self.Registered) do
		if frame.SlickMoverOverlay then
			local conditionVisible = not frame.SlickMoverShouldShow or frame:SlickMoverShouldShow()
			local visible = not SlickUI.DB.locked and conditionVisible and self:IsMoverOverlayVisible(frame.SlickMoverKey)
			frame.SlickMoverOverlay:EnableMouse(visible)
			frame.SlickMoverOverlay:SetShown(visible)
		end
		if frame.SlickMoverOnly then
			frame:EnableMouse(not SlickUI.DB.locked)
			frame:SetFrameStrata(SlickUI.DB.locked and "LOW" or "DIALOG")
		end
	end
	if SlickUI.DB.locked then
		if SlickUI.ExternalFrames then SlickUI.ExternalFrames:EndEditMode() end
		self:CreateGrid():Hide()
		self:CreateExitButton():Hide()
		self:CreateBlizzardEditButton():Hide()
		self:CreateVisibilityButton():Hide()
		local gridSnapButton, frameSnapButton = self:CreateSnapButtons()
		gridSnapButton:Hide()
		frameSnapButton:Hide()
		if self.VisibilityPanel then self.VisibilityPanel:Hide() end
	else
		self:RefreshGrid()
		self:RefreshSnapButtons()
		self:CreateExitButton():Show()
		self:CreateBlizzardEditButton():Show()
		local gridSnapButton, frameSnapButton = self:CreateSnapButtons()
		gridSnapButton:Show()
		frameSnapButton:Show()
		self:CreateVisibilityButton():Show()
		self:HideSettingsWindow()
		if SlickUI.ExternalFrames then SlickUI.ExternalFrames:BeginEditMode() end
	end
end

function Movers:SetGridEnabled(enabled)
	SlickUI.DB.editMode.snapToGrid = enabled == true
	self:RefreshGrid()
	self:RefreshSnapButtons()
end

function Movers:SetFrameSnapEnabled(enabled)
	SlickUI.DB.editMode.snapToFrames = enabled == true
	self:RefreshSnapButtons()
end

function Movers:SetGridSize(size)
	SlickUI.DB.editMode.gridSize = math.max(8, math.min(100, tonumber(size) or 20))
	self:RefreshGrid()
end

function Movers:ResetPositions()
	if not SlickUI:IsAvailableOutOfCombat("Position reset") then
		return
	end
	if SlickUI.Options and SlickUI.Options.CreateResetBackup then
		SlickUI.Options:CreateResetBackup("Reset Frame Positions")
	end

	for key, defaults in pairs(SlickUI.Defaults.positions) do
		SlickUI.DB.positions[key] = { x = defaults.x, y = defaults.y }
		local frame = self.Registered[key]
		if frame then
			ApplyPosition(frame, key)
		end
	end
	if SlickUI.ExternalFrames then SlickUI.ExternalFrames:ResetPositions() end

	SlickUI:Print("Frame positions reset.")
end

function Movers:ApplySavedPositions()
	if InCombatLockdown() then return end
	for key, frame in pairs(self.Registered) do
		if frame and (SlickUI.DB.positions[key] or SlickUI.Defaults.positions[key]) then
			ApplyPosition(frame, key)
		end
	end
end

function Movers:Initialize()
	self:CreateGrid()
	self:CreateExitButton()
	self:CreateBlizzardEditButton()
	self:CreateSnapButtons()
	self:CreateVisibilityButton()
	self.EventFrame = CreateFrame("Frame")
	self.EventFrame:RegisterEvent("DISPLAY_SIZE_CHANGED")
	self.EventFrame:RegisterEvent("UI_SCALE_CHANGED")
	self.EventFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
	self.EventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
	self.EventFrame:SetScript("OnEvent", function(_, event)
		if event == "PLAYER_REGEN_DISABLED" and not SlickUI.DB.locked then
			SlickUI.DB.locked = true
			if SlickUI.ExternalFrames then SlickUI.ExternalFrames:EndEditMode() end
			Movers.Grid:Hide()
			Movers.ExitButton:Hide()
			Movers.BlizzardEditButton:Hide()
			Movers.GridSnapButton:Hide()
			Movers.FrameSnapButton:Hide()
			Movers.VisibilityButton:Hide()
			for _, frame in pairs(Movers.Registered) do
				if frame.SlickMoverOverlay then frame.SlickMoverOverlay:Hide() end
			end
		elseif event == "PLAYER_REGEN_ENABLED" then
			Movers:SetLocked(SlickUI.DB.locked)
		else
			Movers:RefreshGrid()
		end
	end)
end

SlickUI:RegisterModule("Movers", Movers)
