local gridOptionsMax = 6;

PlayerChoiceFrameMixin = {};

function PlayerChoiceFrameMixin:OnLoad()
	self.optionPools = CreateFramePoolCollection();
	self.onCloseCallback = GenerateClosure(self.OnCloseUIFromExitButton, self);

	self:SetupGridFrames();
end

function PlayerChoiceFrameMixin:SetupGridFrames()
	self.optionFrames = {};

	self.GridNoSelectionHeader:SetTextToFit(PLAYER_CHOICE_GRID_HEADER);
	self.GridNoSelectionDescription:SetTextToFit(PLAYER_CHOICE_GRID_DESC);

	-- Container for grid does not support list buttons. Only one template type here is expected.
	self.OptionButtonsContainer.buttonFrameTemplate = "PlayerChoiceBaseOptionButtonFrameTemplate";

	self.WidgetContainer:SetScript("OnHide", function()
		self.WidgetContainer:UnregisterForWidgetSet();
	end);
end

function PlayerChoiceFrameMixin:OnEvent(event, ...)
	HideUIPanel(self);
end

-- IMPORTANT: useOldNineSlice preserves old functionality and should not be used in the future
local customTextureKitInfo = {
	-- Normal texture kit options (using PlayerChoiceNormalOptionTemplate, inherited from defaultTextureKitInfo)
	neutral = {
		useOldNineSlice = true,
	},

	alliance = {
		closeBorderX = 0,
		closeBorderY = 0,
		headerYoffset = -48,
		useOldNineSlice = true,
	},

	horde = {
		headerYoffset = -55,
		useOldNineSlice = true,
	},

	marine = {
		closeButtonX = 3,
		closeButtonY = 3,
		uniqueCorners = true,
		useOldNineSlice = true,
	},

	mechagon = {
		closeButtonX = 3,
		closeButtonY = 3,
		useOldNineSlice = true,
	},

	NightFae = {
		uniqueCorners = true,
		useOldNineSlice = true,
		borderTopLeftAnchorX = -12, 
		borderTopLeftAnchorY = 12, 
		borderBottomRightAnchorX = 12, 
		borderBottomRightAnchorY = -12, 
	},

	Venthyr = {
		closeButtonX = 2,
		closeBorderX = -2,
		closeBorderY = 2,
		uniqueCorners = true,
		useOldNineSlice = true,
	},

	Kyrian = {
		uniqueCorners = true,
		useOldNineSlice = true,
	},

	Dragonflight = {
		closeButtonX = -2,
		closeButtonY = -8,
		uniqueCorners = true,
		useOldNineSlice = true,
	},

	thewarwithin = {
		closeButtonX = -9,
		closeButtonY = -9,
	},

	midnight = {
		closeButtonX = -11,
		closeButtonY = -11,
	},

	-- Texture Kits below here use custom templates 

	-- Oribos is a special texture kit type, used for the covenant choice UI in Shadowlands
	Oribos = {
		optionFrameTemplate = "PlayerChoiceCovenantChoiceOptionTemplate",
		exitButtonSoundKit = SOUNDKIT.UI_COVENANT_CHOICE_CLOSE,
		optionsTopPadding = 124,
		optionsBottomPadding = 36,
		optionsSidePadding = 59,
		optionsSpacing = 6,
		uniqueCorners = true,
		useOldNineSlice = true,
	},

	-- The remaining textures kits are variants of the Torghast-style power choice (genericplayerchoice is the evergreen version)
	genericplayerchoice = {
		optionFrameTemplate = "PlayerChoiceGenericPowerChoiceOptionTemplate",
		optionsTopPadding = 120,
		optionsBottomPadding = 120,
		showOptionsOnly = true,
		frameYOffset = 95,
		timerXOffset = 0,
		timerYOffset = -5,
		useOldNineSlice = true,
	},

	jailerstower = {
		optionFrameTemplate = "PlayerChoiceTorghastOptionTemplate",
		optionsTopPadding = 30,
		optionsBottomPadding = 55,
		showOptionsOnly = true,
		frameYOffset = 95,
		useOldNineSlice = true,
	},

	cypherchoice = {
		optionFrameTemplate = "PlayerChoiceCypherOptionTemplate",
		optionsTopPadding = 120,
		optionsBottomPadding = 55,
		showOptionsOnly = true,
		frameYOffset = 95,
		toggleXOffset = 0,
		toggleYOffset = -20,
		timerXOffset = 0,
		timerYOffset = -5,
		useOldNineSlice = true,
	},
};

local defaultTextureKitInfo = {
	optionFrameTemplate = "PlayerChoiceNormalOptionTemplate",
	optionFrameTemplateGrid = "PlayerChoiceNormalOptionGridTemplate",
	closeButtonX = 1,
	closeButtonY = 2,
	closeBorderX = -1,
	closeBorderY = 1,
	optionsTopPadding = 112,
	optionsBottomPadding = 69,
	optionsSidePadding = 65,
	optionsSpacing = 20,
	frameYOffset = 0,
	borderTopLeftAnchorX = -6, 
	borderTopLeftAnchorY = 6, 
	borderBottomRightAnchorX = 6, 
	borderBottomRightAnchorY = -6, 
};

function PlayerChoiceGetTextureKitInfo(textureKit)
	local kitInfo = customTextureKitInfo[textureKit] or {};
	return setmetatable(kitInfo, {__index = defaultTextureKitInfo});
end

function PlayerChoiceFrameMixin:GetTextureKitInfo()
	local kitInfo = customTextureKitInfo[self.uiTextureKit] or {};
	return setmetatable(kitInfo, {__index = defaultTextureKitInfo});
end

function PlayerChoiceFrameMixin:SetupTextureKits(frame, regions)
	SetupTextureKitOnRegions(self.uiTextureKit, frame, regions, TextureKitConstants.SetVisibility, TextureKitConstants.UseAtlasSize);
end

local DEFAULT_TEXTURE_KIT = "neutral";

function PlayerChoiceFrameMixin:TryShow()
	local choiceInfo = C_PlayerChoice.GetCurrentPlayerChoiceInfo();
	if not choiceInfo then
		return;
	end

	self.choiceInfo = choiceInfo;

	if choiceInfo.uiTextureKit then
		self.uiTextureKit = choiceInfo.uiTextureKit;
		self.isLegacy = false;
	else
		self.uiTextureKit = DEFAULT_TEXTURE_KIT;
		self.isLegacy = true;
	end

	self.textureKitInfo = self:GetTextureKitInfo();

	self:SetupFrame();
	self.Title.Text:SetText(choiceInfo.questionText);

	self:SetupOptions();

	ShowUIPanel(self);
end

function PlayerChoiceFrameMixin:GetObjectGUID()
	return self.choiceInfo and self.choiceInfo.objectGUID;
end

local PLAYER_CHOICE_FRAME_EVENTS = {
	"PLAYER_ENTERING_WORLD",
	"PLAYER_DEAD",
	"PLAYER_CHOICE_CLOSE",
};

local function GetActiveMawBuffContainer()
	if ScenarioBlocksFrame and ScenarioBlocksFrame.MawBuffsBlock:IsShown() then
		return ScenarioBlocksFrame.MawBuffsBlock.Container;
	end
end

function PlayerChoiceFrameMixin:OnShow()
	FrameUtil.RegisterFrameForEvents(self, PLAYER_CHOICE_FRAME_EVENTS);

	if self.choiceInfo and self.choiceInfo.soundKitID then
		PlaySound(self.choiceInfo.soundKitID);
	else
		PlaySound(SOUNDKIT.IG_QUEST_LIST_OPEN);
	end

	local activeMawBuffContainer = GetActiveMawBuffContainer();
	if activeMawBuffContainer then
		activeMawBuffContainer:UpdateListState(true);
	end

	local toggleButton = PlayerChoiceToggle_GetActiveToggle();
	if toggleButton then
		toggleButton:ClearAllPoints();
		toggleButton:SetPoint("TOP", self, "BOTTOM", self.textureKitInfo.toggleXOffset or 0, self.textureKitInfo.toggleYOffset or 0);
	end
	PlayerChoiceToggle_TryShow();

	PlayerChoiceTimeRemaining:TryShow();
end

function PlayerChoiceFrameMixin:OnHide()
	FrameUtil.UnregisterFrameForEvents(self, PLAYER_CHOICE_FRAME_EVENTS);

	if self.choiceInfo and self.choiceInfo.closeUISoundKitID then
		PlaySound(self.choiceInfo.closeUISoundKitID);
	else
		PlaySound(SOUNDKIT.IG_QUEST_LIST_CLOSE);
	end

	C_PlayerChoice.OnUIClosed();

	if CovenantPreviewFrame and CovenantPreviewFrame:IsShown() then
		CovenantPreviewFrame:Hide();
	end

	local activeMawBuffContainer = GetActiveMawBuffContainer();
	if activeMawBuffContainer then
		activeMawBuffContainer:UpdateListState(false);
	end

	local toggleButton = PlayerChoiceToggle_GetActiveToggle();
	if toggleButton and toggleButton:IsShown() then
		toggleButton:UpdateButtonState();
	end
end

function PlayerChoiceFrameMixin:FadeOutAllOptions()
	for optionFrame in self.optionPools:EnumerateActiveByTemplate(self.optionFrameTemplate) do
		optionFrame:FadeOut();
	end
end

function PlayerChoiceFrameMixin:OnSelectionMade()
	if not self.choiceInfo.keepOpenAfterChoice then
		HideUIPanel(self);
	end
end

function PlayerChoiceFrameMixin:OnCloseUIFromExitButton()
	if self.choiceInfo.requiresSelection then
		return;
	end

	if self.textureKitInfo.exitButtonSoundKit then
		PlaySound(self.textureKitInfo.exitButtonSoundKit);
	end
	
	HideUIPanel(self);
end

local titleTextureKitRegions = {
	Left = "UI-Frame-%s-TitleLeft",
	Right = "UI-Frame-%s-TitleRight",
	Middle = "_UI-Frame-%s-TitleMiddle",
};

local backgroundTextureKitRegions = {
	BackgroundTile = "UI-Frame-%s-BackgroundTile",
};

local borderFrameTextureKitRegions = {
	Texture = "UI-Frame-%s-Header",
};

local borderFrameTextureKitRegion = "UI-Frame-%s-Border";

function PlayerChoiceFrameMixin:SetupFrame()
	local showExtraFrames = not self.textureKitInfo.showOptionsOnly;
	self.CloseButton:SetShown(showExtraFrames and not self.choiceInfo.requiresSelection);
	self.Header:SetShown(showExtraFrames);
	self.Title:SetShown(showExtraFrames);
	self.Background:SetShown(showExtraFrames);
	self:EnableMouse(showExtraFrames);

	local showChoicesAsGrid = self.choiceInfo.showChoicesAsGrid;
	self.GridDivider:SetShown(showChoicesAsGrid);
	self.GridNoSelectionHeader:SetShown(showChoicesAsGrid);
	self.GridNoSelectionDescription:SetShown(showChoicesAsGrid);

	-- Selection header and description visibility controled by option selection.
	self.GridSelectionHeader:SetShown(false);
	self.GridSelectionDescription:SetShown(false);
	self.OptionButtonsContainer:SetShown(false);
	self.WidgetContainer:SetShown(false);

	-- Visibility controled by option count in SetupOptionsAsGrid().
	self.PagingControls:Hide();

	-- Using the negation here guarantees useOldNineSlice is a bool instead of nil
	local usesNewNineSlice = not self.textureKitInfo.useOldNineSlice;
	self.NineSlice:SetShown(showExtraFrames and not usesNewNineSlice);
	self.BorderOverlay:SetShown(showExtraFrames and usesNewNineSlice);

	if showExtraFrames then
		local function SetBorderPoints(border)
			border:SetPoint("TOPLEFT", self, "TOPLEFT", self.textureKitInfo.borderTopLeftAnchorX, self.textureKitInfo.borderTopLeftAnchorY);
			border:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", self.textureKitInfo.borderBottomRightAnchorX, self.textureKitInfo.borderBottomRightAnchorY);
		end

		SetBorderPoints(self.NineSlice);
		SetBorderPoints(self.BorderOverlay);

		if self.textureKitInfo.useOldNineSlice then
			if self.textureKitInfo.uniqueCorners then
				NineSliceUtil.ApplyUniqueCornersLayout(self.NineSlice, self.uiTextureKit);
			else
				NineSliceUtil.ApplyIdenticalCornersLayout(self.NineSlice, self.uiTextureKit);
			end
		else 
			self.BorderOverlay:SetAtlas(borderFrameTextureKitRegion:format(self.uiTextureKit));
		end

		UIPanelCloseButton_SetBorderAtlas(self.CloseButton, "UI-Frame-%s-ExitButtonBorder", self.textureKitInfo.closeBorderX, self.textureKitInfo.closeBorderY, self.uiTextureKit);

		self.CloseButton:SetPoint("TOPRIGHT", self, "TOPRIGHT", self.textureKitInfo.closeButtonX, self.textureKitInfo.closeButtonY);

		if self.choiceInfo.hideWarboardHeader or not self.textureKitInfo.headerYoffset then
			self.Header:Hide();
		else
			self:SetupTextureKits(self.Header, borderFrameTextureKitRegions);
			self.Header:SetPoint("BOTTOM", self, "TOP", 0, self.textureKitInfo.headerYoffset);
			self.Header:Layout();
			self.Header:Show();
		end

		self:SetupTextureKits(self.Title, titleTextureKitRegions);
		self:SetupTextureKits(self.Background, backgroundTextureKitRegions);
	end

	self:SetPoint("CENTER", 0, self.textureKitInfo.frameYOffset);
end

local function HideAndAnchorTopLeft(framePool, frame)
	frame:Reset();
	frame:Hide();
	frame:ClearAllPoints();
	frame:SetPoint("TOPLEFT", PlayerChoiceFrame, "TOPLEFT");
end

-- ResetPlayerChoiceOptionHeightData needs to be called when player choice options frames are about to start being processed
function PlayerChoiceFrameMixin:ResetPlayerChoiceOptionHeightData()
	self.alignedSectionMaxHeights = {};
end

function PlayerChoiceFrameMixin:GetPlayerChoiceOptionHeightData()
	return self.alignedSectionMaxHeights;
end

function PlayerChoiceFrameMixin:GetOptionDataForOptionFrame(optionFrame)
	-- Each option frame has an index from 1 to gridOptionsMax that is used to find
	-- the adjusted data index as the page number changes.
	local currentPage = self.PagingControls:GetCurrentPage();
	local dataIndex = optionFrame.index + ((currentPage - 1) * gridOptionsMax);
	return self.choiceInfo.options[dataIndex];
end

-- Called from PagingControlsMixin as an expected function on the paging control's parent.
function PlayerChoiceFrameMixin:OnPageChanged()
	for index, optionFrame in ipairs(self.optionFrames) do
		local optionInfo = self:GetOptionDataForOptionFrame(optionFrame);
		if optionInfo then
			optionFrame:Setup(optionInfo, self.uiTextureKit);
			optionFrame:Show();
		else
			optionFrame:Hide();
		end
	end
end

function PlayerChoiceFrameMixin:SetupOptions()
	self.optionsAligned = false;

	self.topPadding = self.textureKitInfo.optionsTopPadding;
	self.bottomPadding = self.textureKitInfo.optionsBottomPadding;
	self.leftPadding = self.textureKitInfo.optionsSidePadding;
	self.rightPadding = self.textureKitInfo.optionsSidePadding;
	self.spacing = self.textureKitInfo.optionsSpacing;

	self.optionPools:ReleaseAll();
	self:ResetPlayerChoiceOptionHeightData();

	local choiceInfo = self.choiceInfo;
	local showChoicesAsGrid = choiceInfo.showChoicesAsGrid;
	if showChoicesAsGrid then
		self.optionFrameTemplate = self.textureKitInfo.optionFrameTemplateGrid;
	else
		self.optionFrameTemplate = self.textureKitInfo.optionFrameTemplate;
	end

	local templateInfo = C_XMLUtil.GetTemplateInfo(self.optionFrameTemplate);
	self.optionPools:GetOrCreatePool(templateInfo.type, self, self.optionFrameTemplate, HideAndAnchorTopLeft);

	if showChoicesAsGrid then
		self:SetupOptionsAsGrid();
	else
		local showAsList = choiceInfo.showChoicesAsList;
		local soloOption = showAsList or (#choiceInfo.options == 1);

		-- First create and call Setup on all the options.
		-- This needs to be done in a separate loop from the AlignSections call (in AlignOptionHeights), because Setup collects the max heights of all the aligned sections, and then AlignSections adjusts the heights

		for optionIndex, optionInfo in ipairs(choiceInfo.options) do
			local optionFrame = self.optionPools:Acquire(self.optionFrameTemplate);
			optionFrame.layoutIndex = optionIndex;
			optionFrame:Setup(optionInfo, self.uiTextureKit, soloOption, showAsList);
		end

		self:AlignOptionHeights(soloOption);
	end
end

function PlayerChoiceFrameMixin:SetupOptionsAsGrid()
	local function OnEnter(button)
		button.ArtworkAdditive:Show();
	end

	local function OnLeave(button)
		button.ArtworkAdditive:Hide();
	end

	local choiceInfo = self.choiceInfo;
	local optionCount = #choiceInfo.options;
	local requiresPages = optionCount > gridOptionsMax;
	self.PagingControls:SetMaxPages(math.ceil(optionCount / gridOptionsMax));
	self.PagingControls:SetCurrentPage(1);
	self.PagingControls:SetShown(requiresPages);

	self.optionFrames = {};

	local optionFrameCount = math.min(gridOptionsMax, optionCount);
	for index = 1, optionFrameCount do
		local optionFrame = self.optionPools:Acquire(self.optionFrameTemplate)
		optionFrame.index = index;
		table.insert(self.optionFrames, optionFrame);

		local optionInfo = choiceInfo.options[index];
		optionFrame:Setup(optionInfo, self.uiTextureKit);

		optionFrame:SetScript("OnEnter", OnEnter);

		optionFrame:SetScript("OnLeave", OnLeave);

		optionFrame:SetScript("OnClick", function(button)
			local optionInfo = self:GetOptionDataForOptionFrame(button);
			if optionInfo.selected then
				return;
			end

			self.WidgetContainer:SetShown(true);
			self.OptionButtonsContainer:SetShown(true);

			-- Deselect the previous option.
			for index, otherOptionInfo in ipairs(choiceInfo.options) do
				otherOptionInfo.selected = false;
			end

			-- Force all visible frames to deselect.
			for index, otherOptionFrame in ipairs(self.optionFrames) do
				otherOptionFrame:SetSelected(false);
			end

			-- Select this.
			local newSelected = not optionInfo.selected;
			optionInfo.selected = newSelected;
			button:SetSelected(newSelected);

			-- Hide the text displayed when no selection is present.
			self.GridNoSelectionHeader:SetShown(false);
			self.GridNoSelectionDescription:SetShown(false);

			-- Show the text in response to having a selection.
			local optionFontInfo = optionFrame:GetOptionFontInfo();
			self.GridSelectionHeader:SetTextToFit(optionInfo.header);
			self.GridSelectionHeader:SetTextColor(optionFontInfo.headerColor:GetRGB());
			self.GridSelectionHeader:SetShown(true);

			self.GridSelectionDescription:SetTextToFit(optionInfo.description);
			self.GridSelectionDescription:SetTextColor(optionFontInfo.descriptionColor:GetRGB());
			self.GridSelectionDescription:SetShown(true);

			local function WidgetsLayout(widgetContainer, sortedWidgets)
				PlayerChoice_WidgetsLayout(widgetContainer, sortedWidgets, optionInfo.consolidateWidgets);
			end

			local function WidgetInit(widgetFrame)
				PlayerChoice_WidgetInit(widgetFrame, button:GetOptionFontInfo());
			end

			PlayerChoice_SetupWidgets(optionInfo, self.WidgetContainer, WidgetsLayout, WidgetInit);

			local showAsList = false;
			self.OptionButtonsContainer:Setup(optionInfo, showAsList, optionFrame);
			self.OptionButtonsContainer:Layout();
		end);

		optionFrame:Show();
	end

	local stride = 2;
	local spacing = 16;
	local layout = AnchorUtil.CreateGridLayout(GridLayoutMixin.Direction.TopLeftToBottomRight, stride, spacing, spacing);

	-- Frames are repositioned depending on if the paging control is visible.
	local yOffset = requiresPages and -142 or -185;
	local anchor = CreateAnchor("TOPLEFT", self, "TOPLEFT", 50, yOffset);
	AnchorUtil.GridLayout(self.optionFrames, anchor, layout);

	self:SetFixedSize(910, 671);
	self:Layout();
end

function PlayerChoiceFrameMixin:AlignOptionHeights(skipAlignSections)
	-- First, loop through all options once, calculating maxOptionHeight
	-- As we loop, we call AlignSections on each, which aligns the heights of all height-aligned sections.
	-- If skipAlignSections is true, we skip the AlignSections call, and just calculate maxOptionHeight
	local maxOptionHeight = 0;
	for optionFrame in self.optionPools:EnumerateActiveByTemplate(self.optionFrameTemplate) do
		if not skipAlignSections then
			optionFrame:AlignSections();
		end
		optionFrame:Layout();
		maxOptionHeight = math.max(maxOptionHeight, optionFrame:GetHeight());
	end

	-- Then call SetMinHeight on each option, using maxOptionHeight. Each option mixin can choose to add padding however it sees fit (or ignore that min height entirely)
	for optionFrame in self.optionPools:EnumerateActiveByTemplate(self.optionFrameTemplate) do
		optionFrame:SetMinHeight(maxOptionHeight);
		optionFrame:Show();
	end

	self.optionsAligned = true;

	-- Now that all the options have been had their heights aligned, call Layout on ourselves to adjust our own height accordingly
	self:Layout();
end

function PlayerChoiceFrameMixin:AreOptionsAligned()
	return self.optionsAligned;
end

function PlayerChoiceFrameMixin:IsLegacy()
	return self.isLegacy;
end
