EditModeManagerOptionsCategory = {
	Frames = 1,
	Combat = 2,
	Misc = 3
};

local maxLayoutsCopyErrorText = HUD_EDIT_MODE_ERROR_COPY_MAX_LAYOUTS:format(Constants.EditModeConsts.EditModeMaxLayoutsPerType, Constants.EditModeConsts.EditModeMaxLayoutsPerType);
local characterLayoutHeaderText = GetClassColoredTextForUnit("player", HUD_EDIT_MODE_CHARACTER_LAYOUTS_HEADER:format(UnitNameUnmodified("player")));

EditModeManagerFrameMixin = {};

function EditModeManagerFrameMixin:OnLoad()
	self.registeredSystemFrames = {};
	self.modernSystemMap = EditModePresetLayoutManager:GetModernSystemMap();
	self.modernSystems = EditModePresetLayoutManager:GetModernSystems();

	self.LayoutDropdown:SetWidth(220);

	self.LayoutLabel:ClearAllPoints();
	self.LayoutLabel:SetPoint("BOTTOMLEFT", self.LayoutDropdown, "TOPLEFT", 0, 0);
	self.LayoutLabel:SetText(HUD_EDIT_MODE_LAYOUT);

	local function onShowGridCheckboxChecked(isChecked, isUserInput)
		self:SetGridShown(isChecked, isUserInput);
	end
	self.ShowGridCheckButton:SetCallback(onShowGridCheckboxChecked);

	local function onEnableSnapCheckboxChecked(isChecked, isUserInput)
		self:SetEnableSnap(isChecked, isUserInput);
	end
	self.EnableSnapCheckButton:SetCallback(onEnableSnapCheckboxChecked);

	local function onEnableAdvancedOptionsCheckboxChecked(isChecked, isUserInput)
		self:SetEnableAdvancedOptions(isChecked, isUserInput);
	end
	self.EnableAdvancedOptionsCheckButton:SetCallback(onEnableAdvancedOptionsCheckboxChecked);

	local function OnCloseCallback()
		if self:HasActiveChanges() then
			self:ShowRevertWarningDialog();
		else
			HideUIPanel(self);
		end
	end

	self.onCloseCallback = OnCloseCallback;

	self.SaveChangesButton:SetOnClickHandler(GenerateClosure(self.SaveLayoutChanges, self));
	self.RevertAllChangesButton:SetOnClickHandler(GenerateClosure(self.RevertAllChanges, self));

	self:RegisterEvent("EDIT_MODE_LAYOUTS_UPDATED");
	self:RegisterEvent("DISPLAY_SIZE_CHANGED");
	self:RegisterEvent("UI_SCALE_CHANGED");
	self:RegisterUnitEvent("PLAYER_SPECIALIZATION_CHANGED", "player");

	self.FramesBlockingEditMode = {};

	self:SetupEditModeDialogs();
end

function EditModeManagerFrameMixin:OnDragStart()
	self:StartMoving();
end

function EditModeManagerFrameMixin:OnDragStop()
	self:StopMovingOrSizing();
end

function EditModeManagerFrameMixin:OnUpdate()
	self:InvokeOnAnyEditModeSystemAnchorChanged();
	self:RefreshSnapPreviewLines();
end

local function callOnEditModeEnter(index, systemFrame)
	systemFrame:OnEditModeEnter();
end

function EditModeManagerFrameMixin:ShowSystemSelections()
	secureexecuterange(self.registeredSystemFrames, callOnEditModeEnter);
end

function EditModeManagerFrameMixin:EnterEditMode()
	self.editModeActive = true;
	C_UnitAuras.SwitchAuraDataProvider();
	self:ClearActiveChangesFlags();
	self:UpdateDropdownOptions();
	self:ShowSystemSelections();
	self.AccountSettings:OnEditModeEnter();
	EventRegistry:TriggerEvent("EditMode.Enter");
end

local function callOnEditModeExit(index, systemFrame)
	systemFrame:OnEditModeExit();
end

function EditModeManagerFrameMixin:HideSystemSelections()
	secureexecuterange(self.registeredSystemFrames, callOnEditModeExit);
end

function EditModeManagerFrameMixin:ExitEditMode()
	self.editModeActive = false;
	C_UnitAuras.ResetAuraDataProvider();
	self:RevertAllChanges();
	self:HideSystemSelections();
	self.AccountSettings:OnEditModeExit();
	self:InvokeOnAnyEditModeSystemAnchorChanged(true);
	C_EditMode.OnEditModeExit();
	EventRegistry:TriggerEvent("EditMode.Exit");
	PlaySound(SOUNDKIT.IG_MAINMENU_QUIT);
end

function EditModeManagerFrameMixin:OnShow()
	if not self:IsEditModeLocked() then
		self:EnterEditMode();
	elseif self:IsEditModeInLockState("hideSelections")  then
		self:ShowSystemSelections();
		self.AccountSettings:OnEditModeEnter();
	end

	self:ClearEditModeLockState();
	self:Layout();
end

function EditModeManagerFrameMixin:OnHide()
	if not self:IsEditModeLocked() then
		self:ExitEditMode();
	elseif self:IsEditModeInLockState("hideSelections") then
		self:HideSystemSelections();
		self.AccountSettings:OnEditModeExit();
	end
end

function EditModeManagerFrameMixin:IsEditModeActive()
	return self.editModeActive;
end

function EditModeManagerFrameMixin:CheckHideAndLockEditMode(lockState)
	if self:IsEditModeActive() then
		self:ClearSelectedSystem();
		self:SetEditModeLockState(lockState or "hideSelections");
		HideUIPanel(self);
	end
end

function EditModeManagerFrameMixin:ShowIfActive()
	if self:IsEditModeActive() then
		ShowUIPanel(self);
		return true;
	end

	return false;
end

function EditModeManagerFrameMixin:CreateEnterEditModeMenuButton(description, buttonText)
	local button = description:CreateButton(buttonText, function() ShowUIPanel(self); end);
	button:SetEnabled(self:CanEnterEditMode());
	return button;
end

function EditModeManagerFrameMixin:SetEditModeLockState(lockState)
	self.editModeLockState = lockState;
end

function EditModeManagerFrameMixin:IsEditModeInLockState(lockState)
	return self.editModeLockState == lockState;
end

function EditModeManagerFrameMixin:ClearEditModeLockState()
	self.editModeLockState = nil;
end

function EditModeManagerFrameMixin:IsEditModeLocked()
	return self.editModeLockState ~= nil;
end

function EditModeManagerFrameMixin:OnEvent(event, ...)
	if event == "EDIT_MODE_LAYOUTS_UPDATED" then
		local layoutInfo, reconcileLayouts = ...;
		self:UpdateLayoutInfo(layoutInfo, reconcileLayouts);
		self:UpdateTopFramePositions();
		self:InitializeAccountSettings();
	elseif event == "PLAYER_SPECIALIZATION_CHANGED" then
		local layoutInfo = C_EditMode.GetLayouts();
		local activeLayoutChanged = (layoutInfo.activeLayout ~= self.layoutInfo.activeLayout);
		self:UpdateLayoutInfo(layoutInfo);
		if activeLayoutChanged then
			self:NotifyChatOfLayoutChange();
		end
	elseif event == "DISPLAY_SIZE_CHANGED" or event == "UI_SCALE_CHANGED" then
		self:UpdateRightActionBarPositions();
		EditModeMagnetismManager:UpdateUIParentPoints();
	end
end

function EditModeManagerFrameMixin:IsInitialized()
	return self.layoutInfo ~= nil;
end

function EditModeManagerFrameMixin:RegisterSystemFrame(systemFrame)
	table.insert(self.registeredSystemFrames, systemFrame);
end

function EditModeManagerFrameMixin:GetRegisteredSystemFrame(system, systemIndex)
	local foundSystem = nil;
	local function findSystem(index, systemFrame)
		if not foundSystem and systemFrame.system == system and systemFrame.systemIndex == systemIndex then
			foundSystem = systemFrame;
		end
	end
	secureexecuterange(self.registeredSystemFrames, findSystem);
	return foundSystem;
end

local function AreAnchorsEqual(anchorInfo, otherAnchorInfo)
	local anchorEpsilon = 0.1;
	if anchorInfo and otherAnchorInfo then
		return anchorInfo.point == otherAnchorInfo.point
		and anchorInfo.relativeTo == otherAnchorInfo.relativeTo
		and anchorInfo.relativePoint == otherAnchorInfo.relativePoint
		and ApproximatelyEqual(anchorInfo.offsetX, otherAnchorInfo.offsetX, anchorEpsilon)
		and ApproximatelyEqual(anchorInfo.offsetY, otherAnchorInfo.offsetY, anchorEpsilon)
	end

	return anchorInfo == otherAnchorInfo;
end

local function CopyAnchorInfo(anchorInfo, otherAnchorInfo)
	if anchorInfo and otherAnchorInfo then
		anchorInfo.point = otherAnchorInfo.point;
		anchorInfo.relativeTo = otherAnchorInfo.relativeTo;
		anchorInfo.relativePoint = otherAnchorInfo.relativePoint;
		anchorInfo.offsetX = otherAnchorInfo.offsetX;
		anchorInfo.offsetY = otherAnchorInfo.offsetY;
	end
end

local function ConvertToAnchorInfo(point, relativeTo, relativePoint, offsetX, offsetY)
	if point then
		local anchorInfo = {};
		anchorInfo.point = point;
		anchorInfo.relativeTo = relativeTo and relativeTo:GetName() or "UIParent";
		anchorInfo.relativePoint = relativePoint;
		anchorInfo.offsetX = offsetX;
		anchorInfo.offsetY = offsetY;
		return anchorInfo;
	end

	return nil;
end

function EditModeManagerFrameMixin:SetHasActiveChanges(hasActiveChanges)
	-- Clear taint off of the value passed in
	if hasActiveChanges then
		self.hasActiveChanges = true;
	else
		self.hasActiveChanges = false;
	end
	self.SaveChangesButton:SetEnabled(hasActiveChanges);
	self.RevertAllChangesButton:SetEnabled(hasActiveChanges);
end

function EditModeManagerFrameMixin:CheckForSystemActiveChanges()
	local hasActiveChanges = false;
	local function checkIfSystemHasActiveChanges(index, systemFrame)
		if not hasActiveChanges and systemFrame:HasActiveChanges() then
			hasActiveChanges = true;
		end
	end
	secureexecuterange(self.registeredSystemFrames, checkIfSystemHasActiveChanges);

	self:SetHasActiveChanges(hasActiveChanges);
end

function EditModeManagerFrameMixin:HasActiveChanges()
	return self.hasActiveChanges;
end

function EditModeManagerFrameMixin:SetToLayoutAnchor(systemFrame, forceOffsetX, forceOffsetY)
	forceOffsetX = forceOffsetX or 0;
	forceOffsetY = forceOffsetY or 0;

	local anchorInfo;
	local layoutInfo = self:GetActiveLayoutInfo();
	local isPresetLayout = layoutInfo.layoutType == Enum.EditModeLayoutType.Preset;

	if (isPresetLayout) then
		anchorInfo = EditModePresetLayoutManager:GetPresetLayoutSystemAnchorInfo(layoutInfo.layoutIndex, systemFrame.system, systemFrame.systemIndex);
	else
		anchorInfo = EditModeManagerFrame:GetDefaultAnchor(systemFrame); -- GetDefaultAnchor also handles if we have an Override Layout.
	end

	systemFrame:SetPoint(anchorInfo.point, anchorInfo.relativeTo, anchorInfo.relativePoint, anchorInfo.offsetX + forceOffsetX, anchorInfo.offsetY + forceOffsetY);
end

function EditModeManagerFrameMixin:UpdateSystemAnchorInfo(systemFrame)
	local systemInfo = self:GetActiveLayoutSystemInfo(systemFrame.system, systemFrame.systemIndex);
	if systemInfo then
		local anchorInfoChanged = false;

		local point, relativeTo, relativePoint, offsetX, offsetY = systemFrame:GetPoint(1);

		-- If we don't have a relativeTo then we are gonna set our relativeTo to be UIParent
		if not relativeTo then
			relativeTo = UIParent;

			-- When setting our relativeTo to UIParent it's possible for our y position to change slightly depending on UIParent's size from stuff like debug menus
			-- To account for this set out position and then track the change in our top and adjust for that
			local originalSystemFrameTop = systemFrame:GetTop();
			systemFrame:SetPoint(point, relativeTo, relativePoint, offsetX, offsetY);

			offsetY = offsetY + originalSystemFrameTop - systemFrame:GetTop();
			systemFrame:SetPoint(point, relativeTo, relativePoint, offsetX, offsetY);
		end

		-- Undo offset changes due to scale so we're always working as if we're at 1.0 scale
		local frameScale = systemFrame:GetScale();
		offsetX = offsetX * frameScale;
		offsetY = offsetY * frameScale;

		local newAnchorInfo = ConvertToAnchorInfo(point, relativeTo, relativePoint, offsetX, offsetY);
		if not AreAnchorsEqual(systemInfo.anchorInfo, newAnchorInfo) then
			CopyAnchorInfo(systemInfo.anchorInfo, newAnchorInfo);
			anchorInfoChanged = true;
		end

		point, relativeTo, relativePoint, offsetX, offsetY = systemFrame:GetPoint(2);

		-- Undo offset changes due to scale so we're always working as if we're at 1.0 scale
		-- May not always have a second point so nil check first
		if point ~= nil then
			offsetX = offsetX * frameScale;
			offsetY = offsetY * frameScale;
		end

		newAnchorInfo = ConvertToAnchorInfo(point, relativeTo, relativePoint, offsetX, offsetY);
		if not AreAnchorsEqual(systemInfo.anchorInfo2, newAnchorInfo) then
			CopyAnchorInfo(systemInfo.anchorInfo2, newAnchorInfo);
			anchorInfoChanged = true;
		end

		if anchorInfoChanged then
			systemInfo.isInDefaultPosition = false;
		end

		return anchorInfoChanged;
	end

	return false;
end

function EditModeManagerFrameMixin:OnSystemPositionChange(systemFrame)
	if self:UpdateSystemAnchorInfo(systemFrame) then
		systemFrame:SetHasActiveChanges(true);

		self:UpdateActionBarLayout(systemFrame);

		if systemFrame.isBottomManagedFrame or systemFrame.isRightManagedFrame then
			UIParent_ManageFramePositions();
		end

		EditModeSystemSettingsDialog:UpdateDialog(systemFrame);
	end

	self:OnEditModeSystemAnchorChanged();
end

function EditModeManagerFrameMixin:MirrorSetting(system, systemIndex, setting, value)
	local mirroredSettings = EditModeSettingDisplayInfoManager:GetMirroredSettings(system, systemIndex, setting);
	if mirroredSettings then
		for _, mirroredSettingInfo in ipairs(mirroredSettings) do
			local systemFrame = self:GetRegisteredSystemFrame(mirroredSettingInfo.system, mirroredSettingInfo.systemIndex);
			if systemFrame then
				systemFrame:UpdateSystemSettingValue(setting, value);
			end
		end
	end
end

function EditModeManagerFrameMixin:OnSystemSettingChange(systemFrame, changedSetting, newValue)
	local systemInfo = self:GetActiveLayoutSystemInfo(systemFrame.system, systemFrame.systemIndex);
	if systemInfo then
		systemFrame:UpdateSystemSettingValue(changedSetting, newValue);
	end
end

function EditModeManagerFrameMixin:RevertSystemChanges(systemFrame)
	local activeLayoutInfo = self:GetActiveLayoutInfo();
	if activeLayoutInfo then
		for index, systemInfo in ipairs(activeLayoutInfo.systems) do
			if systemInfo.system == systemFrame.system and systemInfo.systemIndex == systemFrame.systemIndex then
				activeLayoutInfo.systems[index] = systemFrame.savedSystemInfo;

				systemFrame:BreakSnappedFrames();
				systemFrame:UpdateSystem(systemFrame.savedSystemInfo);
				self:CheckForSystemActiveChanges();
				return;
			end
		end
	end
end

function EditModeManagerFrameMixin:GetSettingValue(system, systemIndex, setting, useRawValue)
	local systemFrame = self:GetRegisteredSystemFrame(system, systemIndex);
	if systemFrame then
		return systemFrame:GetSettingValue(setting, useRawValue)
	end
end

function EditModeManagerFrameMixin:GetSettingValueBool(system, systemIndex, setting, useRawValue)
	local systemFrame = self:GetRegisteredSystemFrame(system, systemIndex);
	if systemFrame then
		return systemFrame:GetSettingValueBool(setting, useRawValue)
	end
end

function EditModeManagerFrameMixin:DoesSettingValueEqual(system, systemIndex, setting, value)
	local systemFrame = self:GetRegisteredSystemFrame(system, systemIndex);
	if systemFrame then
		return systemFrame:DoesSettingValueEqual(setting, value);
	end
end

function EditModeManagerFrameMixin:DoesSettingDisplayValueEqual(system, systemIndex, setting, value)
	local systemFrame = self:GetRegisteredSystemFrame(system, systemIndex);
	if systemFrame then
		return systemFrame:DoesSettingDisplayValueEqual(setting, value);
	end
end

function EditModeManagerFrameMixin:ArePartyFramesForcedShown()
	return self:IsEditModeActive() and self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowPartyFrames);
end

function EditModeManagerFrameMixin:GetNumArenaFramesForcedShown()
	if self:IsEditModeActive() and self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowArenaFrames) then
		local viewArenaSize = self:GetSettingValue(Enum.EditModeSystem.UnitFrame, Enum.EditModeUnitFrameSystemIndices.Arena, Enum.EditModeUnitFrameSetting.ViewArenaSize);
		if viewArenaSize == Enum.ViewArenaSize.Two then
			return 2;
		else
			return 3;
		end
	end

	return 0;
end

function EditModeManagerFrameMixin:UseRaidStylePartyFrames()
	return self:GetSettingValueBool(Enum.EditModeSystem.UnitFrame, Enum.EditModeUnitFrameSystemIndices.Party, Enum.EditModeUnitFrameSetting.UseRaidStylePartyFrames);
end

function EditModeManagerFrameMixin:ShouldShowPartyFrameBackground()
	return self:GetSettingValueBool(Enum.EditModeSystem.UnitFrame, Enum.EditModeUnitFrameSystemIndices.Party, Enum.EditModeUnitFrameSetting.ShowPartyFrameBackground);
end

function EditModeManagerFrameMixin:UpdateRaidContainerFlow()
	local maxPerLine, orientation;

	local raidGroupDisplayType = self:GetSettingValue(Enum.EditModeSystem.UnitFrame, Enum.EditModeUnitFrameSystemIndices.Raid, Enum.EditModeUnitFrameSetting.RaidGroupDisplayType);
	if raidGroupDisplayType == Enum.RaidGroupDisplayType.SeparateGroupsVertical then
		orientation = "vertical";
		CompactRaidFrameContainer:ApplyToFrames("group", CompactRaidGroup_UpdateBorder);
		maxPerLine = 5;
	elseif raidGroupDisplayType == Enum.RaidGroupDisplayType.SeparateGroupsHorizontal then
		orientation = "horizontal";
		CompactRaidFrameContainer:ApplyToFrames("group", CompactRaidGroup_UpdateBorder);
		maxPerLine = 5;
	elseif raidGroupDisplayType == Enum.RaidGroupDisplayType.CombineGroupsVertical then
		orientation = "vertical";
		maxPerLine = self:GetSettingValue(Enum.EditModeSystem.UnitFrame, Enum.EditModeUnitFrameSystemIndices.Raid, Enum.EditModeUnitFrameSetting.RowSize);
	else
		orientation = "horizontal";
		maxPerLine = self:GetSettingValue(Enum.EditModeSystem.UnitFrame, Enum.EditModeUnitFrameSystemIndices.Raid, Enum.EditModeUnitFrameSetting.RowSize);
	end

	-- Setting CompactRaidFrameContainer to a really big size because the flow container bases its calculations off the size of the container itself
	-- The layout call below shrinks the container back down to fit the actual contents after they have been anchored
	FlowContainer_SetOrientation(CompactRaidFrameContainer, orientation);
	FlowContainer_SetMaxPerLine(CompactRaidFrameContainer, maxPerLine);
	CompactRaidFrameContainer:TryUpdate();
end

function EditModeManagerFrameMixin:AreRaidFramesForcedShown()
	return self:IsEditModeActive() and self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowRaidFrames);
end

function EditModeManagerFrameMixin:GetNumRaidGroupsForcedShown()
	if self:AreRaidFramesForcedShown() then
		local viewRaidSize = self:GetSettingValue(Enum.EditModeSystem.UnitFrame, Enum.EditModeUnitFrameSystemIndices.Raid, Enum.EditModeUnitFrameSetting.ViewRaidSize);
		if viewRaidSize == Enum.ViewRaidSize.Ten then
			return 2;
		elseif viewRaidSize == Enum.ViewRaidSize.TwentyFive then
			return 5;
		elseif viewRaidSize == Enum.ViewRaidSize.Forty then
			return 8;
		else
			return 0;
		end
	else
		return 0;
	end
end

function EditModeManagerFrameMixin:GetNumRaidMembersForcedShown()
	if self:AreRaidFramesForcedShown() then
		local viewRaidSize = self:GetSettingValue(Enum.EditModeSystem.UnitFrame, Enum.EditModeUnitFrameSystemIndices.Raid, Enum.EditModeUnitFrameSetting.ViewRaidSize);
		if viewRaidSize == Enum.ViewRaidSize.Ten then
			return 10;
		elseif viewRaidSize == Enum.ViewRaidSize.TwentyFive then
			return 25;
		elseif viewRaidSize == Enum.ViewRaidSize.Forty then
			return 40;
		else
			return 0;
		end
	else
		return 0;
	end
end

-- NOTE: For RaidFrame sizes, the caller provides both a default and lower bound in the form of the default parameter.
-- This is to avoid coupling between the CUF and EditMode code for constants that are not shared yet and used in several other layout calculations.
function EditModeManagerFrameMixin:GetRaidFrameWidth(systemIndex, default)
	local raidFrameWidth = self:GetSettingValue(Enum.EditModeSystem.UnitFrame, systemIndex, Enum.EditModeUnitFrameSetting.FrameWidth);
	return (raidFrameWidth and raidFrameWidth >= default) and raidFrameWidth or default;
end

function EditModeManagerFrameMixin:GetRaidFrameHeight(systemIndex, default)
	local raidFrameHeight = self:GetSettingValue(Enum.EditModeSystem.UnitFrame, systemIndex, Enum.EditModeUnitFrameSetting.FrameHeight);
	return (raidFrameHeight and raidFrameHeight >= default) and raidFrameHeight or default;
end

function EditModeManagerFrameMixin:GetRaidFrameAuraOrganizationType(systemIndex)
	local auraOrganizationType = self:GetSettingValue(Enum.EditModeSystem.UnitFrame, systemIndex, Enum.EditModeUnitFrameSetting.AuraOrganizationType);
	return (auraOrganizationType and auraOrganizationType > 0) and auraOrganizationType or Enum.RaidAuraOrganizationType.Legacy;
end

function EditModeManagerFrameMixin:GetRaidFrameIconScale(systemIndex, default, settingEnum)
	-- The iconSize setting is not a percentage even though it represents one; convert it here, note that default is expected to be in percentage form
	-- Because the min/max are setup in EditModeSettingDisplayInfo they're not checked here and the caller needs to verify ranges.
	local iconSize = self:GetSettingValue(Enum.EditModeSystem.UnitFrame, systemIndex, settingEnum);
	return (iconSize and iconSize > 0) and (iconSize / 100) or default;
end

function EditModeManagerFrameMixin:ShouldRaidFrameUseHorizontalRaidGroups(systemIndex)
	if systemIndex == Enum.EditModeUnitFrameSystemIndices.Party then
		return self:GetSettingValueBool(Enum.EditModeSystem.UnitFrame, systemIndex, Enum.EditModeUnitFrameSetting.UseHorizontalGroups);
	elseif systemIndex == Enum.EditModeUnitFrameSystemIndices.Raid then
		return self:DoesSettingValueEqual(Enum.EditModeSystem.UnitFrame, systemIndex, Enum.EditModeUnitFrameSetting.RaidGroupDisplayType, Enum.RaidGroupDisplayType.SeparateGroupsHorizontal);
	end

	return false;
end

function EditModeManagerFrameMixin:ShouldRaidFrameDisplayBorder(systemIndex)
	return self:GetSettingValueBool(Enum.EditModeSystem.UnitFrame, systemIndex, Enum.EditModeUnitFrameSetting.DisplayBorder);
end

function EditModeManagerFrameMixin:ShouldRaidFrameShowSeparateGroups()
	local raidGroupDisplayType = self:GetSettingValue(Enum.EditModeSystem.UnitFrame, Enum.EditModeUnitFrameSystemIndices.Raid, Enum.EditModeUnitFrameSetting.RaidGroupDisplayType);
	return (raidGroupDisplayType == Enum.RaidGroupDisplayType.SeparateGroupsVertical) or (raidGroupDisplayType == Enum.RaidGroupDisplayType.SeparateGroupsHorizontal);
end

function EditModeManagerFrameMixin:UpdateActionBarLayout(systemFrame)
	if EditModeUtil:IsBottomAnchoredActionBar(systemFrame) then
		self:UpdateBottomActionBarPositions();
	elseif EditModeUtil:IsRightAnchoredActionBar(systemFrame) or systemFrame == MinimapCluster then
		self:UpdateRightActionBarPositions();
	end
end

function EditModeManagerFrameMixin:UpdateActionBarPositions()
	self:UpdateBottomActionBarPositions();
	self:UpdateRightActionBarPositions();
end

function EditModeManagerFrameMixin:UpdateRightActionBarPositions()
	if not self:IsInitialized() or self.layoutApplyInProgress then
		return;
	end

	local barsToUpdate = self:GetRightActionBars();

	-- Determine new scale
	local topLimit = self:GetRightActionBarTopLimit();
	local bottomLimit = self:GetRightActionBarBottomLimit();
	local availableSpace = topLimit - bottomLimit;
	local multiBarHeight = MultiBarRight:GetHeight();
	local newScale = multiBarHeight > availableSpace and availableSpace / multiBarHeight or 1;

	-- Update bars
	local offsetX = RIGHT_ACTION_BAR_DEFAULT_OFFSET_X;
	local offsetY = RIGHT_ACTION_BAR_DEFAULT_OFFSET_Y;
	local leftMostBar = nil;
	for index, bar in ipairs(barsToUpdate) do
		if bar and bar:IsShown() then
			local isInDefaultPosition = bar:IsInDefaultPosition();
			bar:SetScale(isInDefaultPosition and newScale or 1);

			if isInDefaultPosition then
				local leftMostBarWidth = leftMostBar and -leftMostBar:GetWidth() - 5 + RIGHT_ACTION_BAR_DEFAULT_PADDING_X or 0;
				offsetX = offsetX + leftMostBarWidth;

				bar:ClearAllPoints();
				bar:SetPoint("RIGHT", UIParent, "RIGHT", offsetX, offsetY);

				-- Bar position changed so we should update our flyout direction
				if bar.UpdateSpellFlyoutDirection then
					bar:UpdateSpellFlyoutDirection();
				end

				leftMostBar = bar;
			end
		end
	end

	UIParent_ManageFramePositions();
end

function EditModeManagerFrameMixin:UpdateBottomActionBarPositions()
	if not self:IsInitialized() or self.layoutApplyInProgress then
		return;
	end

	local barsToUpdate = self:GetBottomActionBars();

	local offsetX = 0;
	local offsetY = MAIN_ACTION_BAR_DEFAULT_OFFSET_Y;

	if OverrideActionBar and OverrideActionBar:IsShown() then
		local xpBarHeight = OverrideActionBar.xpBar:IsShown() and OverrideActionBar.xpBar:GetHeight() or 0;
		offsetY = OverrideActionBar:GetHeight() + xpBarHeight + 10;
	end

	local topMostBar = nil;

	for index, bar in ipairs(barsToUpdate) do
		if bar and bar:IsShown() and bar:IsInDefaultPosition() then
			bar:ClearAllPoints();

			if (bar.skipAutomaticPositioning) then
				self:SetToLayoutAnchor(bar);
			else
				if not topMostBar then
					offsetX = -bar:GetWidth() / 2;
				end

				local topBarHeight = topMostBar and topMostBar:GetHeight() + BOTTOM_ACTION_BARS_SPACER_Y or 0;
				offsetY = offsetY + topBarHeight;

				bar:ClearAllPoints();
				bar:SetPoint("BOTTOMLEFT", UIParent, "BOTTOM", offsetX, offsetY);

				topMostBar = bar;
			end

			-- Bar position changed so we should update our flyout direction
			if bar.UpdateSpellFlyoutDirection then
				bar:UpdateSpellFlyoutDirection();
			end
		end
	end

	UIParent_ManageFramePositions();
end

function EditModeManagerFrameMixin:UpdateTopFramePositions()
	-- Currently only needed for GMTicketFrame, but this can be used for managing any future top frames
	UIParent_UpdateTopFramePositions();
end

function EditModeManagerFrameMixin:SelectSystem(selectFrame)
	if not self:IsEditModeLocked() then
		local function selectMatchingSystem(index, systemFrame)
			if systemFrame == selectFrame then
				systemFrame:SelectSystem();
			else
				-- Only highlight a system if it was already highlighted
				if systemFrame.isHighlighted then
					systemFrame:HighlightSystem();
				end
			end
		end
		secureexecuterange(self.registeredSystemFrames, selectMatchingSystem);
	end
end

local function clearSelectedSystem(index, systemFrame)
	-- Only highlight a system if it was already highlighted
	if systemFrame.isHighlighted then
		systemFrame:HighlightSystem();
	end
end

local function ClearSelectedSystems(self)
	secureexecuterange(self.registeredSystemFrames, clearSelectedSystem);
	EditModeSystemSettingsDialog:Hide();
end

local ClearSelectedSystemsDelegate = CreateSecureDelegate(ClearSelectedSystems);

function EditModeManagerFrameMixin:ClearSelectedSystem()
	if issecure() or not InCombatLockdown() then
		ClearSelectedSystemsDelegate(self);
	else
		ClearSelectedSystems(self);
	end
end

function EditModeManagerFrameMixin:NotifyChatOfLayoutChange()
	local newActiveLayoutName = self:GetActiveLayoutInfo().layoutName;
	local systemChatInfo = ChatTypeInfo["SYSTEM"];
	DEFAULT_CHAT_FRAME:AddMessage(HUD_EDIT_MODE_LAYOUT_APPLIED:format(newActiveLayoutName), systemChatInfo.r, systemChatInfo.g, systemChatInfo.b, systemChatInfo.id);
end

-- This method handles removing any out-dated systems/settings from a saved layout data table
function EditModeManagerFrameMixin:RemoveOldSystemsAndSettings(layoutInfo)
	local removedSomething = false;
	local keepSystems = {};

	for _, layoutSystemInfo in ipairs(layoutInfo.systems) do
		local keepSystem;
		if layoutSystemInfo.systemIndex then
			keepSystem = self.modernSystemMap[layoutSystemInfo.system] and self.modernSystemMap[layoutSystemInfo.system][layoutSystemInfo.systemIndex];
		else
			keepSystem = self.modernSystemMap[layoutSystemInfo.system];
		end

		if keepSystem then
			-- This system still exists, so we want to add it to keepSystems, but first we want to check if any settings within it were removed
			local keepSettings = {};
			local removedSetting = false;
			for _, settingInfo in ipairs(layoutSystemInfo.settings) do
				if keepSystem.settings[settingInfo.setting] then
					-- This setting still exists, so we want to add it to keepSettings
					table.insert(keepSettings, settingInfo);
				else
					-- This setting no longer exists, so don't add it to keepSystems
					removedSomething = true;
					removedSetting = true;
				end
			end

			if removedSetting then
				-- A setting was removed, so replace the settings table with keepSettings
				layoutSystemInfo.settings = keepSettings;
			end

			-- Add layoutSystemInfo to keepSystems;
			table.insert(keepSystems, layoutSystemInfo);
		else
			-- This system no longer exists, so don't add it to keepSystems
			removedSomething = true;
		end
	end

	if removedSomething then
		-- Something was removed, so replace the systems table with keepSystems
		layoutInfo.systems = keepSystems;
	end

	return removedSomething;
end

-- This method handles adding any missing systems/settings to a saved layout data table
function EditModeManagerFrameMixin:AddNewSystemsAndSettings(layoutInfo)
	local addedSomething = false;

	-- Create a system/setting map to allow for efficient checking of each system & setting below
	local layoutSystemMap = {};
	for _, layoutSystemInfo in ipairs(layoutInfo.systems) do
		local settingMap = EditModeUtil:GetSettingMapFromSettings(layoutSystemInfo.settings);

		if layoutSystemInfo.systemIndex then
			if not layoutSystemMap[layoutSystemInfo.system] then
				layoutSystemMap[layoutSystemInfo.system] = {};
			end
			layoutSystemMap[layoutSystemInfo.system][layoutSystemInfo.systemIndex] = { settingMap = settingMap, settings = layoutSystemInfo.settings };
		else
			layoutSystemMap[layoutSystemInfo.system] = { settingMap = settingMap, settings = layoutSystemInfo.settings };
		end
	end

	-- Loop through all of the modern systems/setting and add any that don't exist in the saved layout data table
	for _, systemInfo in ipairs(self.modernSystems) do
		local existingSystem;
		if systemInfo.systemIndex then
			existingSystem = layoutSystemMap[systemInfo.system] and layoutSystemMap[systemInfo.system][systemInfo.systemIndex];
		else
			existingSystem = layoutSystemMap[systemInfo.system];
		end

		if not existingSystem then
			-- This system was newly added since this layout was saved so add it
			table.insert(layoutInfo.systems, CopyTable(systemInfo));
			addedSomething = true;
		else
			-- This system already existed, but we still need to check if any settings were added to it
			for _, settingInfo in ipairs(systemInfo.settings) do
				if not existingSystem.settingMap[settingInfo.setting] then
					-- This setting was newly added since this layout was saved so add it
					table.insert(existingSystem.settings, CopyTable(settingInfo));
					addedSomething = true;
				end
			end
		end
	end

	return addedSomething;
end

function EditModeManagerFrameMixin:ReconcileWithModern(layoutInfo)
	local removedSomething = self:RemoveOldSystemsAndSettings(layoutInfo);
	local addedSomething = self:AddNewSystemsAndSettings(layoutInfo);
	return removedSomething or addedSomething;
end

-- Sometimes new systems/settings may be added to (or removed from) EditMode. When that happens the saved layout data be will out of date
-- This method handles adding any missing systems/settings and removing any existing systems/settings from the saved layout data
function EditModeManagerFrameMixin:ReconcileLayoutsWithModern()
	local somethingChanged = false;
	for _, layoutInfo in ipairs(self.layoutInfo.layouts) do
		if self:ReconcileWithModern(layoutInfo) then
			somethingChanged = true;
		end
	end

	if somethingChanged then
		-- Something changed, so we need to send the updated edit mode info up to be saved on logout
		C_EditMode.SaveLayouts(self.layoutInfo);
	end
end

function EditModeManagerFrameMixin:UpdateAccountSettingMap()
	self.accountSettingMap = EditModeUtil:GetSettingMapFromSettings(self.accountSettings);
end

function EditModeManagerFrameMixin:GetAccountSettingValue(setting)
	return self.accountSettingMap[setting].value;
end

function EditModeManagerFrameMixin:GetAccountSettingValueBool(setting)
	return self:GetAccountSettingValue(setting) == 1;
end

function EditModeManagerFrameMixin:HasAccountSettings()
	return self.accountSettings ~= nil;
end

function EditModeManagerFrameMixin:InitializeAccountSettings()
	self.accountSettings = C_EditMode.GetAccountSettings();
	self:UpdateAccountSettingMap();

	self:SetGridShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowGrid));
	self:SetGridSpacing(self:GetAccountSettingValue(Enum.EditModeAccountSetting.GridSpacing));
	self:SetEnableSnap(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.EnableSnap));
	self:SetEnableAdvancedOptions(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.EnableAdvancedOptions));
	self.AccountSettings:SetExpandedState(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.SettingsExpanded));
	self.AccountSettings:SetTargetAndFocusShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowTargetAndFocus));
	self.AccountSettings:SetPartyFramesShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowPartyFrames));
	self.AccountSettings:SetRaidFramesShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowRaidFrames));
	if (StanceBar) then
		self.AccountSettings:SetActionBarShown(StanceBar, self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowStanceBar));
	end
	if (PetActionBar) then
		self.AccountSettings:SetActionBarShown(PetActionBar, self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowPetActionBar));
	end
	if (PossessActionBar) then
		self.AccountSettings:SetActionBarShown(PossessActionBar, self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowPossessActionBar));
	end
	self.AccountSettings:SetCastBarShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowCastBar));
	self.AccountSettings:SetEncounterBarShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowEncounterBar));
	self.AccountSettings:SetExtraAbilitiesShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowExtraAbilities));
	self.AccountSettings:SetBuffsAndDebuffsShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowBuffsAndDebuffs));
	self.AccountSettings:SetExternalDefensivesShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowExternalDefensives));
	self.AccountSettings:SetTalkingHeadFrameShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowTalkingHeadFrame));
	self.AccountSettings:SetVehicleLeaveButtonShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowVehicleLeaveButton));
	self.AccountSettings:SetBossFramesShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowBossFrames));
	self.AccountSettings:SetArenaFramesShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowArenaFrames));
	self.AccountSettings:SetLootFrameShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowLootFrame));
	self.AccountSettings:SetHudTooltipShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowHudTooltip));
	self.AccountSettings:SetStatusTrackingBar2Shown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowStatusTrackingBar2));
	self.AccountSettings:SetDurabilityFrameShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowDurabilityFrame));
	self.AccountSettings:SetPetFrameShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowPetFrame));
	self.AccountSettings:SetCooldownViewerShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowCooldownViewer));
	self.AccountSettings:SetPersonalResourceDisplayShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowPersonalResourceDisplay));
	self.AccountSettings:SetEncounterEventsShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowEncounterEvents));
	self.AccountSettings:SetDamageMeterShown(self:GetAccountSettingValueBool(Enum.EditModeAccountSetting.ShowDamageMeter));
end

function EditModeManagerFrameMixin:OnAccountSettingChanged(changedSetting, newValue)
	if type(newValue) == "boolean" then
		newValue = newValue and 1 or 0;
	end

	for _, settingInfo in pairs(self.accountSettings) do
		if settingInfo.setting == changedSetting then
			if settingInfo.value ~= newValue then
				settingInfo.value = newValue;
				self:UpdateAccountSettingMap();
				C_EditMode.SetAccountSetting(changedSetting, newValue);
			end
			return;
		end
	end
end

function EditModeManagerFrameMixin:UpdateLayoutCounts(savedLayouts)
	self.numLayouts = {
		[Enum.EditModeLayoutType.Account] = 0,
		[Enum.EditModeLayoutType.Character] = 0,
	};

	for _, layoutInfo in ipairs(savedLayouts) do
		self.numLayouts[layoutInfo.layoutType] = self.numLayouts[layoutInfo.layoutType] + 1;
	end
end

function EditModeManagerFrameMixin:AreLayoutsOfTypeMaxed(layoutType)
	return self.numLayouts[layoutType] >= Constants.EditModeConsts.EditModeMaxLayoutsPerType;
end

function EditModeManagerFrameMixin:AreLayoutsFullyMaxed()
	return self:AreLayoutsOfTypeMaxed(Enum.EditModeLayoutType.Account) and self:AreLayoutsOfTypeMaxed(Enum.EditModeLayoutType.Character);
end

function EditModeManagerFrameMixin:UpdateLayoutInfo(layoutInfo, reconcileLayouts)
	self.layoutApplyInProgress = true;
	self.layoutInfo = layoutInfo;

	if reconcileLayouts then
		self:ReconcileLayoutsWithModern();
	end

	local savedLayouts = self.layoutInfo.layouts;
	self.layoutInfo.layouts = EditModePresetLayoutManager:GetCopyOfPresetLayouts();
	tAppendAll(self.layoutInfo.layouts, savedLayouts);

	self:UpdateLayoutCounts(savedLayouts);

	self:InitSystemAnchors();
	self:UpdateSystems();
	self:ClearActiveChangesFlags();

	if self:IsShown() then
		self:UpdateDropdownOptions();
	end

	self.layoutApplyInProgress = false;
	self:UpdateActionBarPositions();

	local forceInvokeYes = true;
	self:InvokeOnAnyEditModeSystemAnchorChanged(forceInvokeYes);
end

function EditModeManagerFrameMixin:OnEditModeSystemAnchorChanged()
	self.editModeSystemAnchorDirty = true;
end

function EditModeManagerFrameMixin:InvokeOnAnyEditModeSystemAnchorChanged(force)
	if not force and not self.editModeSystemAnchorDirty then
		return;
	end

	local function callOnAnyEditModeSystemAnchorChanged(index, systemFrame)
		systemFrame:OnAnyEditModeSystemAnchorChanged();
	end
	secureexecuterange(self.registeredSystemFrames, callOnAnyEditModeSystemAnchorChanged);

	self.editModeSystemAnchorDirty = nil;
end

function EditModeManagerFrameMixin:GetLayouts()
	return self.layoutInfo.layouts;
end

function EditModeManagerFrameMixin:SetGridShown(gridShown, isUserInput)
	self.Grid:SetShown(gridShown);
	self.GridSpacingSlider:SetEnabled(gridShown);

	if isUserInput then
		self:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowGrid, gridShown);
	else
		self.ShowGridCheckButton:SetControlChecked(gridShown);
	end
end

function EditModeManagerFrameMixin:SetGridSpacing(gridSpacing, isUserInput)
	self.Grid:SetGridSpacing(gridSpacing);
	self.GridSpacingSlider:SetupSlider(gridSpacing);

	if isUserInput then
		self:OnAccountSettingChanged(Enum.EditModeAccountSetting.GridSpacing, gridSpacing);
	end
end

function EditModeManagerFrameMixin:SetEnableSnap(enableSnap, isUserInput)
	self.snapEnabled = enableSnap;

	if not self.snapEnabled then
		self:HideSnapPreviewLines();
	end

	if isUserInput then
		self:OnAccountSettingChanged(Enum.EditModeAccountSetting.EnableSnap, enableSnap);
	else
		self.EnableSnapCheckButton:SetControlChecked(enableSnap);
	end
end

function EditModeManagerFrameMixin:IsSnapEnabled()
	return self.snapEnabled;
end

function EditModeManagerFrameMixin:SetSnapPreviewFrame(snapPreviewFrame)
	self.snapPreviewFrame = snapPreviewFrame;
end

function EditModeManagerFrameMixin:ClearSnapPreviewFrame()
	self.snapPreviewFrame = nil;
	self:HideSnapPreviewLines();
end

function EditModeManagerFrameMixin:ShouldShowSnapPreviewLines()
	return self:IsSnapEnabled() and self.snapPreviewFrame;
end

function EditModeManagerFrameMixin:RefreshSnapPreviewLines()
	self:HideSnapPreviewLines();

	if not self:ShouldShowSnapPreviewLines() then
		return;
	end

	if not self.magnetismPreviewLinesPool then
		self.magnetismPreviewLinePool = EditModeUtil.CreateLinePool(self.MagnetismPreviewLinesContainer, "MagnetismPreviewLineTemplate");
	end

	local magneticFrameInfos = EditModeMagnetismManager:GetMagneticFrameInfos(self.snapPreviewFrame);
	if magneticFrameInfos then
		for _, magneticFrameInfo in ipairs(magneticFrameInfos) do
			local lineAnchors = EditModeMagnetismManager:GetPreviewLineAnchors(magneticFrameInfo);
			for _, lineAnchor in ipairs(lineAnchors) do
				local line = self.magnetismPreviewLinePool:Acquire();
				line:Setup(magneticFrameInfo, lineAnchor);
			end
		end
	end
end

function EditModeManagerFrameMixin:HideSnapPreviewLines()
	if self.magnetismPreviewLinePool then
		self.magnetismPreviewLinePool:ReleaseAll();
	end
end

function EditModeManagerFrameMixin:SetEnableAdvancedOptions(enableAdvancedOptions, isUserInput)
	self.advancedOptionsEnabled = enableAdvancedOptions;
	self.AccountSettings:LayoutSettings();

	if isUserInput then
		self:OnAccountSettingChanged(Enum.EditModeAccountSetting.EnableAdvancedOptions, enableAdvancedOptions);
	else
		self.EnableAdvancedOptionsCheckButton:SetControlChecked(enableAdvancedOptions);
	end
end

function EditModeManagerFrameMixin:AreAdvancedOptionsEnabled()
	return self.advancedOptionsEnabled;
end

local function SortLayouts(a, b)
	-- Sorts the layouts: character-specific -> account -> preset
	local layoutTypeA = a.layoutInfo.layoutType;
	local layoutTypeB = b.layoutInfo.layoutType;
	if layoutTypeA ~= layoutTypeB then
		return layoutTypeA > layoutTypeB;
	end

	return a.index < b.index;
end

function EditModeManagerFrameMixin:CreateLayoutTbls()
	self.highestLayoutIndexByType = {};

	local layoutTbls = {};
	local hasCharacterLayouts = false;
	for index, layoutInfo in ipairs(self.layoutInfo.layouts) do
		table.insert(layoutTbls, { index = index, layoutInfo = layoutInfo });

		local layoutType = layoutInfo.layoutType;
		if layoutType == Enum.EditModeLayoutType.Character then
			hasCharacterLayouts = true;
		end

		if not self.highestLayoutIndexByType[layoutType] or self.highestLayoutIndexByType[layoutType] < index then
			self.highestLayoutIndexByType[layoutType] = index;
		end
	end

	table.sort(layoutTbls, SortLayouts);

	return layoutTbls, hasCharacterLayouts;
end

function EditModeManagerFrameMixin:GetMaxLayoutsErrorText()
	return HUD_EDIT_MODE_ERROR_MAX_LAYOUTS:format(Constants.EditModeConsts.EditModeMaxLayoutsPerType, Constants.EditModeConsts.EditModeMaxLayoutsPerType);
end

function EditModeManagerFrameMixin:SetupEditModeDialogs()
	EditModeLayoutDialog:SetLayoutManager(self);
	EditModeLayoutDialog:SetModeData({
		newLayout = {
			title = HUD_EDIT_MODE_NAME_LAYOUT_DIALOG_TITLE,
			acceptText = SAVE,
			cancelText = CANCEL,
			disabledAcceptTooltip = HUD_EDIT_MODE_ERROR_ENTER_NAME,
			needsEditbox = true,
			needsCharacterSpecific = true,
			onCancelEvent = "EditMode.NewLayoutCancel",
			onAcceptCallback = self.CreateNewLayoutFromDialog,
			updateAcceptCallback = self.CanCreateNewLayoutFromDialog,
		},

		renameLayout = {
			title = HUD_EDIT_MODE_RENAME_LAYOUT_DIALOG_TITLE,
			acceptText = SAVE,
			cancelText = CANCEL,
			disabledAcceptTooltip = nil,
			needsEditbox = true,
			needsCharacterSpecific = false,
			onAcceptCallback = self.RenameLayoutFromDialog,
			updateAcceptCallback = self.ValidateLayoutNameFromDialog,
		},

		deleteLayout = {
			title = HUD_EDIT_MODE_DELETE_LAYOUT_DIALOG_TITLE,
			acceptText = YES,
			cancelText = NO,
			disabledAcceptTooltip = nil,
			needsEditbox = false,
			needsCharacterSpecific = false,
			onAcceptCallback = self.DeleteLayoutFromDialog,
			updateAcceptCallback = function() return true; end,
		},
	});

	EditModeImportLayoutDialog:SetLayoutManager(self);
	EditModeImportLayoutDialog:SetModeData({
		importLayout = {
			title = HUD_EDIT_MODE_IMPORT_LAYOUT_DIALOG_TITLE,
			importEditBoxLabel = HUD_EDIT_MODE_IMPORT_LAYOUT_DIALOG_EDIT_BOX_LABEL,
			nameEditBoxLabel = HUD_EDIT_MODE_IMPORT_LAYOUT_LINK_DIALOG_EDIT_BOX_LABEL,
			instructionsLabel = HUD_EDIT_MODE_IMPORT_LAYOUT_INSTRUCTIONS,
			acceptText = HUD_EDIT_MODE_IMPORT_LAYOUT,
			cancelText = CANCEL,
			disabledAcceptTooltip = HUD_EDIT_MODE_ERROR_ENTER_IMPORT_STRING_AND_NAME,
			onAcceptCallback = self.ImportLayoutFromDialog,
			updateAcceptCallback = self.CanImportFromDialog,
			needsCharacterSpecific = true,
		},
	});
end

function EditModeManagerFrameMixin:CreateNewLayoutFromDialog(dialog)
	local isLayoutImportedNo = false;
	self:MakeNewLayout(CopyTable(dialog:GetLayoutInfo()), dialog:GetDesiredLayoutType(), dialog:GetEditBoxText(), isLayoutImportedNo);
end

function EditModeManagerFrameMixin:RenameLayoutFromDialog(dialog)
	self:RenameLayout(dialog:GetLayoutIndex(), dialog:GetEditBoxText());
end

function EditModeManagerFrameMixin:DeleteLayoutFromDialog(dialog)
	self:DeleteLayout(dialog:GetLayoutIndex());
end

function EditModeManagerFrameMixin:ImportLayoutFromDialog(dialog)
	self:ImportLayout(dialog:GetLayoutInfo(), dialog:GetDesiredLayoutType(), dialog:GetEditBoxText());
end

function EditModeManagerFrameMixin:IsCharacterSpecificLayout(layout)
	return (layout.layoutType == Enum.EditModeLayoutType.Character);
end

function EditModeManagerFrameMixin:GetLayoutName(layout)
	return layout.layoutName;
end

local maxCharLayoutsErrorText = HUD_EDIT_MODE_ERROR_MAX_CHAR_LAYOUTS:format(Constants.EditModeConsts.EditModeMaxLayoutsPerType);
local maxAccountLayoutsErrorText = HUD_EDIT_MODE_ERROR_MAX_ACCOUNT_LAYOUTS:format(Constants.EditModeConsts.EditModeMaxLayoutsPerType);
local maxLayoutsErrorText = HUD_EDIT_MODE_ERROR_MAX_LAYOUTS:format(Constants.EditModeConsts.EditModeMaxLayoutsPerType, Constants.EditModeConsts.EditModeMaxLayoutsPerType);

function EditModeManagerFrameMixin:ValidateLayoutNameFromDialog(dialog)
	local editBoxText = dialog:GetEditBoxText();
	local editModeLayouts = self:GetLayouts();
	for index, layout in ipairs(editModeLayouts) do
		if self:GetLayoutName(layout) == editBoxText then
			return false, HUD_EDIT_MODE_ERROR_DUPLICATE_NAME;
		end
	end

	local hasValidInput = UserInputNonEmpty(editBoxText);
	if not hasValidInput then
		return false, HUD_EDIT_MODE_ERROR_ENTER_NAME;
	end

	if not C_EditMode.IsValidLayoutName(editBoxText) then
		return false, HUD_EDIT_MODE_ERROR_ENTER_NAME; -- TODO: Add custom string for this?
	end

	return true;
end

function EditModeManagerFrameMixin:CanCreateNewLayoutFromDialog(dialog)
	if self:AreLayoutsFullyMaxed() then
		return false, maxLayoutsErrorText;
	end

	local layoutType = dialog:GetDesiredLayoutType();
	if self:AreLayoutsOfTypeMaxed(layoutType) then
		if layoutType == Enum.EditModeLayoutType.Character then
			return false, maxCharLayoutsErrorText;
		else
			return false, maxAccountLayoutsErrorText;
		end
	end

	return self:ValidateLayoutNameFromDialog(dialog);
end

function EditModeManagerFrameMixin:CanRenameLayoutFromDialog(dialog)
	return self:ValidateLayoutNameFromDialog(dialog);
end

function EditModeManagerFrameMixin:CanImportFromDialog(dialog)
	local isEnabled, disabledTooltip = self:CanCreateNewLayoutFromDialog(dialog);
	if not isEnabled then
		return isEnabled, disabledTooltip;
	end

	if not dialog:GetLayoutInfo() then
		return false, HUD_EDIT_MODE_ERROR_ENTER_IMPORT_STRING_AND_NAME;
	end

	return true;
end

function EditModeManagerFrameMixin:UpdateDropdownOptions()
	local function IsSelected(index)
		return self.layoutInfo.activeLayout == index;
	end

	local function SetSelected(index)
		if not self:IsLayoutSelected(index) then
			if self:HasActiveChanges() then
				self:ShowRevertWarningDialog(index);
			else
				self:SelectLayout(index);
			end
		end
	end

	local layoutTbls, hasCharacterLayouts = self:CreateLayoutTbls();

	self.LayoutDropdown:SetupMenu(function(dropdown, rootDescription)
		rootDescription:SetTag("MENU_EDIT_MODE_MANAGER");

		local lastLayoutType = nil;
		local addedCharacterSpecificHeader = false;
		for _, layoutTbl in ipairs(layoutTbls) do
			local layoutInfo = layoutTbl.layoutInfo;
			local index = layoutTbl.index;
			local layoutType = layoutInfo.layoutType;

			if layoutType == Enum.EditModeLayoutType.Character and not addedCharacterSpecificHeader then
				addedCharacterSpecificHeader = true;
				rootDescription:CreateTitle(characterLayoutHeaderText);
			end

			if lastLayoutType and lastLayoutType ~= layoutType then
				rootDescription:CreateDivider();
			end

			lastLayoutType = layoutType;

			local isUserLayout = layoutType == Enum.EditModeLayoutType.Account or layoutType == Enum.EditModeLayoutType.Character;
			local isPreset = layoutType == Enum.EditModeLayoutType.Preset;
			local text = isPreset and HUD_EDIT_MODE_PRESET_LAYOUT:format(layoutInfo.layoutName) or layoutInfo.layoutName;

			local radio = rootDescription:CreateRadio(text, IsSelected, SetSelected, index);
			if isUserLayout then
				local copyButton = radio:CreateButton(HUD_EDIT_MODE_COPY_LAYOUT, function()
					self:ShowNewLayoutDialog(layoutInfo);
				end);

				local layoutsMaxed = EditModeManagerFrame:AreLayoutsFullyMaxed();
				if layoutsMaxed or self:HasActiveChanges() then
					copyButton:SetEnabled(false);

					local tooltipText = layoutsMaxed and maxLayoutsCopyErrorText or HUD_EDIT_MODE_ERROR_COPY;
					copyButton:SetTooltip(function(tooltip, elementDescription)
						GameTooltip_SetTitle(tooltip, HUD_EDIT_MODE_COPY_LAYOUT);
						GameTooltip_AddErrorLine(tooltip, tooltipText);
					end);
				end

				radio:CreateButton(HUD_EDIT_MODE_RENAME_LAYOUT, function()
					self:ShowRenameLayoutDialog(index, layoutInfo);
				end);

				radio:DeactivateSubmenu();

				radio:AddInitializer(function(button, description, menu)
					local gearButton = MenuTemplates.AttachAutoHideGearButton(button);
					MenuTemplates.SetUtilityButtonTooltipText(gearButton, HUD_EDIT_MODE_RENAME_OR_COPY_LAYOUT);
					MenuTemplates.SetUtilityButtonAnchor(gearButton, MenuVariants.GearButtonAnchor, button);
					MenuTemplates.SetUtilityButtonClickHandler(gearButton, function()
						description:ForceOpenSubmenu();
					end);

					local cancelButton = MenuTemplates.AttachAutoHideCancelButton(button);
					MenuTemplates.SetUtilityButtonTooltipText(cancelButton, HUD_EDIT_MODE_DELETE_LAYOUT);
					MenuTemplates.SetUtilityButtonAnchor(cancelButton, MenuVariants.CancelButtonAnchor, gearButton);
					MenuTemplates.SetUtilityButtonClickHandler(cancelButton, function()
						self:ShowDeleteLayoutDialog(index, layoutInfo);
						menu:Close();
					end);
				end);
			else
				radio:AddInitializer(function(button, description, menu)
					local gearButton = MenuTemplates.AttachAutoHideGearButton(button);
					MenuTemplates.SetUtilityButtonTooltipText(gearButton, HUD_EDIT_MODE_COPY_LAYOUT);
					MenuTemplates.SetUtilityButtonAnchor(gearButton, MenuVariants.GearButtonAnchor, button);
					MenuTemplates.SetUtilityButtonClickHandler(gearButton, function()
						self:ShowNewLayoutDialog(layoutInfo);
						menu:Close();
					end);
				end);
			end
		end

		rootDescription:CreateDivider();

		-- new layout
		local newLayoutDisableOnMaxLayouts = true;
		local newLayoutDisableOnActiveChanges = true;
		local disabled = EditModeLayoutManagerUtil.GetDisableReason(newLayoutDisableOnMaxLayouts, newLayoutDisableOnActiveChanges, self) ~= nil;
		local text = EditModeLayoutManagerUtil.GetNewLayoutText(disabled);
		local newLayoutButton = rootDescription:CreateButton(text, function()
			self:ShowNewLayoutDialog();
		end);
		EditModeLayoutManagerUtil.SetElementDescriptionEnabledState(newLayoutButton, newLayoutDisableOnMaxLayouts, newLayoutDisableOnActiveChanges, self);

		-- import layout
		local importLayoutDisableOnMaxLayouts = true;
		local importLayoutDisableOnActiveChanges = false;
		local importLayoutButton = rootDescription:CreateButton(HUD_EDIT_MODE_IMPORT_LAYOUT, function()
			self:ShowImportLayoutDialog();
		end);
		EditModeLayoutManagerUtil.SetElementDescriptionEnabledState(importLayoutButton, importLayoutDisableOnMaxLayouts, importLayoutDisableOnActiveChanges, self);

		-- share
		local shareSubmenu = rootDescription:CreateButton(HUD_EDIT_MODE_SHARE_LAYOUT);
		shareSubmenu:CreateButton(HUD_EDIT_MODE_COPY_TO_CLIPBOARD, function()
			self:CopyActiveLayoutToClipboard();
		end);
	end);
end

local function initSystemAnchor(index, systemFrame)
	-- Managed frames don't need to be reset.
	if (systemFrame.isBottomManagedFrame or systemFrame.isRightManagedFrame) and systemFrame:IsInDefaultPosition() then
		return;
	end

	systemFrame:ClearAllPoints();
	systemFrame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 0, 0);
end

function EditModeManagerFrameMixin:InitSystemAnchors()
	secureexecuterange(self.registeredSystemFrames, initSystemAnchor);
end

function EditModeManagerFrameMixin:UpdateSystems()
	local function callUpdateSystem(index, systemFrame)
		self:UpdateSystem(systemFrame);
	end
	secureexecuterange(self.registeredSystemFrames, callUpdateSystem);
end

function EditModeManagerFrameMixin:UpdateSystem(systemFrame, forceFullUpdate)
	local systemInfo = self:GetActiveLayoutSystemInfo(systemFrame.system, systemFrame.systemIndex);
	if systemInfo then
		if forceFullUpdate then
			systemFrame:MarkAllSettingsDirty();
		end

		systemFrame:UpdateSystem(systemInfo);
	end
end

function EditModeManagerFrameMixin:SetOverrideLayout(overrideLayoutIndex)
	local overrideLayouts = EditModePresetLayoutManager:GetCopyOfOverrideLayouts();
	if not overrideLayouts then
		self.overrideLayoutInfo = nil;
		return;
	end

	local overrideLayout = overrideLayouts[overrideLayoutIndex];
	self.overrideLayoutInfo = overrideLayout or nil;
	if overrideLayout then
		self:UpdateLayoutInfo(C_EditMode.GetLayouts());
	end
end

function EditModeManagerFrameMixin:ClearOverrideLayout()
	self.overrideLayoutInfo = nil;
	self:UpdateLayoutInfo(C_EditMode.GetLayouts());
end

function EditModeManagerFrameMixin:GetActiveLayoutInfo()
	if self.overrideLayoutInfo then
		return self.overrideLayoutInfo;
	else
		return self.layoutInfo and self.layoutInfo.layouts[self.layoutInfo.activeLayout];
	end
end

function EditModeManagerFrameMixin:GetActiveLayoutSystemInfo(system, systemIndex)
	local activeLayoutInfo = self:GetActiveLayoutInfo();
	if activeLayoutInfo then
		for _, systemInfo in ipairs(activeLayoutInfo.systems) do
			if systemInfo.system == system and systemInfo.systemIndex == systemIndex then
				return systemInfo;
			end
		end
	end
end

function EditModeManagerFrameMixin:IsActiveLayoutPreset()
	local activeLayoutInfo = self:GetActiveLayoutInfo();
	return activeLayoutInfo and activeLayoutInfo.layoutType == Enum.EditModeLayoutType.Preset;
end

function EditModeManagerFrameMixin:SelectLayout(layoutIndex)
	if layoutIndex ~= self.layoutInfo.activeLayout then
		self:ClearSelectedSystem();
		C_EditMode.SetActiveLayout(layoutIndex);
		self:NotifyChatOfLayoutChange();
	end
end

function EditModeManagerFrameMixin:IsLayoutSelected(layoutIndex)
	return layoutIndex == self.layoutInfo.activeLayout;
end

function EditModeManagerFrameMixin:ResetDropdownToActiveLayout()
	self:UpdateDropdownOptions();
end

function EditModeManagerFrameMixin:MakeNewLayout(newLayoutInfo, layoutType, layoutName, isLayoutImported)
	if newLayoutInfo and layoutName and layoutName ~= "" and C_EditMode.IsValidLayoutName(layoutName) then
		self:RevertAllChanges();

		newLayoutInfo.layoutType = layoutType;
		newLayoutInfo.layoutName = layoutName;

		local newLayoutIndex;
		if self.highestLayoutIndexByType[layoutType] then
			newLayoutIndex = self.highestLayoutIndexByType[layoutType] + 1;
		elseif (layoutType == Enum.EditModeLayoutType.Character) and self.highestLayoutIndexByType[Enum.EditModeLayoutType.Account] then
			newLayoutIndex = self.highestLayoutIndexByType[Enum.EditModeLayoutType.Account] + 1;
		else
			newLayoutIndex = Enum.EditModePresetLayoutsMeta.NumValues + 1;
		end

		local activateNewLayout = not EditModeUnsavedChangesDialog:HasPendingSelectedLayout();

		table.insert(self.layoutInfo.layouts, newLayoutIndex, newLayoutInfo);
		self:SaveLayouts();
		C_EditMode.OnLayoutAdded(newLayoutIndex, activateNewLayout, isLayoutImported);
	end
end

function EditModeManagerFrameMixin:DeleteLayout(layoutIndex)
	local deleteLayoutInfo = self.layoutInfo.layouts[layoutIndex];
	if deleteLayoutInfo and deleteLayoutInfo.layoutType ~= Enum.EditModeLayoutType.Preset then
		table.remove(self.layoutInfo.layouts, layoutIndex);
		self:SaveLayouts();
		C_EditMode.OnLayoutDeleted(layoutIndex);
	end
end

function EditModeManagerFrameMixin:DeleteAllLayouts()
	for layoutIndex, layoutInfo in ipairs_reverse(self.layoutInfo.layouts) do
		self:DeleteLayout(layoutIndex);
	end
	self:UpdateDropdownOptions();
end

function EditModeManagerFrameMixin:RenameLayout(layoutIndex, layoutName)
	if layoutName ~= "" and C_EditMode.IsValidLayoutName(layoutName) then
		local renameLayoutInfo = self.layoutInfo.layouts[layoutIndex];
		if renameLayoutInfo and renameLayoutInfo.layoutType ~= Enum.EditModeLayoutType.Preset then
			renameLayoutInfo.layoutName = layoutName;
			self:SaveLayouts();
			self:UpdateDropdownOptions();
		end
	end
end

function EditModeManagerFrameMixin:CopyActiveLayoutToClipboard()
	local activeLayoutInfo = self:GetActiveLayoutInfo();
	CopyToClipboard(C_EditMode.ConvertLayoutInfoToString(activeLayoutInfo));
	ChatFrameUtil.DisplaySystemMessageInPrimary(HUD_EDIT_MODE_COPY_TO_CLIPBOARD_NOTICE:format(activeLayoutInfo.layoutName));
end

--[[
function EditModeManagerFrameMixin:LinkActiveLayoutToChat()
	local hyperlink = C_EditMode.ConvertLayoutInfoToHyperlink(self:GetActiveLayoutInfo());
	if not ChatFrameUtil.InsertLink(hyperlink) then
		ChatFrameUtil.OpenChat(hyperlink);
	end
end
]]--

local function clearActiveChangesFlag(index, systemFrame)
	systemFrame:SetHasActiveChanges(false);
end

function EditModeManagerFrameMixin:ClearActiveChangesFlags()
	secureexecuterange(self.registeredSystemFrames, clearActiveChangesFlag);
	self:SetHasActiveChanges(false);
end

function EditModeManagerFrameMixin:ImportLayout(newLayoutInfo, layoutType, layoutName)
	local isLayoutImportedYes = true;
	self:MakeNewLayout(newLayoutInfo, layoutType, layoutName, isLayoutImportedYes);
end

local function callPrepareForSave(index, systemFrame)
	systemFrame:PrepareForSave();
end

function EditModeManagerFrameMixin:PrepareSystemsForSave()
	secureexecuterange(self.registeredSystemFrames, callPrepareForSave);
end

function EditModeManagerFrameMixin:SaveLayouts()
	self:PrepareSystemsForSave();
	C_EditMode.SaveLayouts(self.layoutInfo);
	self:ClearActiveChangesFlags();
	EventRegistry:TriggerEvent("EditMode.SavedLayouts");
end

function EditModeManagerFrameMixin:SaveLayoutChanges()
	if self:IsActiveLayoutPreset() then
		self:ShowNewLayoutDialog();
	else
		self:SaveLayouts();
	end
end

function EditModeManagerFrameMixin:RevertAllChanges()
	self:ClearSelectedSystem();
	self:UpdateLayoutInfo(C_EditMode.GetLayouts());
	UIParent_ManageFramePositions();
end

function EditModeManagerFrameMixin:ShowNewLayoutDialog(layoutInfo)
	self:ClearSelectedSystem();
	EditModeLayoutDialog:ShowNewLayoutDialog(layoutInfo or self:GetActiveLayoutInfo());
end

function EditModeManagerFrameMixin:ShowImportLayoutDialog()
	self:ClearSelectedSystem();
	EditModeImportLayoutDialog:ShowImportLayoutDialog();
end

--[[
function EditModeManagerFrameMixin:OpenAndShowImportLayoutLinkDialog(link)
	if not self:IsShown() then
		self:Show();
	end

	EditModeImportLayoutLinkDialog:ShowDialog(link);
end
--]]

function EditModeManagerFrameMixin:ShowRenameLayoutDialog(layoutIndex, layoutInfo)
	self:ClearSelectedSystem();
	EditModeLayoutDialog:ShowRenameLayoutDialog(layoutIndex, layoutInfo);
end

function EditModeManagerFrameMixin:ShowDeleteLayoutDialog(layoutIndex, layoutInfo)
	self:ClearSelectedSystem();
	EditModeLayoutDialog:ShowDeleteLayoutDialog(layoutIndex, layoutInfo);
end

function EditModeManagerFrameMixin:ShowRevertWarningDialog(selectedLayoutIndex)
	self:ClearSelectedSystem();
	EditModeUnsavedChangesDialog:ShowDialog(selectedLayoutIndex);
end

function EditModeManagerFrameMixin:TryShowUnsavedChangesGlow()
	if self:HasActiveChanges() then
		GlowEmitterFactory:Show(self.SaveChangesButton, GlowEmitterMixin.Anims.NPE_RedButton_GreenGlow);
		return true;
	end
end

function EditModeManagerFrameMixin:ClearUnsavedChangesGlow()
	GlowEmitterFactory:Hide(self.SaveChangesButton);
end

function EditModeManagerFrameMixin:BlockEnteringEditMode(blockingFrame)
	self.FramesBlockingEditMode[blockingFrame] = true;
end

function EditModeManagerFrameMixin:UnblockEnteringEditMode(blockingFrame)
	self.FramesBlockingEditMode[blockingFrame] = nil;
end

function EditModeManagerFrameMixin:CanEnterEditMode()
	if C_GameRules.IsGameRuleActive(Enum.GameRule.EditModeDisabled) then
		return false;
	end

	if C_PlayerInfo.IsPlayerNPERestricted() then
		return false;
	end

	-- We cannot continue until account settings are created because opening edit mode depends on them.
	if not self:HasAccountSettings() then
		return false;
	end

	if not TableIsEmpty(self.FramesBlockingEditMode) then
		return false;
	end

	return true;
end

function EditModeManagerFrameMixin:GetBestLayoutIndex(layoutInfo)
	return layoutInfo.layoutIndex or Constants.EditModeLayoutConsts.EditModeDefaultLayout;
end

function EditModeManagerFrameMixin:GetDefaultAnchor(frame)
	local layoutInfo = self:GetActiveLayoutInfo();

	if layoutInfo.layoutType == Enum.EditModeLayoutType.Override then
		local layoutIndex = self:GetBestLayoutIndex(layoutInfo);
		return EditModePresetLayoutManager:GetOverrideLayoutSystemAnchorInfo(layoutIndex, frame.system, frame.systemIndex);
	end

	-- Assume we want preset since this is default anchoring and there is not an override active
	return EditModePresetLayoutManager:GetPresetLayoutSystemAnchorInfo(Constants.EditModeLayoutConsts.EditModeDefaultLayout, frame.system, frame.systemIndex);
end

EditModeGridMixin = {}

function EditModeGridMixin:OnLoad()
	self.linePool = EditModeUtil.CreateLinePool(self, "EditModeGridLineTemplate");

	self:RegisterEvent("DISPLAY_SIZE_CHANGED");
	self:RegisterEvent("UI_SCALE_CHANGED");
	hooksecurefunc("UpdateUIParentPosition", function() if self:IsShown() then self:UpdateGrid() end end);
end

function EditModeGridMixin:OnHide()
	EditModeMagnetismManager:UnregisterGrid();
	self.linePool:ReleaseAll();
end

function EditModeGridMixin:SetGridSpacing(spacing)
	self.gridSpacing = spacing;
	self:UpdateGrid();
end

function EditModeGridMixin:UpdateGrid()
	if not self:IsVisible() then
		return;
	end

	self.linePool:ReleaseAll();
	EditModeMagnetismManager:RegisterGrid(self:GetCenter());

	local centerLine = true;
	local centerLineNo = false;
	local verticalLine = true;
	local verticalLineNo = false;

	local centerVerticalLine = self.linePool:Acquire();
	centerVerticalLine:SetupLine(centerLine, verticalLine, 0, 0);
	centerVerticalLine:Show();

	local centerHorizontalLine = self.linePool:Acquire();
	centerHorizontalLine:SetupLine(centerLine, verticalLineNo, 0, 0);
	centerHorizontalLine:Show();

	local halfNumVerticalLines = floor((self:GetWidth() / self.gridSpacing) / 2);
	local halfNumHorizontalLines = floor((self:GetHeight() / self.gridSpacing) / 2);

	for i = 1, halfNumVerticalLines do
		local xOffset = i * self.gridSpacing;

		local line = self.linePool:Acquire();
		line:SetupLine(centerLineNo, verticalLine, xOffset, 0);
		line:Show();

		line = self.linePool:Acquire();
		line:SetupLine(centerLineNo, verticalLine, -xOffset, 0);
		line:Show();
	end

	for i = 1, halfNumHorizontalLines do
		local yOffset = i * self.gridSpacing;

		local line = self.linePool:Acquire();
		line:SetupLine(centerLineNo, verticalLineNo, 0, yOffset);
		line:Show();

		line = self.linePool:Acquire();
		line:SetupLine(centerLineNo, verticalLineNo, 0, -yOffset);
		line:Show();
	end
end

EditModeGridSpacingSliderMixin = {};

function EditModeGridSpacingSliderMixin:OnLoad()
	CallbackRegistryMixin.OnLoad(self);

	self.cbrHandles = EventUtil.CreateCallbackHandleContainer();
	self.cbrHandles:RegisterCallback(self.Slider, MinimalSliderWithSteppersMixin.Event.OnValueChanged, self.OnSliderValueChanged, self);

	self.formatters = {};
	self.formatters[MinimalSliderWithSteppersMixin.Label.Right] = CreateMinimalSliderFormatter(MinimalSliderWithSteppersMixin.Label.Right);
end

local minSpacing = Constants.EditModeConsts.EditModeMinGridSpacing;
local maxSpacing = Constants.EditModeConsts.EditModeMaxGridSpacing;
local spacingStepSize = 10;
local numSteps = (maxSpacing - minSpacing) / spacingStepSize;

function EditModeGridSpacingSliderMixin:SetupSlider(gridSpacing)
	self.Slider:Init(gridSpacing, minSpacing, maxSpacing, numSteps, self.formatters);
end

function EditModeGridSpacingSliderMixin:SetEnabled(enabled)
	self.Slider:SetEnabled(enabled);
end

function EditModeGridSpacingSliderMixin:OnSliderValueChanged(value)
	local isUserInput = true;
	EditModeManagerFrame:SetGridSpacing(value, isUserInput);
end

local function SetupEditModeCheckBoxFunctions(self, keyName, onCheckFn, onMouseOverFn)
	local checkBox = self.SettingsContainer[keyName];
	self.settingsCheckButtons[keyName] = checkBox;
	checkBox:SetCallback(onCheckFn);
	checkBox:SetMouseOverCallback(onMouseOverFn);
end

local function SetupEditModeCheckBox(self, keyName, onCheckFnName, onMouseOverFnName)
	local callback = function(...) self[onCheckFnName](self, ...); end;
	local mouseover = function(...) self[onMouseOverFnName](self, ...) end;
	SetupEditModeCheckBoxFunctions(self, keyName, callback, mouseover);

	if not self[onMouseOverFnName] then
		self[onMouseOverFnName] = function() assertsafe(false, "Missing function; EditMode requires implementation for " .. tostring(onMouseOverFnName)); end;
	end
end

local checkBoxSetupData =
{
	TargetAndFocus = { callbackName = "SetTargetAndFocusShown", mouseoverName = "SetTargetAndFocusMouseOver", },
	PartyFrames = { callbackName = "SetPartyFramesShown", mouseoverName = "SetPartyFramesMouseOver", },
	RaidFrames = { callbackName = "SetRaidFramesShown", mouseoverName = "SetRaidFramesMouseOver", },
	CastBar = { callbackName = "SetCastBarShown", mouseoverName = "SetCastBarMouseOver", },
	EncounterBar = { callbackName = "SetEncounterBarShown", mouseoverName = "SetEncounterBarMouseOver", },
	ExtraAbilities = { callbackName = "SetExtraAbilitiesShown", mouseoverName = "SetExtraAbilitiesMouseOver", },
	BuffsAndDebuffs = { callbackName = "SetBuffsAndDebuffsShown", mouseoverName = "SetBuffsAndDebuffsMouseOver", },
	ExternalDefensives = { callbackName = "SetExternalDefensivesShown", mouseoverName = "SetExternalDefensivesMouseOver", },
	TalkingHeadFrame = { callbackName = "SetTalkingHeadFrameShown", mouseoverName = "SetTalkingHeadFrameMouseOver", },
	VehicleLeaveButton = { callbackName = "SetVehicleLeaveButtonShown", mouseoverName = "SetVehicleLeaveButtonMouseOver", },
	BossFrames = { callbackName = "SetBossFramesShown", mouseoverName = "SetBossFramesMouseOver", },
	ArenaFrames = { callbackName = "SetArenaFramesShown", mouseoverName = "SetArenaFramesMouseOver", },
	LootFrame = { callbackName = "SetLootFrameShown", mouseoverName = "SetLootFrameMouseOver", },
	HudTooltip = { callbackName = "SetHudTooltipShown", mouseoverName = "SetHudTooltipMouseOver", },
	StatusTrackingBar2 = { callbackName = "SetStatusTrackingBar2Shown", mouseoverName = "SetStatusTrackingBar2MouseOver", },
	DurabilityFrame = { callbackName = "SetDurabilityFrameShown", mouseoverName = "SetDurabilityFrameMouseOver", },
	PetFrame = { callbackName = "SetPetFrameShown", mouseoverName = "SetPetFrameMouseOver", },
	TimerBars = { callbackName = "SetTimerBarsShown", mouseoverName = "SetTimerBarsMouseOver", },
	VehicleSeatIndicator = { callbackName = "SetVehicleSeatIndicatorShown", mouseoverName = "SetVehicleSeatIndicatorMouseOver", },
	ArchaeologyBar = { callbackName = "SetArchaeologyBarShown", mouseoverName = "SetArchaeologyBarMouseOver", },
	CooldownViewer = { callbackName = "SetCooldownViewerShown", mouseoverName = "SetCooldownViewerMouseOver", },
	EncounterEvents = { callbackName = "SetEncounterEventsShown", mouseoverName = "SetEncounterEventsMouseOver", },
	DamageMeter = { callbackName = "SetDamageMeterShown", mouseoverName = "SetDamageMeterMouseOver", },
	StanceBar = { callbackName = "SetStanceBarShown", mouseoverName = "SetStanceBarMouseOver", },
	PetActionBar = { callbackName = "SetPetActionBarShown", mouseoverName = "SetPetActionBarMouseOver", },
	PossessActionBar = { callbackName = "SetPossessActionBarShown", mouseoverName = "SetPossessActionBarMouseOver", },
	TotemActionBar = { callbackName = "SetTotemActionBarShown", mouseoverName = "SetTotemActionBarMouseOver", },
};

EditModeAccountSettingsMixin = {};

function EditModeAccountSettingsMixin:OnLoad()
	self:PrepareSettingsCheckButtons();
	self:PrepareSettingsCheckButtonVisibility(); -- Set visibility for settings based on game flavor (e.g., Mainline, Classic, etc.).

	self:LayoutSettings();
end

function EditModeAccountSettingsMixin:PrepareSettingsCheckButtons()
	self.settingsCheckButtons = {};

	for keyName, setupData in pairs(checkBoxSetupData) do
		SetupEditModeCheckBox(self, keyName, setupData.callbackName, setupData.mouseoverName);
	end


	-- This will likely need integration into the new system; holding off on that while resolving a conflict.
	local function onPersonalResourceDisplayCheckboxChecked(isChecked, isUserInput)
		self:SetPersonalResourceDisplayShown(isChecked, isUserInput);
	end
	self.settingsCheckButtons.PersonalResourceDisplay = self.SettingsContainer.PersonalResourceDisplay;
	self.settingsCheckButtons.PersonalResourceDisplay:SetCallback(onPersonalResourceDisplayCheckboxChecked);
end

function EditModeAccountSettingsMixin:OnEvent(event, ...)
	if event == "PLAYER_TARGET_CHANGED" then
		self.oldTargetName = UnitName("target");
		if not self.oldTargetName then
			-- Unregister before setting so we don't fall back into this OnEvent from this change
			self:UnregisterEvent("PLAYER_TARGET_CHANGED");
			TargetUnit("player");
			self:RegisterEvent("PLAYER_TARGET_CHANGED");
		end
	elseif event == "PLAYER_FOCUS_CHANGED" then
		self.oldFocusName = UnitName("focus");
		if not self.oldFocusName then
			-- Unregister before setting so we don't fall back into this OnEvent from this change
			self:UnregisterEvent("PLAYER_FOCUS_CHANGED");
			FocusUnit("player");
			self:RegisterEvent("PLAYER_FOCUS_CHANGED");
		end
	end
end

function EditModeAccountSettingsMixin:OnEditModeEnter()
	self.oldActionBarSettings = {};

	self:EditModeFrameSetup();
end

function EditModeAccountSettingsMixin:OnEditModeExit()
	self:EditModeFrameReset();
end

function EditModeAccountSettingsMixin:LayoutSettings()
	local showAdvancedOptions = EditModeManagerFrame:AreAdvancedOptionsEnabled();
	for _, checkButton in pairs(self.settingsCheckButtons) do
		if checkButton.shouldHide then
			-- If our game version isn't showing this checkButton, just hide it.
			checkButton:Hide();
		else
			if showAdvancedOptions then
				if checkButton.category == EditModeManagerOptionsCategory.Frames then
					checkButton:SetParent(self.SettingsContainer.ScrollChild.AdvancedOptionsContainer.FramesContainer);
				elseif checkButton.category == EditModeManagerOptionsCategory.Combat then
					checkButton:SetParent(self.SettingsContainer.ScrollChild.AdvancedOptionsContainer.CombatContainer);
				else -- Misc
					checkButton:SetParent(self.SettingsContainer.ScrollChild.AdvancedOptionsContainer.MiscContainer);
				end

				checkButton.layoutIndex = checkButton.advancedLayoutIndex;
				checkButton:Show();
			else -- Only show basic options
				checkButton:SetParent(self.SettingsContainer.ScrollChild.BasicOptionsContainer);

				checkButton.layoutIndex = checkButton.basicLayoutIndex;
				checkButton:SetShown(checkButton.isBasicOption);
			end
		end
	end

	self.SettingsContainer.ScrollChild.BasicOptionsContainer:SetShown(not showAdvancedOptions);
	self.SettingsContainer.ScrollChild.AdvancedOptionsContainer:SetShown(showAdvancedOptions);

	EditModeManagerFrame:Layout();
end

function EditModeAccountSettingsMixin:ResetTargetAndFocus()
	self:UnregisterEvent("PLAYER_TARGET_CHANGED");
	self:UnregisterEvent("PLAYER_FOCUS_CHANGED");

	if self.oldTargetName then
		TargetUnit(self.oldTargetName);
	else
		ClearTarget();
	end
	self.oldTargetName = nil;

	if self.oldFocusName then
		FocusUnit(self.oldFocusName);
	else
		ClearFocus();
	end
	self.oldFocusName = nil;

	TargetFrame:ClearHighlight();
	FocusFrame:ClearHighlight();
end

function EditModeAccountSettingsMixin:RefreshTargetAndFocus()
	local showTargetAndFocus = self.settingsCheckButtons.TargetAndFocus:IsControlChecked();
	if showTargetAndFocus then
		self.oldTargetName = UnitName("target");
		self.oldFocusName = UnitName("focus");

		if not TargetFrame:IsShown() then
			TargetUnit("player");
		end

		if not FocusFrame:IsShown() then
			FocusUnit("player");
		end

		TargetFrame:HighlightSystem();
		FocusFrame:HighlightSystem();

		self:RegisterEvent("PLAYER_TARGET_CHANGED");
		self:RegisterEvent("PLAYER_FOCUS_CHANGED");
	else
		self:ResetTargetAndFocus();
	end
end

function EditModeAccountSettingsMixin:SetTargetAndFocusShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowTargetAndFocus, shown);
		self:RefreshTargetAndFocus();
	else
		self.settingsCheckButtons.TargetAndFocus:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:SetTargetAndFocusMouseOver(...)
	TargetFrame:ShowEditInstructions(...);
	FocusFrame:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:RefreshPartyFrames()
	local showPartyFrames = self.settingsCheckButtons.PartyFrames:IsControlChecked();
	if showPartyFrames then
		PartyFrame:HighlightSystem();
		PartyFrame:Raise();
	else
		PartyFrame:ClearHighlight();
	end

	CompactPartyFrame:RefreshMembers();
	UpdateRaidAndPartyFrames();
end

function EditModeAccountSettingsMixin:ResetPartyFrames()
	CompactPartyFrame:RefreshMembers();
	UpdateRaidAndPartyFrames();
end

function EditModeAccountSettingsMixin:SetPartyFramesShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowPartyFrames, shown);
		self:RefreshPartyFrames();
	else
		self.settingsCheckButtons.PartyFrames:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:SetPartyFramesMouseOver(...)
	PartyFrame:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:RefreshRaidFrames()
	local showRaidFrames = self.settingsCheckButtons.RaidFrames:IsControlChecked();
	if showRaidFrames then
		CompactRaidFrameManager_SetSetting("IsShown", true);
		CompactRaidFrameContainer:HighlightSystem();
	else
		CompactRaidFrameContainer:ClearHighlight();
	end

	CompactRaidFrameContainer:ApplyToFrames("group", CompactRaidGroup_UpdateUnits);
	CompactRaidFrameContainer:TryUpdate();
	EditModeManagerFrame:UpdateRaidContainerFlow();
	UpdateRaidAndPartyFrames();
end

function EditModeAccountSettingsMixin:ResetRaidFrames()
	CompactRaidFrameContainer:ApplyToFrames("group", CompactRaidGroup_UpdateUnits);
	CompactRaidFrameContainer:TryUpdate();
	EditModeManagerFrame:UpdateRaidContainerFlow();
end

function EditModeAccountSettingsMixin:SetRaidFramesShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowRaidFrames, shown);
		self:RefreshRaidFrames();
	else
		self.settingsCheckButtons.RaidFrames:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:SetRaidFramesMouseOver(...)
	CompactRaidFrameContainer:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetCastBarMouseOver(...)
	PlayerCastingBarFrame:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetEncounterBarMouseOver(...)
	EncounterBar:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetExtraAbilitiesMouseOver(...)
	ExtraAbilityContainer:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetBuffsAndDebuffsMouseOver(...)
	BuffFrame:ShowEditInstructions(...);
	DebuffFrame:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetExternalDefensivesMouseOver(...)
	ExternalDefensivesFrame:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetTalkingHeadFrameMouseOver(...)
	TalkingHeadFrame:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetVehicleLeaveButtonMouseOver(...)
	MainMenuBarVehicleLeaveButton:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetBossFramesMouseOver(...)
	BossTargetFrameContainer:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetArenaFramesMouseOver(...)
	CompactArenaFrame:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetLootFrameMouseOver(...)
	LootFrame:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetHudTooltipMouseOver(...)
	GameTooltipDefaultContainer:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetStatusTrackingBar2MouseOver(...)
	SecondaryStatusTrackingBarContainer:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetDurabilityFrameMouseOver(...)
	DurabilityFrame:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetPetFrameMouseOver(...)
	PetFrame:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetTimerBarsMouseOver(...)
	MirrorTimerContainer:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetVehicleSeatIndicatorMouseOver(...)
	VehicleSeatIndicator:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetTotemActionBarMouseOver(...)
	MultiCastActionBarFrame:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetArchaeologyBarMouseOver(...)
	ArcheologyDigsiteProgressBar:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetCooldownViewerMouseOver(...)
	for index, cooldownViewer in ipairs(self:GetCooldownViewerFrames()) do
		cooldownViewer:ShowEditInstructions(...);
	end
end

function EditModeAccountSettingsMixin:SetEncounterEventsMouseOver(...)
	for index, encounterEventsFrame in ipairs(self:GetEncounterEventsFrames()) do
		encounterEventsFrame:ShowEditInstructions(...);
	end
end

function EditModeAccountSettingsMixin:SetDamageMeterMouseOver(...)
	for index, frame in ipairs(self:GetDamageMeterFrames()) do
		frame:ShowEditInstructions(...);
	end
end

function EditModeAccountSettingsMixin:SetupActionBar(bar)
	local isShown = bar:IsShown();
	self.oldActionBarSettings[bar] = {
		isShown = isShown;
	}

	-- If the bar is already showing then set control checked
	if isShown then
		self.settingsCheckButtons[bar:GetName()]:SetControlChecked(true);
	end

	self:RefreshActionBarShown(bar);
end

function EditModeAccountSettingsMixin:ResetActionBarShown(bar)
	bar.editModeForceShow = false;
	bar:SetShown(self.oldActionBarSettings[bar].isShown);
end

function EditModeAccountSettingsMixin:RefreshActionBarShown(bar)
	local barName = bar:GetName();
	local show = self.settingsCheckButtons[barName]:IsControlChecked();

	if show then
		bar.editModeForceShow = true;
		bar:Show();
		bar:HighlightSystem();
	else
		self:ResetActionBarShown(bar);
		bar:ClearHighlight();
	end
end

function EditModeAccountSettingsMixin:SetActionBarShown(bar, shown, isUserInput)
	local barName = bar:GetName();
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting["Show"..barName], shown);
		self:RefreshActionBarShown(bar);
	else
		self.settingsCheckButtons[barName]:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:SetStanceBarShown(...)
	self:SetActionBarShown(StanceBar, ...);
end

function EditModeAccountSettingsMixin:SetPetActionBarShown(...)
	self:SetActionBarShown(PetActionBar, ...);
end

function EditModeAccountSettingsMixin:SetPossessActionBarShown(...)
	self:SetActionBarShown(PossessActionBar, ...);
end

function EditModeAccountSettingsMixin:SetStanceBarMouseOver(...)
	StanceBar:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetPetActionBarMouseOver(...)
	PetActionBar:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetPossessActionBarMouseOver(...)
	PossessActionBar:ShowEditInstructions(...);
end

function EditModeAccountSettingsMixin:SetCastBarShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowCastBar, shown);
		self:RefreshCastBar();
	else
		self.settingsCheckButtons.CastBar:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshCastBar()
	local showCastBar = self.settingsCheckButtons.CastBar:IsControlChecked();
	if showCastBar then
		PlayerCastingBarFrame.isInEditMode = true;
		PlayerCastingBarFrame:HighlightSystem();
	else
		PlayerCastingBarFrame.isInEditMode = false;
		PlayerCastingBarFrame:ClearHighlight();
	end
	PlayerCastingBarFrame:UpdateShownState();
end

function EditModeAccountSettingsMixin:SetupEncounterBar()
	-- If encounter bar is showing and has content showing then auto enable the setting
	if EncounterBar:IsShown() and EncounterBar:HasContentShowing() then
		self.settingsCheckButtons.EncounterBar:SetControlChecked(true);
	end
end

function EditModeAccountSettingsMixin:SetEncounterBarShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowEncounterBar, shown);
		self:RefreshEncounterBar();
	else
		self.settingsCheckButtons.EncounterBar:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshEncounterBar()
	local showEncounterbar = self.settingsCheckButtons.EncounterBar:IsControlChecked();
	if showEncounterbar then
		EncounterBar.minimumWidth = 230;
		EncounterBar.minimumHeight = 30;
		EncounterBar:HighlightSystem();
	else
		EncounterBar.minimumWidth = 1;
		EncounterBar.minimumHeight = 1;
		EncounterBar:ClearHighlight();
	end

	EncounterBar:Layout();
	UIParent_ManageFramePositions();
end

function EditModeAccountSettingsMixin:SetExtraAbilitiesShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowExtraAbilities, shown);
		self:RefreshExtraAbilities();
	else
		self.settingsCheckButtons.ExtraAbilities:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshExtraAbilities()
	local showExtraAbilities = self.settingsCheckButtons.ExtraAbilities:IsControlChecked();
	if showExtraAbilities then
		ExtraAbilityContainer.isInEditMode = true;
		ExtraAbilityContainer:HighlightSystem();
	else
		ExtraAbilityContainer.isInEditMode = false;
		ExtraAbilityContainer:ClearHighlight();
	end

	ExtraAbilityContainer:UpdateShownState();
end

function EditModeAccountSettingsMixin:SetBuffsAndDebuffsShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowBuffsAndDebuffs, shown);
		self:RefreshBuffsAndDebuffs();
	else
		self.settingsCheckButtons.BuffsAndDebuffs:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:SetExternalDefensivesShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowExternalDefensives, shown);
		self:RefreshExternalDefensives();
	else
		self.settingsCheckButtons.ExternalDefensives:SetControlChecked(shown);
	end
end

local function RefreshAuraFrame(frame, isEditing)
	frame:SetIsEditing(isEditing);

	if isEditing then
		frame:HighlightSystem();
	else
		frame:ClearHighlight();
	end

	frame:UpdateAuraButtons();
end

function EditModeAccountSettingsMixin:RefreshBuffsAndDebuffs()
	local showBuffsAndDebuffs = self.settingsCheckButtons.BuffsAndDebuffs:IsControlChecked();

	local frames = { BuffFrame, DebuffFrame };
	for _, auraFrame in pairs(frames) do
		RefreshAuraFrame(auraFrame, showBuffsAndDebuffs);
	end
end

function EditModeAccountSettingsMixin:RefreshExternalDefensives()
	RefreshAuraFrame(ExternalDefensivesFrame, self.settingsCheckButtons.ExternalDefensives:IsControlChecked() and self.settingsCheckButtons.ExternalDefensives:ShouldEnable());
end

function EditModeAccountSettingsMixin:SetTalkingHeadFrameShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowTalkingHeadFrame, shown);
		self:RefreshTalkingHeadFrame();
	else
		self.settingsCheckButtons.TalkingHeadFrame:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshTalkingHeadFrame()
	local showTalkingHeadFrame = self.settingsCheckButtons.TalkingHeadFrame:IsControlChecked();
	if showTalkingHeadFrame then
		TalkingHeadFrame.isInEditMode = true;
		TalkingHeadFrame:HighlightSystem();
	else
		TalkingHeadFrame.isInEditMode = false;
		TalkingHeadFrame:ClearHighlight();
	end

	TalkingHeadFrame:UpdateShownState();
	UIParent_ManageFramePositions();
end

function EditModeAccountSettingsMixin:SetVehicleLeaveButtonShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowVehicleLeaveButton, shown);
		self:RefreshVehicleLeaveButton();
	else
		self.settingsCheckButtons.VehicleLeaveButton:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshVehicleLeaveButton()
	local showVehicleLeaveButton = self.settingsCheckButtons.VehicleLeaveButton:IsControlChecked();
	if showVehicleLeaveButton then
		MainMenuBarVehicleLeaveButton.isInEditMode = true;
		MainMenuBarVehicleLeaveButton:HighlightSystem();
	else
		MainMenuBarVehicleLeaveButton.isInEditMode = false;
		MainMenuBarVehicleLeaveButton:ClearHighlight();
	end

	MainMenuBarVehicleLeaveButton:UpdateShownState();
end

function EditModeAccountSettingsMixin:SetBossFramesShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowBossFrames, shown);
		self:RefreshBossFrames();
	else
		self.settingsCheckButtons.BossFrames:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshBossFrames()
	local showBossFrames = self.settingsCheckButtons.BossFrames:IsControlChecked();
	if showBossFrames then
		BossTargetFrameContainer.isInEditMode = true;
		BossTargetFrameContainer:HighlightSystem();
	else
		BossTargetFrameContainer.isInEditMode = false;
		BossTargetFrameContainer:ClearHighlight();
	end

	BossTargetFrameContainer:UpdateShownState();
end

function EditModeAccountSettingsMixin:SetArenaFramesShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowArenaFrames, shown);
		self:RefreshArenaFrames();
	else
		self.settingsCheckButtons.ArenaFrames:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshArenaFrames()
	local showArenaFrames = self.settingsCheckButtons.ArenaFrames:IsControlChecked();
	CompactArenaFrame:SetIsInEditMode(showArenaFrames);
end

function EditModeAccountSettingsMixin:ResetArenaFrames()
	CompactArenaFrame:SetIsInEditMode(false);
end

function EditModeAccountSettingsMixin:SetLootFrameShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowLootFrame, shown);
		self:RefreshLootFrame();
	else
		self.settingsCheckButtons.LootFrame:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshLootFrame()
	local showLootFrame = self.settingsCheckButtons.LootFrame:IsControlChecked() and GetCVar("lootUnderMouse") ~= "1";
	if showLootFrame then
		LootFrame.isInEditMode = true;
		LootFrame:HighlightSystem();
	else
		LootFrame.isInEditMode = false;
		LootFrame:ClearHighlight();
	end

	LootFrame:UpdateShownState();
end

function EditModeAccountSettingsMixin:SetHudTooltipShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowHudTooltip, shown);
		self:RefreshHudTooltip();
	else
		self.settingsCheckButtons.HudTooltip:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshHudTooltip()
	local showHudTooltip = self.settingsCheckButtons.HudTooltip:IsControlChecked();
	if showHudTooltip then
		GameTooltip_Hide();
		GameTooltipDefaultContainer:Show();
	else
		GameTooltipDefaultContainer:Hide();
	end
end

function EditModeAccountSettingsMixin:ResetHudTooltip()
	GameTooltipDefaultContainer:Hide();
end

function EditModeAccountSettingsMixin:SetStatusTrackingBar2Shown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowStatusTrackingBar2, shown);
		self:RefreshStatusTrackingBar2();
	else
		self.settingsCheckButtons.StatusTrackingBar2:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:SetupStatusTrackingBar2()
	self.settingsCheckButtons.StatusTrackingBar2:SetLabelText(SecondaryStatusTrackingBarContainer:GetSystemName());

	if SecondaryStatusTrackingBarContainer:IsShown() then
		self.settingsCheckButtons.StatusTrackingBar2:SetControlChecked(true);
	end
end

function EditModeAccountSettingsMixin:RefreshStatusTrackingBar2()
	local showStatusTrackingBar2 = self.settingsCheckButtons.StatusTrackingBar2:IsControlChecked();
	if showStatusTrackingBar2 then
		SecondaryStatusTrackingBarContainer.isInEditMode = true;
		SecondaryStatusTrackingBarContainer:HighlightSystem();
	else
		SecondaryStatusTrackingBarContainer.isInEditMode = false;
		SecondaryStatusTrackingBarContainer:ClearHighlight();
	end
	SecondaryStatusTrackingBarContainer:UpdateShownState();
end

function EditModeAccountSettingsMixin:SetupDurabilityFrame()
	-- If the frame is already showing then set control checked
	if DurabilityFrame:IsShown() then
		self.settingsCheckButtons.DurabilityFrame:SetControlChecked(true);
	end
end

function EditModeAccountSettingsMixin:SetDurabilityFrameShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowDurabilityFrame, shown);
		self:RefreshDurabilityFrame();
	else
		self.settingsCheckButtons.DurabilityFrame:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshDurabilityFrame()
	local showDurabilityFrame = self.settingsCheckButtons.DurabilityFrame:IsControlChecked();
	if showDurabilityFrame then
		DurabilityFrame.isInEditMode = true;
		DurabilityFrame:HighlightSystem();
	else
		DurabilityFrame.isInEditMode = false;
		DurabilityFrame:ClearHighlight();
	end

	DurabilityFrame:UpdateShownState();
end

function EditModeAccountSettingsMixin:SetupPetFrame()
	-- If the frame is already showing then set control checked
	if PetFrame:IsShown() then
		self.settingsCheckButtons.PetFrame:SetControlChecked(true);
	end
end

function EditModeAccountSettingsMixin:SetPetFrameShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowPetFrame, shown);
		self:RefreshPetFrame();
	else
		self.settingsCheckButtons.PetFrame:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshPetFrame()
	local showPetFrame = self.settingsCheckButtons.PetFrame:IsControlChecked();
	if showPetFrame then
		PetFrame.isInEditMode = true;
		PetFrame:HighlightSystem();
	else
		PetFrame.isInEditMode = false;
		PetFrame:ClearHighlight();
	end

	PetFrame:UpdateShownState();
end

function EditModeAccountSettingsMixin:SetupTimerBars()
	-- If the frame is already showing then set control checked
	if MirrorTimerContainer:HasAnyTimersShowing() then
		self.settingsCheckButtons.TimerBars:SetControlChecked(true);
	end
end

function EditModeAccountSettingsMixin:SetTimerBarsShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowTimerBars, shown);
		self:RefreshTimerBars();
	else
		self.settingsCheckButtons.TimerBars:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshTimerBars()
	local showTimerBars = self.settingsCheckButtons.TimerBars:IsControlChecked();
	MirrorTimerContainer:SetIsInEditMode(showTimerBars);
	if showTimerBars then
		MirrorTimerContainer:HighlightSystem();
	else
		MirrorTimerContainer:ClearHighlight();
	end
end

function EditModeAccountSettingsMixin:SetupVehicleSeatIndicator()
	-- If the frame is already showing then set control checked
	if VehicleSeatIndicator:IsShown() then
		self.settingsCheckButtons.VehicleSeatIndicator:SetControlChecked(true);
	end
end

function EditModeAccountSettingsMixin:SetVehicleSeatIndicatorShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowVehicleSeatIndicator, shown);
		self:RefreshVehicleSeatIndicator();
	else
		self.settingsCheckButtons.VehicleSeatIndicator:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshVehicleSeatIndicator()
	local showVehicleSeatIndicator = self.settingsCheckButtons.VehicleSeatIndicator:IsControlChecked();
	VehicleSeatIndicator:SetIsInEditMode(showVehicleSeatIndicator);
	if showVehicleSeatIndicator then
		VehicleSeatIndicator:HighlightSystem();
	else
		VehicleSeatIndicator:ClearHighlight();
	end
end

function EditModeAccountSettingsMixin:SetupTotemActionBar()
	-- If the frame is already showing then set control checked
	if MultiCastActionBarFrame:IsShown() then
		self.settingsCheckButtons.TotemActionBar:SetControlChecked(true);
	end
end

function EditModeAccountSettingsMixin:SetTotemActionBarShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowTotemActionBar, shown);
		self:RefreshTotemActionBar();
	else
		self.settingsCheckButtons.TotemActionBar:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshTotemActionBar()
	local showTotemActionBar = self.settingsCheckButtons.TotemActionBar:IsControlChecked();
	MultiCastActionBarFrame:SetIsInEditMode(showTotemActionBar);
	if showTotemActionBar then
		MultiCastActionBarFrame:HighlightSystem();
	else
		MultiCastActionBarFrame:ClearHighlight();
	end
end

function EditModeAccountSettingsMixin:SetupArchaeologyBar()
	-- If the frame is already showing then set control checked
	if ArcheologyDigsiteProgressBar:IsShown() then
		self.settingsCheckButtons.ArchaeologyBar:SetControlChecked(true);
	end
end

function EditModeAccountSettingsMixin:SetArchaeologyBarShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowArchaeologyBar, shown);
		self:RefreshArchaeologyBar();
	else
		self.settingsCheckButtons.ArchaeologyBar:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshArchaeologyBar()
	local showArchaeologyBar = self.settingsCheckButtons.ArchaeologyBar:IsControlChecked();
	ArcheologyDigsiteProgressBar:SetIsInEditMode(showArchaeologyBar);
	if showArchaeologyBar then
		ArcheologyDigsiteProgressBar:HighlightSystem();
	else
		ArcheologyDigsiteProgressBar:ClearHighlight();
	end
end

function EditModeAccountSettingsMixin:SetCooldownViewerShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowCooldownViewer, shown);
		self:RefreshCooldownViewer();
	else
		self.settingsCheckButtons.CooldownViewer:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:GetCooldownViewerFrames()
	local frames = { EssentialCooldownViewer, UtilityCooldownViewer, BuffIconCooldownViewer, BuffBarCooldownViewer };
	assertsafe(#frames == Enum.EditModeCooldownViewerSystemIndicesMeta.NumValues, "Missing cooldown viewer frame.");
	return frames;
end

function EditModeAccountSettingsMixin:SetPersonalResourceDisplayShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowPersonalResourceDisplay, shown);
		self:RefreshPersonalResourceDisplay();
	else
		self.settingsCheckButtons.PersonalResourceDisplay:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:RefreshCooldownViewer()
	local showCooldownViewer = self.settingsCheckButtons.CooldownViewer:IsControlChecked() and self.settingsCheckButtons.CooldownViewer:ShouldEnable();

	for _, cooldownViewer in ipairs(self:GetCooldownViewerFrames()) do
		cooldownViewer:SetIsEditing(showCooldownViewer);
		if showCooldownViewer then
			cooldownViewer:HighlightSystem();
		else
			cooldownViewer:ClearHighlight();
		end
	end
end

function EditModeAccountSettingsMixin:RefreshPersonalResourceDisplay()
	local showPersonalResourceDisplay = self.settingsCheckButtons.PersonalResourceDisplay:IsControlChecked();

	if showPersonalResourceDisplay then
		PersonalResourceDisplayFrame:SetIsInEditMode(true);
		PersonalResourceDisplayFrame:HighlightSystem();
	else
		PersonalResourceDisplayFrame:SetIsInEditMode(false);
		PersonalResourceDisplayFrame:ClearHighlight();
	end
end

function EditModeAccountSettingsMixin:SetEncounterEventsShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowEncounterEvents, shown);
		self:RefreshEncounterEvents();
	else
		self.settingsCheckButtons.EncounterEvents:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:GetEncounterEventsFrames()
	local frames = { EncounterTimeline, CriticalEncounterWarnings, MediumEncounterWarnings, MinorEncounterWarnings };
	assertsafe(#frames == Enum.EditModeEncounterEventsSystemIndicesMeta.NumValues, "Missing encounter events frame.");
	return frames;
end

function EditModeAccountSettingsMixin:RefreshEncounterEvents()
	local showEncounterEventsFrames = self.settingsCheckButtons.EncounterEvents:IsControlChecked() and self.settingsCheckButtons.EncounterEvents:ShouldEnable();
	local encounterEventsFrames = self:GetEncounterEventsFrames();

	local function ShowEncounterEventFrame(_, encounterEventsFrame)
		encounterEventsFrame:SetIsEditing(true);
		encounterEventsFrame:HighlightSystem();
	end

	local function HideEncounterEventFrame(_, encounterEventsFrame)
		encounterEventsFrame:SetIsEditing(false);
		encounterEventsFrame:ClearHighlight();
	end

	if showEncounterEventsFrames then
		secureexecuterange(encounterEventsFrames, ShowEncounterEventFrame);
	else
		secureexecuterange(encounterEventsFrames, HideEncounterEventFrame);
	end
end

function EditModeAccountSettingsMixin:SetDamageMeterShown(shown, isUserInput)
	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.ShowDamageMeter, shown);
		self:RefreshDamageMeter();
	else
		self.settingsCheckButtons.DamageMeter:SetControlChecked(shown);
	end
end

function EditModeAccountSettingsMixin:GetDamageMeterFrames()
	local frames = { DamageMeter };
	return frames;
end

function EditModeAccountSettingsMixin:RefreshDamageMeter()
	local showDamageMeter = self.settingsCheckButtons.DamageMeter:IsControlChecked() and self.settingsCheckButtons.DamageMeter:ShouldEnable();

	local damageMeterFrames = self:GetDamageMeterFrames();

	if showDamageMeter then
		for _, damageMeter in ipairs(damageMeterFrames) do
			damageMeter:SetIsEditing(true);
			damageMeter:HighlightSystem();
		end
	else
		for _, damageMeter in ipairs(damageMeterFrames) do
			damageMeter:SetIsEditing(false);
			damageMeter:ClearHighlight();
		end
	end
end

function EditModeAccountSettingsMixin:SetExpandedState(expanded, isUserInput)
	self.expanded = expanded;
	self.Expander.Label:SetText(expanded and HUD_EDIT_MODE_COLLAPSE_OPTIONS or HUD_EDIT_MODE_EXPAND_OPTIONS);

	self.SettingsContainer:SetShown(self.expanded);
	if self.expanded then
		self:LayoutSettings();
	else
		EditModeManagerFrame:Layout();
	end

	if isUserInput then
		EditModeManagerFrame:OnAccountSettingChanged(Enum.EditModeAccountSetting.SettingsExpanded, expanded);
	end
end

function EditModeAccountSettingsMixin:ToggleExpandedState()
	local isUserInput = true;
	self:SetExpandedState(not self.expanded, isUserInput);
end

EditModeManagerTutorialMixin = {};

local HelpTipInfos = {
	[1] = { text = EDIT_MODE_HELPTIPS_LAYOUTS, buttonStyle = HelpTip.ButtonStyle.Next, offsetX = 0, offsetY = 0, targetPoint = HelpTip.Point.RightEdgeCenter, relativeRegionParentKey="LayoutDropdown",
			cvarBitfield = "closedInfoFramesAccountWide", bitfieldFlag = Enum.FrameTutorialAccount.EditModeManager, useParentStrata = true, system = "EditMode" },
	[2] = { text = EDIT_MODE_HELPTIPS_SHOW_HIDDEN_FRAMES, buttonStyle = HelpTip.ButtonStyle.Next, offsetX = 0, offsetY = 0, targetPoint = HelpTip.Point.RightEdgeCenter, relativeRegionParentKey="AccountSettings",
			cvarBitfield = "closedInfoFramesAccountWide", bitfieldFlag = Enum.FrameTutorialAccount.EditModeManager, useParentStrata = true, system = "EditMode" },
	[3] = { text = EDIT_MODE_HELPTIPS_ADVANCED_OPTIONS, buttonStyle = HelpTip.ButtonStyle.Next, offsetX = 0, offsetY = 0, targetPoint = HelpTip.Point.RightEdgeCenter, relativeRegionParentKey="EnableAdvancedOptionsCheckButton",
			cvarBitfield = "closedInfoFramesAccountWide", bitfieldFlag = Enum.FrameTutorialAccount.EditModeManager, useParentStrata = true, system = "EditMode" },
	[4] = { text = EDIT_MODE_HELPTIPS_SELECT_FRAMES, buttonStyle = HelpTip.ButtonStyle.GotIt, offsetX = 0, offsetY = 0, targetPoint = HelpTip.Point.BottomEdgeCenter, hideArrow = true,
			cvarBitfield = "closedInfoFramesAccountWide", bitfieldFlag = Enum.FrameTutorialAccount.EditModeManager, useParentStrata = true, system = "EditMode" },
};

function EditModeManagerTutorialMixin:OnLoad()
	local onAcknowledgeCallback = GenerateClosure(self.ProgressHelpTips, self);
	for index, helpTipInfo in ipairs(HelpTipInfos) do
		helpTipInfo.onAcknowledgeCallback = onAcknowledgeCallback;
	end
end

function EditModeManagerTutorialMixin:HasHelptipsToShow()
	return HelpTip:AreHelpTipsEnabled() and not GetCVarBitfield("closedInfoFramesAccountWide", Enum.FrameTutorialAccount.EditModeManager);
end

function EditModeManagerTutorialMixin:OnShow()
	if self:HasHelptipsToShow() then
		self:BeginHelpTips();
	end
end

function EditModeManagerTutorialMixin:OnClick()
	if HelpTip:IsShowingAny(self) then
		HelpTip:HideAll(self);
	else
		self:BeginHelpTips();
	end
end

function EditModeManagerTutorialMixin:BeginHelpTips()
	-- Expand the account setttings for the help tips
	local expanded = true;
	local isUserInput = false;
	EditModeManagerFrame.AccountSettings:SetExpandedState(expanded, isUserInput)

	self.currentTipIndex = 1;
	self:ShowHelpTip();
end

function EditModeManagerTutorialMixin:ShowHelpTip()
	local helpTipInfo = HelpTipInfos[self.currentTipIndex];
	if helpTipInfo then
		local relativeRegion = helpTipInfo.relativeRegionParentKey and EditModeManagerFrame[helpTipInfo.relativeRegionParentKey] or EditModeManagerFrame;
		HelpTip:Show(self, helpTipInfo, relativeRegion);
	end
end

function EditModeManagerTutorialMixin:ProgressHelpTips()
	-- the cvarbitfield flag will be set by each helptip, but just updating once
	if self.currentTipIndex == 1 then
		MainMenuMicroButton:UpdateNotificationIcon();
	end

	self.currentTipIndex = self.currentTipIndex + 1;

	if self.currentTipIndex > #HelpTipInfos then
		HelpTip:HideAll(self);
		return;
	end

	self:ShowHelpTip();
end
