--- @class StdUi
local StdUi = LibStub and LibStub('StdUi', true);
if not StdUi then
	return
end

local module, version = 'ContextMenu', 3;
if not StdUi:UpgradeNeeded(module, version) then
	return
end

--- ContextMenuItem Events

local ContextMenuItemOnEnter = function(itemFrame, button)
	itemFrame.parentContext:CloseSubMenus();

	itemFrame.childContext:ClearAllPoints();
	itemFrame.childContext:SetPoint('TOPLEFT', itemFrame, 'TOPRIGHT', 0, 0);
	itemFrame.childContext:Show();
end

local ContextMenuItemOnMouseUp = function(itemFrame, button)
	local hide
	if button == 'LeftButton' and itemFrame.contextMenuData.callback then
		hide = itemFrame.contextMenuData.callback(itemFrame, itemFrame.parentContext)
	elseif button == 'RightButton' then
		hide = true
	end
	if hide == true and itemFrame.mainContext then
		itemFrame.mainContext:Hide()
	end
end

--- ContextMenuEvents

local ContextMenuOnMouseUp = function(self, button)
	if button == 'RightButton' then
		local uiScale = UIParent:GetScale();
		local cursorX, cursorY = GetCursorPosition();

		cursorX = cursorX / uiScale;
		cursorY = cursorY / uiScale;

		self:ClearAllPoints();

		if self:IsShown() then
			self:Hide();
		else
			self:SetPoint('TOPLEFT', nil, 'BOTTOMLEFT', cursorX, cursorY);
			self:Show();
		end
	end
end

---@type ContextMenu
StdUi.ContextMenuMethods = {

	CloseMenu         = function(self)
		self:CloseSubMenus();
		self:Hide();
	end,

	CloseSubMenus     = function(self)
		for i = 1, #self.optionFrames do
			local optionFrame = self.optionFrames[i];
			if optionFrame.childContext then
				optionFrame.childContext:CloseMenu();
			end
		end
	end,

	HookRightClick    = function(self)
		local parent = self:GetParent();
		if parent then
			-- ContextMenuOnMouseUp requires a reference to this menu (self)
			local menu = self -- don't trust magic variable names
			parent:HookScript('OnMouseUp', function (_, button) ContextMenuOnMouseUp(menu, button) end);
		end
	end,

	HookChildrenClick = function(self)

	end,

	CreateItem        = function(parent, data, i)
		local itemFrame;

		if data.constructor and type(data.constructor) == 'function' then
			itemFrame = data.constructor(parent, data, i);
		elseif data.title then
			itemFrame = parent.stdUi:Frame(parent, nil, 20);
			itemFrame.text = parent.stdUi:Label(itemFrame);
			parent.stdUi:GlueLeft(itemFrame.text, itemFrame, 0, 0, true);
		elseif data.isSeparator then
			itemFrame = parent.stdUi:Frame(parent, nil, 20);
			itemFrame.texture = parent.stdUi:Texture(itemFrame, nil, 8,
				[[Interface\COMMON\UI-TooltipDivider-Transparent]]);
			itemFrame.texture:SetPoint('CENTER');
			itemFrame.texture:SetPoint('LEFT');
			itemFrame.texture:SetPoint('RIGHT');
		elseif data.checkbox then
			itemFrame = parent.stdUi:Checkbox(parent, '');
		elseif data.radio then
			itemFrame = parent.stdUi:Radio(parent, '', data.radioGroup);
		elseif data.text then
			itemFrame = parent.stdUi:HighlightButton(parent, nil, 20);
		end

		itemFrame.contextMenuData = data;

		-- Need mainContext on all items for right click compatibility.
		-- This will also keep propagating mainContext thru all children.
		-- Note: In the top-most level of items frames, the parent does NOT have a
		-- mainContext, and in that case the parent itself IS the mainContext.
		itemFrame.mainContext = parent.mainContext or parent

		if not data.isSeparator then
			itemFrame.text:SetJustifyH('LEFT');
		end

		if not data.isSeparator and data.children then
			itemFrame.icon = parent.stdUi:Texture(itemFrame, 10, 10, [[Interface\Buttons\SquareButtonTextures]]);
			itemFrame.icon:SetTexCoord(0.42187500, 0.23437500, 0.01562500, 0.20312500);
			parent.stdUi:GlueRight(itemFrame.icon, itemFrame, -4, 0, true);

			itemFrame.childContext = parent.stdUi:ContextMenu(parent, data.children, true, parent.level + 1);
			itemFrame.parentContext = parent;

			itemFrame:HookScript('OnEnter', ContextMenuItemOnEnter);
		end

		if data.events then
			for eventName, eventHandler in pairs(data.events) do
				itemFrame:SetScript(eventName, eventHandler);
			end
		end

		-- Always need Right click capability in item frames to close the menu
		if data.hookOnClickIndicator then
			itemFrame:HookScript('OnClick', ContextMenuItemOnMouseUp)
		else
			itemFrame:SetScript('OnMouseUp', ContextMenuItemOnMouseUp)
		end

		if data.custom then
			for key, value in pairs(data.custom) do
				itemFrame[key] = value;
			end
		end

		return itemFrame;
	end,

	UpdateItem        = function(parent, itemFrame, data, i)
		local padding = parent.padding;

		if data.renderer and type(data.renderer) == 'function' then
			data.renderer(parent, itemFrame, data, i);
		elseif data.title then
			itemFrame.text:SetText(data.title);
			parent.stdUi:ButtonAutoWidth(itemFrame);
		elseif data.checkbox or data.radio then
			itemFrame.text:SetText(data.checkbox or data.radio);
			itemFrame:AutoWidth();
			if data.value then
				itemFrame:SetValue(data.value);
			end
		elseif data.text then
			itemFrame:SetText(data.text);
			parent.stdUi:ButtonAutoWidth(itemFrame);
		end

		if data.children then
			-- add arrow size
			itemFrame:SetWidth(itemFrame:GetWidth() + 16);
		end

		if (parent:GetWidth() - padding * 2) < itemFrame:GetWidth() then
			parent:SetWidth(itemFrame:GetWidth() + padding * 2);
		end

		itemFrame:SetPoint('LEFT', padding, 0);
		itemFrame:SetPoint('RIGHT', -padding, 0);

		if data.color and not data.isSeparator then
			itemFrame.text:SetTextColor(unpack(data.color));
		end
	end,

	DrawOptions       = function(self, options)
		if not self.optionFrames then
			self.optionFrames = {};
		end

		local _, totalHeight = self.stdUi:ObjectList(
			self,
			self.optionFrames,
			self.CreateItem,
			self.UpdateItem,
			options,
			0,
			self.padding,
			-self.padding
		);

		self:SetHeight(totalHeight + self.padding);
	end,

	StartHideCounter  = function(self)
		if self.timer then
			self.timer:Cancel();
		end
		self.timer = C_Timer:NewTimer(3, self.TimerCallback);
	end,

	StopHideCounter   = function()

	end
};

StdUi.ContextMenuEvents = {
	OnEnter = function(self)

	end,
	OnLeave = function(self)

	end
};

function StdUi:ContextMenu(parent, options, stopHook, level)
	---@class ContextMenu
	local panel = self:Panel(parent);
	panel.stdUi = self;
	panel.level = level or 1;
	panel.padding = 16;

	panel:SetFrameStrata('FULLSCREEN_DIALOG');

	-- force context menus to stay on the screen where they can be used
	panel:SetClampedToScreen(true)

	for k, v in pairs(self.ContextMenuMethods) do
		panel[k] = v;
	end

	for k, v in pairs(self.ContextMenuEvents) do
		panel:SetScript(k, v);
	end

	panel:DrawOptions(options);

	if panel.level == 1 then
		-- self reference for children
		panel.mainContext = panel;
		if not stopHook then
			panel:HookRightClick();
		end
	end

	panel:Hide();

	return panel;
end

StdUi:RegisterModule(module, version);