-- Conditionally modify macros
-- Make a Plumber Macro by adding #plumber:[command] to the top of the macro body
-- Implementations:
-- 1. plumber:drawer        Create a custom SpellFlyout by adding # to your regular macro. E.g. #/use:Dalaran Hearthsone
-- 2. plumber:drive         Added to your regular mount macro. Summon G-99 Breakneck in Undermine. Change the icon.


local _, addon = ...
--if addon.IS_MIDNIGHT then return end;     --DEBUG Functioning again in 64228, praise the Absolute!


-- User Settings
local HIDE_UNUSABLE = false;        --Hide unusable spells
local UPDATE_FREQUENTLY = false;    --Update drawers when BAG_UPDATE_DELAYED, SPELLS_CHANGED
------------------

local L = addon.L;
local API = addon.API;
local CallbackRegistry = addon.CallbackRegistry;
--local SpellFlyout = addon.SpellFlyout;  --Unused, Insecure
local SecureSpellFlyout = addon.SecureSpellFlyout;
local GetDBValue = addon.GetDBValue;

local find = string.find;
local match = string.match;
local gmatch = string.gmatch;
local gsub = string.gsub;
local format = string.format;
local tinsert = table.insert;
local pairs = pairs;
local ipairs = ipairs;
local strlenutf8 = strlenutf8;
local InCombatLockdown = InCombatLockdown;
local GetMacroBody = GetMacroBody;
local GetMacroInfo = GetMacroInfo;
local GetMacroIndexByName = GetMacroIndexByName;
local EditMacro = EditMacro;
local GetNumMacros = GetNumMacros;
local GetActiveAbilities = C_ZoneAbility and C_ZoneAbility.GetActiveAbilities or API.Nop;
local GetMountInfoByID = C_MountJournal and C_MountJournal.GetMountInfoByID or API.Nop;
local FindSpellOverrideByID = C_SpellBook and C_SpellBook.FindSpellOverrideByID or FindSpellOverrideByID or API.Nop;
local GetActionInfo = GetActionInfo;
local GetCursorInfo = GetCursorInfo;
local GetCurrentKeyBoardFocus = GetCurrentKeyBoardFocus;
local CreateFrame = CreateFrame;

local DoesItemReallyExist = API.DoesItemReallyExist;
local GetItemIDForItemInfo = C_Item.GetItemIDForItemInfo;
local GetItemNameByID = C_Item.GetItemNameByID;
local GetItemIconByID = C_Item.GetItemIconByID;
local RequestLoadItemDataByID = C_Item.RequestLoadItemDataByID;
local RequestLoadSpellData = C_Spell.RequestLoadSpellData;
local GetSpellName = C_Spell.GetSpellName;
local GetSpellTexture = C_Spell.GetSpellTexture;
local GetSpellInfo = C_Spell.GetSpellInfo;
local CanPlayerPerformAction = API.CanPlayerPerformAction;
local GetItemCraftingQuality = API.GetItemCraftingQuality;


local MacroInterpreter = {};    --Add info to tooltip
local EditorUI = {};            --Attach to MacroFrame once it loaded
local EditorSetup = {};         --Setup the editor when viewing supported Plumber Macro
local DrawerUpdator = CreateFrame("Frame");     --Optional. Update button states when bag, spell change. (Flagged dirty after certain events, update all drawers unpon mouse over Action Button or entering combat)


local ModifyType = {
	None = 0,
	Add = 1,
	Overwrite = 2,
};


local function AddExtraLineToMacroBody(extraLine, body)
	extraLine = "\n\n"..extraLine;
	local numRequired = strlenutf8(extraLine) + 3;
	local numTotal = strlenutf8(body);
	local overflow;
	if numTotal + numRequired >= 255 then
		overflow = true;
		local pattern = "$";
		local n = 0;
		while n < numRequired do
			n = n + 1;
			pattern = "."..pattern;
		end
		body = gsub(body, pattern, extraLine);
	else
		overflow = false;
		body = body..extraLine;
	end
	return body, overflow
end

local PlumberMacros = {};
do
	PlumberMacros["drive"] = {
		name = L["PlumberMacro Drive"],
		type = ModifyType.Add,
		spellID = 460013,

		events = {
			"SPELLS_CHANGED",
		},

		conditionFunc = function()
			local uiMapID = API.GetPlayerMap();
			if uiMapID == 2346 or uiMapID == 2374 or uiMapID == 2406 or uiMapID == 2407 or uiMapID == 2408 or uiMapID == 2409 or uiMapID == 2411 or uiMapID == 2428 then
				return true
			end
			local abilities = GetActiveAbilities();
			if abilities then
				for i, ability in ipairs(abilities) do
					if ability.spellID and FindSpellOverrideByID(ability.spellID) == 460013 then
						return true
					end
				end
			end
			return false
		end,

		addFunc = function()
			local spellName = (GetSpellName(PlumberMacros["drive"].spellID)) or "G-99 Breakneck";
			--return "/cast spell:460013\n/cast [noswimming] "..spellName
			return "/cast "..spellName
			--The first /cast doesn't get executed but it's necessary to make GetActionInfo() return the macroIndex instead of spellID
			--when applying [noswimming], the game will think the ZoneAbility isn't on the ActionBar and display the ZoneAbilityFrame
		end,

		trueReturn = {
			icon = 1408996,
		},

		falseReturn = {
			icon = 0,
			bestIconFunc = function(body)
				if find(body, "C_MountJournal.SummonByID(0)", 1, true) then
					return 413588
				end
			end
		},
	};

	PlumberMacros["drawer"] = {
		name = L["PlumberMacro Drawer"],
		type = ModifyType.None,
		--alwaysUpdate = true,

		conditionFunc = function ()
			return true
		end,
	};
end

local IsMacroSpell = {};


local EL = CreateFrame("Frame");
EL.macroIndexMin1 = 1;
EL.macroIndexMax1 = MAX_ACCOUNT_MACROS or 120;
EL.macroIndexMin2 = EL.macroIndexMax1 + 1;
EL.maxCharacterMacros = MAX_CHARACTER_MACROS or 30;
EL.macroIndexMax2 = EL.macroIndexMin2 + EL.maxCharacterMacros;
EL.macroEvents = {};
EL.macroIndexCommandInfo = {};


function EL:CheckSupportedMacros()
	self:UnregisterAllEvents();

	if not self.isInitialized then
		self:RegisterEvent("PLAYER_ENTERING_WORLD");
	end

	for command, commandInfo in pairs(PlumberMacros) do
		commandInfo.currentState = nil;
	end

	self.macroEvents = {};
	self.activeCommands = {};
	self.macroIndexCommandInfo = {}; -- [macroIndex] = commandInfo

	MacroInterpreter.macroCommand = {};

	local body;
	local command, commandInfo;
	local n = 0;
	local numAccountMacros, numCharacterMacros = GetNumMacros();

	for i = 1, 2 do
		local fromIndex, toIndex;
		if i == 1 then
			fromIndex = self.macroIndexMin1;
			toIndex = fromIndex + numAccountMacros - 1;
		else
			fromIndex = self.macroIndexMin2;
			toIndex = fromIndex + numCharacterMacros - 1;
		end

		if toIndex >= fromIndex then
			for index = fromIndex, toIndex do
				body = GetMacroBody(index);
				if body then
					command = match(body, "#plumber:(%w+)");
					commandInfo = command and PlumberMacros[command];
					if commandInfo then
						n = n + 1;

						local overrideCommand;

						if commandInfo.shouldUseDrawer and commandInfo.shouldUseDrawer() then
							overrideCommand = "drawer";
							self.macroIndexCommandInfo[index] = commandInfo;
						end

						if overrideCommand then
							command = overrideCommand;
						end

						if not self.activeCommands[command] then
							self.activeCommands[command] = {};
						end
						tinsert(self.activeCommands[command], index);

						if commandInfo.events then
							for _, event in ipairs(commandInfo.events) do
								if not self.macroEvents[event] then
									self.macroEvents[event] = {};
								end
								tinsert(self.macroEvents[event], {
									index = index,
									command = command,
								});
							end
						end

						MacroInterpreter.macroCommand[index] = command;
					end
				end
			end
		end
	end

	self.anySupported = n > 0;

	if self.anySupported then
		--print("Plumber Macro Found:", n);
		MacroInterpreter:Init();
		for event in pairs(self.macroEvents) do
			if event == "UNIT_AURA" then
				self:RegisterUnitEvent(event, "player");
			else
				self:RegisterEvent(event);
			end
		end
	end

	DrawerUpdator:SetEnabled(self.activeCommands["drawer"] ~= nil);
end

function EL:RequestUpdateMacroByEvent(event)
	if not self.eventQueue then
		self.eventQueue = {};
	end
	self.t = -0.5;
	self.eventQueue[event] = true;
	if self:GetScript("OnUpdate") == nil then
		self:SetScript("OnUpdate", self.OnUpdate_UpdateMacroByEvent);
	end
end

function EL:OnUpdate_UpdateMacroByEvent(elapsed)
	self.t = self.t + elapsed;
	if self.t > 0 then
		self.t = nil;
		self:SetScript("OnUpdate", nil);
		if self.eventQueue then
			local events = {};
			local n = 0;
			for event in pairs(self.eventQueue) do
				n = n + 1;
				events[n] = event;
			end
			self.eventQueue = nil;

			for _, event in ipairs(events) do
				if self.macroEvents[event] then
					self:UpdateMacroByEvent(event);
				end
			end
		end
	end
end

function EL:UpdateMacroByEvent(event)
	if GetCurrentKeyBoardFocus() ~= nil and EditorUI:IsEditBoxFocused() then
		return
	end

	local commands = {};

	for _, info in ipairs(self.macroEvents[event]) do
		if not commands[info.command] then
			commands[info.command] = {};
		end
		tinsert(commands[info.command], info.index);
	end

	self:UpdateMacros(commands);
end

function EL:UpdateMacros(commands)
	if self.anySupported then
		commands = commands or self.activeCommands;

		local inCombat = InCombatLockdown();
		local anyChange, newState, returns;
		local name, icon, body, anyEdit;
		local commandInfo;
		local prefix;

		local updateList = false;

		for command, list in pairs(commands) do
			commandInfo = PlumberMacros[command];
			newState = commandInfo.conditionFunc();
			if commandInfo.alwaysUpdate or newState ~= commandInfo.currentState then
				anyChange = true;
				if not inCombat then
					commandInfo.currentState = newState;
					returns = nil;
					if newState then
						returns = commandInfo.trueReturn;
					else
						returns = commandInfo.falseReturn;
					end

					for _, index in ipairs(list) do
						name, icon, body = GetMacroInfo(index);

						if command == match(body, "#plumber:(%w+)") then
							anyEdit = false;

							if returns and returns.icon then
								if returns.icon ~= icon then
									anyEdit = true;
								end
								icon = returns.icon;
								if icon == 0 then
									if returns.bestIconFunc then
										icon = returns.bestIconFunc(body);
									end
									icon = icon or 134400;
								end
							end

							if commandInfo.type == ModifyType.Add and commandInfo.addFunc then
								prefix = "#plumber:"..command;
								body = gsub(body, ".+##", "");
								body = gsub(body, prefix, "");
								while find(body, "^\n") do
									body = gsub(body, "^\n", "");
								end
								if newState then
									local extraLine = commandInfo.addFunc();
									body = prefix.."\n"..extraLine.."\n##\n"..body;
								else
									body = prefix.."\n"..body;
								end
								anyEdit = true;
							end

							if (commandInfo.writeFunc) and ( (not commandInfo.shouldUseDrawer) or (not commandInfo.shouldUseDrawer()) ) then
								local _body, _icon = commandInfo.writeFunc(body);
								--print(math.random(100), _body)    --debug
								if _body then
									body = _body;
								end
								if _icon then
									icon = _icon;
								end
								anyEdit = true;
							end

							if anyEdit then
								EditMacro(index, name, icon, body);
							end
						else
							--This is when the body doesn't match our last cache
							--Possibly due to macros update without using MacroFrame (using other macro addons)
							updateList = true;
						end
					end
				end
			end
		end

		if anyChange and inCombat then
			self:RegisterEvent("PLAYER_REGEN_ENABLED");
		end

		if updateList then
			self:CheckSupportedMacros();
		end
	end
end


local DrawerUpdateFlag = {
	Combat = 0,
	Started = 1,
	Success = 2,
};
EL.drawerUpdateFlag = DrawerUpdateFlag.Combat;

function EL:InitializeDrawerInfo()
	self:CheckSupportedMacros();
	local drawers = self.activeCommands and self.activeCommands["drawer"];
	if drawers and #drawers > 0 then
		local name, icon, body;
		for _, macroIndex in ipairs(drawers) do
			name, icon, body = GetMacroInfo(macroIndex);
			MacroInterpreter:GetDrawerInfo(body);
		end
	end
end

function EL:UpdateDrawers()
	if InCombatLockdown() then
		self.drawerUpdateFlag = DrawerUpdateFlag.Combat;
		return
	end
	self.drawerUpdateFlag = DrawerUpdateFlag.Started;

	SecureSpellFlyout:ReleaseClickHandlers();
	local drawers = self.activeCommands and self.activeCommands["drawer"];
	if drawers and #drawers > 0 then
		local name, icon, body, drawerInfo, overflow, anyOverflow, anyChange;
		local handlerName;
		local checkUsability = true;
		local hideUnusable = HIDE_UNUSABLE;
		local alwaysShowConsumables = not UPDATE_FREQUENTLY;
		local commandInfo;

		for _, macroIndex in ipairs(drawers) do
			commandInfo = self.macroIndexCommandInfo[macroIndex];

			if commandInfo and commandInfo.shouldUseDrawer() and commandInfo.getOverrideDrawerInfo then
				drawerInfo = commandInfo.getOverrideDrawerInfo();
				name = commandInfo.name;
				body, icon = commandInfo.writeFunc();
				body = "#plumber:"..commandInfo.command;
			else
				name, icon, body = GetMacroInfo(macroIndex);
				drawerInfo = MacroInterpreter:GetDrawerInfo(body, checkUsability, hideUnusable, alwaysShowConsumables);
			end

			if drawerInfo then
				--print(name, #drawerInfo)
				handlerName = SecureSpellFlyout:AddActionsAndGetHandler(drawerInfo);
				if handlerName then
					body, anyChange = SecureSpellFlyout:RemoveClickHandlerFromMacro(body);
					local extraLine = "/click "..handlerName;
					body, overflow = AddExtraLineToMacroBody(extraLine, body);
					EditMacro(macroIndex, name, icon, body);
					if overflow then
						anyOverflow = true;
					end
				end
			end
		end

		if anyOverflow then
			self:UpdateDrawers();
		end
	end

	self.drawerUpdateFlag = DrawerUpdateFlag.Success;
end

function EL:UpdateMacrosAndDrawers()
	if InCombatLockdown() then
		self:RegisterEvent("PLAYER_REGEN_ENABLED");
		return
	end
	self:UnregisterEvent("PLAYER_REGEN_ENABLED");
	self:UpdateMacros();
end

function EL:OnUpdate_UpdateMacros(elapsed)
	self.t = self.t + elapsed;
	if self.t > 0 then
		self.t = nil;
		self:SetScript("OnUpdate", nil);

		if self.macroCheckPending then
			self.macroCheckPending = nil;
			self:CheckSupportedMacros();
		end

		if self.macroUpdatePending then
			self.macroUpdatePending = nil;
			self:UpdateMacros();
		end

		if self.drawerUpdatePending then
			self.drawerUpdatePending = nil;
			DrawerUpdator:RequestUpdate(0);
		end
	end
end

function EL:RequestUpdateMacros(delay)
	delay = delay and -delay or 0;
	self.t = delay;
	self.macroCheckPending = true;
	self.macroUpdatePending = true;
	self.drawerUpdatePending = true;
	self:SetScript("OnUpdate", self.OnUpdate_UpdateMacros);
end

function EL:RequestCheckMacros(delay)
	delay = delay and -delay or 0;
	self.t = delay;
	self.macroCheckPending = true;
	self:SetScript("OnUpdate", self.OnUpdate_UpdateMacros);
end

function EL:LoadSpellAndItem()
	for _, commandInfo in pairs(PlumberMacros) do
		if commandInfo.spellID then
			RequestLoadSpellData(commandInfo.spellID);
			IsMacroSpell[commandInfo.spellID] = true;
		end
		if commandInfo.itemID then
			RequestLoadItemDataByID(commandInfo.itemID);
		end
	end
end


do  --EditorUI  --MacroForge
	function EditorUI:OnLoad()
		self.OnLoad = nil;

		self:SetScript("OnShow", self.OnShow);
		self:SetScript("OnHide", self.OnHide);

		self.args = {};
		self.objectPools = {};

		if self.SourceEditBox then
			self.SourceEditBox:HookScript("OnEditFocusGained", EditorUI.OnEditFocusGained);
			self.SourceEditBox:HookScript("OnEditFocusLost", EditorUI.OnEditFocusLost);
			self.SourceEditBox:HookScript("OnTextChanged", EditorUI.OnTextChanged);
		end
	end

	function EditorUI:OnShow()
		self.args = {};
		if self.ExtraFrame then
			self.ExtraFrame:UpdatePixel();
		end
		self:RequestSearchInEditBox();
	end

	function EditorUI:OnHide()
		EL:RequestUpdateMacros();
		self.isEditing = nil;
		self.t = 0;
		self:SetScript("OnUpdate", nil);
		self:SetScript("OnEvent", nil);
		self:HideUI();
		self:ReleaseElements();
	end

	function EditorUI.OnEditFocusGained(editBox)
		EditorUI.isEditing = true;
		EditorUI:RequestSearchInEditBox();
	end

	function EditorUI.OnEditFocusLost(editBox)
		EditorUI.isEditing = nil;
	end

	function EditorUI.OnTextChanged(editBox, userInput)
		if userInput then
			EditorUI:RequestSearchInEditBox(0.5);
		else
			EditorUI:RequestSearchInEditBox();
		end
	end

	function EditorUI:RequestSearchInEditBox(delay)
		delay = delay or 0.016;
		self.t = -delay;
		self:SetScript("OnUpdate", EditorUI.OnUpdate_EditBox);
	end

	function EditorUI:OnUpdate_EditBox(elapsed)
		self.t = self.t + elapsed;
		if self.t > 0 then
			self.t = 0;
			self:SetScript("OnUpdate", nil);
			self:SearchInEditBoxForSupportedCommand();
		end
	end

	function EditorUI:HideUI()
		self.args = {};
		if self.ExtraFrame then
			self.ExtraFrame:Hide();
		end
		if self.MouseBlocker then
			self.MouseBlocker:Hide();
		end
	end

	function EditorUI:TriggerMouseBlocker()
		if self.MouseBlocker then
			self.MouseBlocker:Show();
			C_Timer.After(0, function()
			self.MouseBlocker:Hide();
			end)
		end
	end

	function EditorUI:SearchInEditBoxForSupportedCommand(forceUpdate)
		if forceUpdate then
			self.args = {};
		end
		local body = self.SourceEditBox:GetText();
		local command = match(body, "#plumber:(%w+)");
		if command and PlumberMacros[command] and self.ExtraFrame and PlumberMacros[command].editorSetupFunc then
			self.ExtraFrame:Show();
			PlumberMacros[command].editorSetupFunc(body);
		else
			self:HideUI();
		end
	end

	function EditorUI:SetFrameHeight(height)
		if self.ExtraFrame then
			self.ExtraFrame:SetHeight(height);
		end
	end

	function EditorUI:DisplayNote(text)
		self.Note:SetText(text);
	end

	function EditorUI:HighlightIconButton(iconButton, colorIndex)
		if self.HighlightFrame then
			self.HighlightFrame:Hide();
			self.HighlightFrame:ClearAllPoints();
		end
		if iconButton then
			self.HighlightFrame:SetParent(iconButton);
			self.HighlightFrame:SetPoint("TOPLEFT", iconButton.Border, "TOPLEFT", 0, 0);
			self.HighlightFrame:SetPoint("BOTTOMRIGHT", iconButton.Border, "BOTTOMRIGHT", 0, 0);
			self.HighlightFrame:Show();
			self.HighlightFrame:SetFrameLevel(iconButton:GetFrameLevel() + 1);

			if colorIndex == 1 then --Green
				self.HighlightFrame.Texture:SetTexCoord(416/512, 488/512, 0, 72/512);
			elseif colorIndex == 2 then --Red
				self.HighlightFrame.Texture:SetTexCoord(416/512, 488/512, 72/512, 144/512);
			else --Yellow
				self.HighlightFrame.Texture:SetTexCoord(344/512, 416/512, 0, 72/512);
			end
		end
	end

	function EditorUI:RaiseIconButtonLevel(iconButton)
		if self.objectPools.iconButtonPool then
			local baseFrameLevel = self.ExtraFrame:GetFrameLevel() + 1;
			for i, object in ipairs(self.objectPools.iconButtonPool.objects) do
				if object == iconButton then
					object:SetFrameLevel(baseFrameLevel + 20);
				else
					object:SetFrameLevel(baseFrameLevel + i);
				end
			end
		end
	end

	function EditorUI:ReleaseElements()
		self:DisplayNote(nil);
		self:HighlightIconButton(nil);

		if self.objectPools then
			for _, pool in pairs(self.objectPools) do
				pool:ReleaseAll();
			end
		end

		if self.ReorderController then
			self.ReorderController:Stop();
		end

		if self.otherElements then
			for _, object in ipairs(self.otherElements) do
				object:Hide();
				object:ClearAllPoints();
			end
		end
	end

	function EditorUI:AddRemovableElements(object)
		if not self.otherElements then
			self.otherElements = {};
		end
		tinsert(self.otherElements, object);
	end

	function EditorUI:GetCurrentMacroIndex()
		local selectedMacroIndex = MacroFrame:GetSelectedIndex();
		local actualIndex = MacroFrame:GetMacroDataIndex(selectedMacroIndex);
		return actualIndex
	end

	function EditorUI:SaveMacroBody(body)
		if not InCombatLockdown() then
			EditorUI.SourceEditBox:SetText(body);
			--MacroFrame:SaveMacro();
			EditMacro(self:GetCurrentMacroIndex(), nil, nil, body);
			EL:RequestUpdateMacros(0.0);
		end
	end

	function EditorUI:SetMacroIcon(icon)
		if not InCombatLockdown() then
			EditMacro(self:GetCurrentMacroIndex(), nil, icon);
			EL:RequestUpdateMacros(0.0);
		end
	end

	function EditorUI:IsEditBoxFocused()
		return self.SourceEditBox and self.SourceEditBox.HasFocus and self.SourceEditBox:HasFocus()
	end
end


local function CreateEditorUI(parent)
	if not parent then return end;

	local f = CreateFrame("Frame", nil, parent);
	API.Mixin(f, EditorUI);
	EditorUI = f;
	f:OnLoad();

	local ef = API.CreateNewSliceFrame(f, "RoughWideFrame");
	f.ExtraFrame = ef;
	ef:Hide();
	ef:SetScript("OnHide", function()
		ef:Hide();
		ef:UnregisterAllEvents();
	end);
	ef:SetScript("OnShow", function()
		ef:UpdatePixel();
	end);
	local offsetY = -4;
	ef:SetPoint("TOPLEFT", parent, "BOTTOMLEFT", 0, offsetY);
	ef:SetPoint("TOPRIGHT", parent, "BOTTOMRIGHT", 0, offsetY);
	ef:EnableMouse(true);
	ef:SetFrameStrata("HIGH");

	f.Note = ef:CreateFontString(nil, "OVERLAY", "GameFontNormal");
	f.Note:SetJustifyH("CENTER");
	f.Note:SetTextColor(0.8, 0.8, 0.8);
	f.Note:SetPoint("LEFT", ef, "LEFT", 16, 0);
	f.Note:SetPoint("RIGHT", ef, "RIGHT", -16, 0);
	f.Note:SetSpacing(2);

	f.HighlightFrame = CreateFrame("Frame", nil, ef);
	f.HighlightFrame.Texture = f.HighlightFrame:CreateTexture(nil, "OVERLAY");
	f.HighlightFrame.Texture:SetAllPoints(true);
	f.HighlightFrame:Hide();

	f.MouseBlocker = CreateFrame("Frame", nil, ef);
	f.MouseBlocker:SetAllPoints(true);
	f.MouseBlocker:SetFrameLevel(128);
	f.MouseBlocker:SetFixedFrameLevel(true);
	f.MouseBlocker:EnableMouse(true);
	f.MouseBlocker:Hide();

	f:SetFrameHeight(72);
	f:RequestSearchInEditBox();
end

local function RequestUpdateMacros()
	EL:RequestUpdateMacros();
end

local function CreateEditorUI_Blizzard()
	if MacroSaveButton then
		MacroSaveButton:HookScript("OnClick", RequestUpdateMacros);
	end

	if MacroFrameText and MacroFrameText:IsObjectType("EditBox") then
		EditorUI.SourceEditBox = MacroFrameText;
	end

	if DeleteMacro then
		hooksecurefunc("DeleteMacro", RequestUpdateMacros);
	end

	local frame = MacroFrame;
	if frame then
		CreateEditorUI(frame);

		--We can't use this because of securecall("MacroFrame_SaveMacro") in SECURE_ACTIONS.action
		--Interface/AddOns/Blizzard_FrameXML/Mainline/SecureTemplates.lua
		--[[
			if frame.SaveMacro then
				hooksecurefunc(frame, "SaveMacro", RequestUpdateMacros);
			end
		--]]

		if frame.SelectMacro then
			--SelectMacro triggered by "UPDATE_MACROS"
			hooksecurefunc(frame, "SelectMacro", function()
				EL:RequestCheckMacros();
			end);
		end
	end
end

local function CreateEditorUI_MacroToolkit()
	--Note: MacroToolkit causes a breif fps drop when formatting our drawer commands with or without Plumber enabled.

	local SaveButton = MacroToolkitSave;
	if SaveButton then
		SaveButton:HookScript("OnClick", function()
			EL:RequestUpdateMacros();
		end);
	end

	if MacroToolkitText and MacroToolkitText:IsObjectType("EditBox") then
		EditorUI.SourceEditBox = MacroToolkitText;
	end

	EditorUI.macroFrameHooked = true;

	if MacroToolkitFrame then
		CreateEditorUI(MacroToolkitFrame);

		local pauseUpdate;

		local function UpdateMacroTooltipFrame()
			if not pauseUpdate then
				pauseUpdate = true;
				C_Timer.After(0.03, function()
					pauseUpdate = nil;
					if not InCombatLockdown() then
						securecall(MacroToolkit.MacroFrameUpdate, MacroToolkit);
					end
				end);
			end
		end

		function EditorUI:GetCurrentMacroIndex()
			return MacroToolkitFrame.selectedMacro
		end

		function EditorUI:SaveMacroBody(body)
			if not InCombatLockdown() then
				EditorUI.SourceEditBox:SetText(body);
				EditMacro(self:GetCurrentMacroIndex(), nil, nil, body);
				SaveButton:Click("LeftButton");
				EL:RequestUpdateMacros(0.0);
				UpdateMacroTooltipFrame();
			end
		end

		function EditorUI:SetMacroIcon(icon)
			if not InCombatLockdown() then
				EditMacro(self:GetCurrentMacroIndex(), nil, icon);
				SaveButton:Click("LeftButton");
				EL:RequestUpdateMacros(0.0);
				UpdateMacroTooltipFrame();
			end
		end
	end
end


if MacroFrame_LoadUI then
	--UPDATE_MACROS fires when selecting any macro, without changing its content, so we update our macro after MarcoFrame is closed
	hooksecurefunc("MacroFrame_LoadUI", function()
		if not EditorUI.macroFrameHooked then
			if MacroFrame then
				EditorUI.macroFrameHooked = true;
				CreateEditorUI_Blizzard();
			end
		end
	end);
end


function EL:OnEvent(event, ...)
	--print(event, GetTimePreciseSec())
	if event == "PLAYER_ENTERING_WORLD" then
		self.isInitialized = true;
		self:UnregisterEvent(event);
		self:LoadSpellAndItem();
		self:RequestUpdateMacros(0.5);
		DrawerUpdator:RequestUpdate(0.7);

		if C_AddOns.IsAddOnLoaded("MacroToolkit") then
			CreateEditorUI_MacroToolkit();
		end
	elseif event == "PLAYER_REGEN_ENABLED" then
		self:UpdateMacrosAndDrawers();
	elseif event == "UPDATE_MACROS" then
		--UPDATE_MACROS usually fires twice before PLAYER_ENTERING_WORLD
		self:UnregisterEvent(event);
		self:InitializeDrawerInfo();
	elseif self.macroEvents and self.macroEvents[event] then
		self:RequestUpdateMacroByEvent(event);
	end
end
EL:SetScript("OnEvent", EL.OnEvent);
EL:RegisterEvent("UPDATE_MACROS");
EL:RegisterEvent("PLAYER_ENTERING_WORLD");



do  --MacroInterpreter
	MacroInterpreter.macroCommand = {};

	local MacroHandlers = {
		["(/randomfavoritepet)"] = "SetRandomFavoritePet",
		["(/randompet)"] = "SetRandomPet",
		["/summonpet%s+(.+)"] = "SetSummonPet",
		["/sp%s+(.+)"] = "SetSummonPet",
		["(/dismisspet)"] = "SetDismissPet",
		["/emote%s+(.+)"] = "SetCustomEmote",
		["/e%s+(.+)"] = "SetCustomEmote",
	};

	function MacroInterpreter:Init()
		self.Init = function() end;

		local tooltipDataType = Enum.TooltipDataType and Enum.TooltipDataType.Macro;
		if tooltipDataType and TooltipDataProcessor and TooltipDataProcessor.AddTooltipPostCall then
			local function Callback(tooltip)
				local tooltipData = tooltip.infoList and tooltip.infoList[1] and tooltip.infoList[1].tooltipData;
				if tooltipData and tooltipData.type == tooltipDataType then
					local action = tooltip.infoList[1].getterArgs[1];
					if action then
						local actionType, id, subType = GetActionInfo(action);
						--print(actionType, id, subType)
						if actionType == "macro" then
							if subType == "" then

							elseif subType == "spell" and IsMacroSpell[id] then
								local macroName = tooltipData.lines and tooltipData.lines[1]and tooltipData.lines[1].leftText;
								id = macroName and GetMacroIndexByName(macroName);
							else
								return
							end

							if id then
								self:TooltipSetMacro(tooltip, id);
							end
						end
					end
				end
			end
			TooltipDataProcessor.AddTooltipPostCall(tooltipDataType, Callback);
		else
			print("Plumber AddOn Alert: WoW\'s TooltipDataProcessor methods changed.")
		end
	end

	function MacroInterpreter:GetDrawerInfo(body, checkUsability, hideUnusable, alwaysShowConsumables, forEditor)
		if not body then return end;
		if not find(body, "#plumber:drawer") then return end;

		local tbl;
		local n = 0;
		local processed, usable;
		local name, icon, actionType, id, macroText, craftingQuality, tempID;
		local canPerform, isConsumable;

		for line in gmatch(body, "#(/[^\n]+)") do
			processed = false;
			usable = nil;
			name = nil;
			icon = nil;
			actionType = nil;
			id = nil;
			macroText = nil;
			tempID = nil;

			if not processed then
				tempID = match(line, "/use%s+mount:(%d+)");
				if not tempID then
					tempID = match(line, "/cast%s+mount:(%d+)");
				end
				if tempID then
					processed = true;
					actionType = "mount";
					id = tonumber(tempID);
				end
			end

			if not processed then
				tempID = match(line, "/sp%s+pet:(%d+)");
				if tempID then
					processed = true;
					actionType = "SetSummonPet";
					id = tonumber(tempID);
					name, usable = API.GetPetNameAndUsability(id, true);
					id = name;
					if name then
						macroText = "/sp "..name;
					end
				end
			end

			if not processed then
				id = match(line, "/cast%s+spell:(%d+)");
				id = tonumber(id);
				if id then
					processed = true;
					actionType = "spell";
					name = GetSpellName(tonumber(id));
				end
			end

			if not processed then
				name = match(line, "/cast%s+(.+)");
				if name then
					processed = true;
					actionType = "spell";
				end
			end

			if not processed then
				id = match(line, "/use%s+item:(%d+)");
				id = tonumber(id);
				if id then
					processed = true;
					actionType = "item";
				end
			end

			if not processed then
				name = match(line, "/use%s+(.+)");
				if name then
					processed = true;
					actionType = "item";
				end
			end

			if not processed then
				local professionIndex = match(line, "/prof(%d)");
				if professionIndex then
					processed = true;
					professionIndex = tonumber(professionIndex);
					if professionIndex == 1 or professionIndex == 2 then
						local info = API.GetProfessionSpellInfo(professionIndex);
						if info then
							icon = info.texture;
							name = info.name;
							id = info.spellID;
							actionType = "profession";
							macroText = format("/run PlumberGlobals.OpenProfessionFrame(%s)", professionIndex);
							usable = true;
						else
							icon = 134400;
							name = professionIndex == 1 and L["Drawer Add Profession1"] or L["Drawer Add Profession2"];
							id = -1;
							actionType = "profession";
							macroText = "";
							usable = false;
						end
					end
				end
			end

			if not processed then
				if find(line, "/home") then
					local h = addon.Housing;
					if h then
						if h.DoesPlayerHaveMultipleHomes() and not forEditor then
							-- Avoid showing two teleportHome buttons if it's the editor that requires the drawerInfo
							processed = true;
							actionType = nil; -- so the action won't be duplicated by the end

							if not tbl then
								tbl = {};
							end

							for _, info in ipairs(h.GetOverrideDrawerInfo_TeleportHome()) do
								n = n + 1;
								tbl[n] = info;
							end
						else
							processed = true;
							icon, macroText, name = h.GetDynamicTeleportAction();
							id = -1;
							actionType = "teleportHome";
							usable = true;
						end
					end
				end
			end

			if not processed then   --generic macro command match
				local arg;
				for pattern, handler in pairs(MacroHandlers) do
					arg = match(line, pattern);
					if arg then
						id = arg;
						actionType = handler;
						icon = 136243;  --trade_engineering
						processed = true;
						break
					end
				end
			end

			if actionType then
				if actionType == "spell" then
					if (not id) and name then
						local spellInfo = GetSpellInfo(name);
						if spellInfo then
							usable = true;
							id = spellInfo.spellID;
							icon = spellInfo.iconID;
						end
					end
					if id and (not icon) then
						icon = GetSpellTexture(id) or 134400;
					end
					if id and CanPlayerPerformAction(actionType, id) then
						usable = true;
					end
					if name then
						macroText = "/cast "..name;
					end
				elseif actionType == "item" then
					if (not id) and name then
						id = GetItemIDForItemInfo(name);
					end
					if id and DoesItemReallyExist(id) then
						name = GetItemNameByID(id);
						icon = GetItemIconByID(id);
						macroText = format("/use \"item:%d\"", id);
						craftingQuality = GetItemCraftingQuality(id);
						if name and craftingQuality then
							name = format("%s T%s", name, craftingQuality);
							--name = format("%s|A:Professions-ChatIcon-Quality-Tier%s:0:0|a", name, craftingQuality);
							--local markup = CreateAtlasMarkupWithAtlasSize("Professions-ChatIcon-Quality-Tier"..craftingQuality, 0, 0, nil, nil, nil, 0.5)
							--name = name..markup
						end
					end
				elseif actionType == "mount" then
					if id == 0 then --RandomFavoriteMount
						local _spellID = 150544;
						name = L["Random Favorite Mount"];
						icon = GetSpellTexture(_spellID);
						usable = true;
						macroText = "/run C_MountJournal.SummonByID(0)";
						id = _spellID;
					else
						local _name, _spellID, _icon, _isActive, _isUsable, _sourceType, _isFavorite, _isFactionSpecific, _faction, _shouldHideOnChar, _isCollected = GetMountInfoByID(id);
						name = GetSpellName(_spellID) or _name;
						icon = _icon;
						usable = _isCollected and not _shouldHideOnChar;
						macroText = format("/run C_MountJournal.SummonByID(%d)", id);
						id = _spellID;
					end
				elseif actionType == "profession" then
					--Action will be set in SpellFlyout_Secure.lua
				else
					name = name or line;
					macroText = macroText or line;
					usable = true;
				end

				if checkUsability and id and (usable == nil) then
					canPerform, isConsumable = CanPlayerPerformAction(actionType, id);
					if canPerform then
						usable = true;
					elseif isConsumable and alwaysShowConsumables then
						usable = true;
					end
				end

				if hideUnusable and not usable then
					id = nil;
				end

				if id and id ~= 0 then
					if not tbl then
						tbl = {};
					end

					n = n + 1;
					tbl[n] = {
						tooltipLineText = name,
						icon = icon,
						actionType = actionType,
						id = id,
						usable = usable,
						macroText = macroText,
						rawMacroText = line,
					};
				end
			end
		end

		return tbl or {}
	end

	function MacroInterpreter.drawer(tooltip, body)
		local drawerInfo = MacroInterpreter:GetDrawerInfo(body, true);
		if drawerInfo then
			if EL.drawerUpdateFlag == DrawerUpdateFlag.Combat then
				tooltip:AddLine(L["PlumberMacro DrawerFlag Combat"], 1, 0.1, 0.1, true);
			elseif EL.drawerUpdateFlag == DrawerUpdateFlag.Started then
				tooltip:AddLine(L["PlumberMacro DrawerFlag Stuck"], 1, 0.1, 0.1, true);
			end

			for _, info in ipairs(drawerInfo) do
				if info.tooltipLineText then
					if info.usable then
						tooltip:AddLine(info.tooltipLineText, 1, 0.82, 0);
					else
						tooltip:AddLine(info.tooltipLineText, 0.6, 0.6, 0.6);
					end
				end
			end
		end
	end

	function MacroInterpreter:TooltipSetMacro(tooltip, macroIndex)
		local command = self.macroCommand[macroIndex];
		if command and PlumberMacros[command] then
			tooltip:AddLine(PlumberMacros[command].name, 0, 0.8, 1);
			if self[command] then
				local body = GetMacroBody(macroIndex);
				self[command](tooltip, body);
			end

			tooltip:Show();
		end
	end
end


do  --Editor Setup
	EditorSetup.itemButtonSize = 32;
	EditorSetup.itemButtonGap = 4;
	EditorSetup.iconButtonContainerHeight = 72;

	function EditorSetup.AcquireIconButton()
		if not EditorUI.objectPools.iconButtonPool then
			EditorUI.objectPools.iconButtonPool = {};

			local file = "Interface/AddOns/Plumber/Art/Frame/MacroForge.png";

			local IconButtonMixin = {};

			local Placeholder = CreateFrame("Frame", nil, EditorSetup.IconButtonContainer);
			Placeholder:Hide();
			Placeholder:SetSize(32, 32);
			Placeholder.Border = Placeholder:CreateTexture(nil, "OVERLAY");
			Placeholder.Border:SetPoint("CENTER", Placeholder, "CENTER", 0, 0);
			Placeholder.Border:SetTexCoord(272/512, 344/512, 72/512, 144/512);
			Placeholder.Border:SetTexture(file);
			Placeholder.Icon = Placeholder:CreateTexture(nil, "OVERLAY");
			EditorUI:AddRemovableElements(Placeholder);
			API.DisableSharpening(Placeholder.Border);

			local ReorderController = API.CreateDragReorderController(EditorSetup.IconButtonContainer);
			EditorUI.ReorderController = ReorderController;

			ReorderController:SetOnDragStartCallback(function()
				ReorderController:SetObjects(EditorUI.objectPools.iconButtonPool:GetActiveObjects());
				EditorUI:HighlightIconButton(ReorderController:GetDraggedObject(), 1);
				Placeholder:Show();
				ReorderController:SetPlaceholder(Placeholder);
			end);

			ReorderController:SetOnDragEndCallback(function(draggedObject, delete)
				Placeholder:Hide();
				local body = "#plumber:drawer";
				for _, object in ipairs(ReorderController.objects) do
					if not(delete and object == draggedObject) then
						body = body.."\n#"..object.rawMacroText;
					end
				end
				EditorUI:SaveMacroBody(body);
			end);

			ReorderController:SetBoundary(EditorUI.ExtraFrame);

			ReorderController:SetInBoundaryCallback(function()
				EditorUI:HighlightIconButton(ReorderController:GetDraggedObject(), 1);
				Placeholder.Border:SetTexCoord(272/512, 344/512, 72/512, 144/512);
			end);

			ReorderController:SetOutBoundaryCallback(function()
				EditorUI:HighlightIconButton(ReorderController:GetDraggedObject(), 2);
				Placeholder.Border:SetTexCoord(344/512, 416/512, 72/512, 144/512);
			end);

			function IconButtonMixin:SetEffectiveSize(w)
				self:SetSize(w, w);
				self.Border:SetSize(w*72/64, w*72/64);
				self.Icon:SetSize(w*60/64, w*60/64);
				if self.IconOverlay then
					self.IconOverlay:SetSize(w * 36/64, w * 36/64);
				end
			end

			IconButtonMixin.SetEffectiveSize(Placeholder, EditorSetup.itemButtonSize);

			local hl = EditorUI.HighlightFrame.Texture;
			hl:SetTexture(file);
			hl:SetTexCoord(344/512, 416/512, 0, 72/512);
			API.DisableSharpening(hl);

			function IconButtonMixin:OnEnter()
				if ReorderController:IsDraggingObject() then
					return
				end

				EditorUI:HighlightIconButton(self);
				if self:ShowTooltip() then
					local tooltip = GameTooltip;
					if self.tooltipMethod or self.tooltipFunc then
						tooltip:AddLine(" ");
					end

					if InCombatLockdown() then
						tooltip:AddLine(L["PlumberMacro Error EditMacroInCombat"], 1, 0.1, 0.1, true);
					else
						tooltip:AddLine("<"..L["Click To Set Macro Icon"]..">", 0.1, 1, 0.1, true);
						tooltip:AddLine("<"..L["Drag To Reorder"]..">", 0.1, 1, 0.1, true);
					end

					tooltip:Show();

					if self:IsDataCached() then
						self.UpdateTooltip = nil;
					else
						self.UpdateTooltip = self.OnEnter;
					end
				end
			end

			function IconButtonMixin:OnLeave()
				GameTooltip:Hide();
				if ReorderController:IsDraggingObject() then
					return
				end
				EditorUI:HighlightIconButton(nil);
			end

			function IconButtonMixin:OnMouseDown(mouseButton)
				if mouseButton == "LeftButton" then
					if InCombatLockdown() then return end;

					GameTooltip:Hide();

					if IsControlKeyDown() then
						EditorUI:SetMacroIcon(self.Icon:GetTexture());
						return
					end

					ReorderController:SetDraggedObject(self);
					ReorderController:PreDragStart();
					EditorUI:RaiseIconButtonLevel(self);
				end
			end

			function IconButtonMixin:OnMouseUp()
				if ReorderController:IsDraggingObject() then
					EditorUI:TriggerMouseBlocker();
				end
				ReorderController:OnMouseUp();
			end

			function IconButtonMixin:OnLoad()
				self:SetScript("OnEnter", self.OnEnter);
				self:SetScript("OnLeave", self.OnLeave);
				self:SetScript("OnMouseDown", self.OnMouseDown);
				self:SetScript("OnMouseUp", self.OnMouseUp);
			end

			function IconButtonMixin:OnRemoved()
				self:ClearAction();
			end

			SecureSpellFlyout:PopulateButtonMixin(IconButtonMixin);

			local function CreateObject()
				local f = CreateFrame("Button", nil, EditorSetup.IconButtonFrame, "PlumberSmallIconButtonTemplate");

				f.Border:SetTexture(file);
				f.Border:SetTexCoord(272/512, 344/512, 0, 72/512);
				API.DisableSharpening(f.Border);

				f.IconOverlay = f:CreateTexture(nil, "OVERLAY");
				f.IconOverlay:SetPoint("BOTTOMRIGHT", f, "CENTER", 0 ,0);
				f.IconOverlay:Hide();
				f.IconOverlay:SetTexture("Interface/AddOns/Plumber/Art/Frame/SpellFlyout");

				API.Mixin(f, IconButtonMixin);
				f:OnLoad();
				f:SetEffectiveSize(EditorSetup.itemButtonSize);

				return f
			end
			EditorUI.objectPools.iconButtonPool = API.CreateObjectPool(CreateObject);
		end
		return EditorUI.objectPools.iconButtonPool:Acquire();
	end

	function EditorSetup.AcquireCheckbox()
		if not EditorUI.objectPools.checkboxPool then
			local function CreateCheckbox()
				return addon.CreateCheckbox(EditorUI.ExtraFrame);
			end
			EditorUI.objectPools.checkboxPool = API.CreateObjectPool(CreateCheckbox);
		end
		return EditorUI.objectPools.checkboxPool:Acquire();
	end

	local SupportedCursorInfo = {
		item = {
			command = "/use",
			argGetter = function(itemID, itemLink)
				local name = GetItemNameByID(itemID);
				return name, (itemID and "item:"..itemID) or nil
			end,
		},

		spell = {
			command = "/cast",
			argGetter = function(spellIndex, bookType, spellID, baseSpellID)
				local name = GetSpellName(spellID);
				local info;
				for i = 1, 2 do
					info = API.GetProfessionSpellInfo(i);
					if info and (spellID == info.spellID or baseSpellID == info.spellID) then
						if i == 1 then
							return L["Drawer Add Profession1"], nil, "/prof1"
						else
							return L["Drawer Add Profession2"], nil, "/prof2"
						end
					end
				end
				return name, (spellID and "spell:"..spellID) or nil
			end,
		},

		battlepet = {
			command = "/sp",
			argGetter = function(petGUID)
				local speciesID, customName, level, xp, maxXp, displayID, favorite, speciesName = C_PetJournal.GetPetInfoByPetID(petGUID);
				return speciesName, (speciesID and "pet:"..speciesID) or nil
			end,
		},

		mount = {
			command = "/use",
			argGetter = function(mountID, mountIndex)
				if mountID > 9999 then
					--the exact id for RandomFavoriteMount is (2^28 - 1)
					local name = GetSpellName(150544);
					return name, "mount:0"
				end
				local name = GetMountInfoByID(mountID);
				return name, (mountID and "mount:"..mountID) or nil
			end,
		},


		--Classic
		--GetCompanionInfo seems to be broken
		--[[
		companion = {
			command = "/use",
			argGetter = function(index, companionType)
				if companionType == "MOUNT" then
					local name, spellID, icon, isActive, isUsable, sourceType, isFavorite, isFactionSpecific, faction, shouldHideOnChar, isCollected, mountID = C_MountJournal.GetDisplayedMountInfo(index);
					return name, (mountID and "mount:"..mountID) or nil
				elseif companionType == "CRITTER" then
					local petID, speciesID, owned, customName, level, favorite, isRevoked, speciesName = C_PetJournal.GetPetInfoByIndex(index);
					return speciesName, (speciesID and "pet:"..speciesID) or nil
				end
			end,
		},
		--]]
	};

	function EditorSetup.DrawerOnEvent(self, event, ...)
		if event == "CURSOR_CHANGED" then
			local infoType, arg1, arg2, arg3, arg4 = GetCursorInfo();

			if infoType then
				EditorSetup.ReceptorFrame:Show();
				EditorSetup.IconButtonFrame:Hide();
				EditorUI.Note:Hide();
				local receptor = EditorSetup.ReceptorFrame;
				local info = SupportedCursorInfo[infoType];
				local inCombat = InCombatLockdown();

				if (not inCombat) and info and (arg1 ~= nil) then
					receptor.PlusSign:Show();
					receptor.Instruction:Hide();
					local name, commandArg, overrideCommand = info.argGetter(arg1, arg2, arg3, arg4);
					local newCommand;
					if overrideCommand then
						newCommand = "#"..overrideCommand;
					else
						newCommand = commandArg and format("#%s %s", info.command, commandArg) or nil;
					end
					name = name or "Unknown";

					local function Receptor_OnEnter(_self)
						receptor.PlusSign:Hide();
						receptor.Instruction:Show();
						receptor.Instruction:SetText(format(L["Drawer Add Action Format"], "["..name.."]"));
						receptor.Instruction:SetTextColor(1, 0.82, 0);
						receptor.newCommand = newCommand;
					end

					local function Receptor_OnLeave(_self)
						receptor.PlusSign:Show();
						receptor.Instruction:Hide();
						receptor.newCommand = nil;
					end

					local function Receptor_OnClick(_self, button)
						if button == "RightButton" then
							if not InCombatLockdown() then
								ClearCursor();
							end
							return
						end

						if newCommand then
							local body = EditorUI.SourceEditBox:GetText();
							body = body.."\n"..newCommand;
							EditorUI:SaveMacroBody(body);
						end

						if not InCombatLockdown() then
							ClearCursor();
						end
					end

					local function Receptor_OnReceiveDrag(_self)
						Receptor_OnClick(_self, "LeftButton");
					end

					receptor:SetScript("OnEnter", Receptor_OnEnter);
					receptor:SetScript("OnLeave", Receptor_OnLeave);
					receptor:SetScript("OnClick", Receptor_OnClick);
					receptor:SetScript("OnReceiveDrag", Receptor_OnReceiveDrag);

					if receptor:IsMouseMotionFocus() then
						Receptor_OnEnter(receptor);
					else
						Receptor_OnLeave(receptor);
					end
				else
					receptor.PlusSign:Hide();
					receptor.Instruction:Show();
					if inCombat then
						receptor.Instruction:SetText(L["PlumberMacro Error EditMacroInCombat"]);
						receptor.Instruction:SetTextColor(1, 0.1, 0.1);
					else
						receptor.Instruction:SetText(format(L["Unsupported Action Type Format"], infoType));
						receptor.Instruction:SetTextColor(0.6, 0.6, 0.6);
					end
					receptor:SetScript("OnEnter", nil);
					receptor:SetScript("OnLeave", nil);
					receptor:SetScript("OnClick", nil);
					receptor:SetScript("OnReceiveDrag", nil);
				end
			else
				EditorSetup.ReceptorFrame:Hide();
				EditorSetup.IconButtonFrame:Show();
				EditorUI.Note:Show();
			end
		end
	end

	function EditorSetup.DrawerInitFrames()
		if not EditorSetup.IconButtonContainer then
			local MainFrame = EditorUI.ExtraFrame;

			local IconButtonContainer = CreateFrame("Frame", nil, MainFrame);
			EditorSetup.IconButtonContainer = IconButtonContainer;
			IconButtonContainer:SetPoint("TOPLEFT", MainFrame, "TOPLEFT", 0, 0);
			IconButtonContainer:SetPoint("TOPRIGHT", MainFrame, "TOPRIGHT", 0, 0);
			IconButtonContainer:SetHeight(EditorSetup.iconButtonContainerHeight);

			if not EditorSetup.IconButtonFrame then
				local f = CreateFrame("Frame", nil, IconButtonContainer);
				EditorSetup.IconButtonFrame = f;
				f:SetPoint("TOPLEFT", IconButtonContainer, "TOPLEFT", 0, 0);
				f:SetPoint("BOTTOMRIGHT", IconButtonContainer, "BOTTOMRIGHT", 0, 0);
			end

			if not EditorSetup.ReceptorFrame then
				local f = CreateFrame("Button", nil, IconButtonContainer);
				EditorSetup.ReceptorFrame = f;
				f:SetAllPoints(true);
				f:RegisterForClicks("LeftButtonUp", "RightButtonUp");

				local PlusSign = f:CreateTexture(nil, "OVERLAY");
				f.PlusSign = PlusSign;
				PlusSign:SetSize(32, 32);
				PlusSign:SetPoint("CENTER", f, "CENTER", 0, 0);
				PlusSign:SetTexture("Interface/AddOns/Plumber/Art/Frame/MacroForge.png");
				PlusSign:SetTexCoord(0/512, 64/512, 72/512, 136/512)

				local Instruction = f:CreateFontString(nil, "OVERLAY", "GameFontNormal");
				f.Instruction = Instruction;
				Instruction:Hide();
				Instruction:SetJustifyH("CENTER");
				Instruction:SetPoint("LEFT", f, "LEFT", 16, 0);
				Instruction:SetPoint("RIGHT", f, "RIGHT", -16, 0);
				Instruction:SetSpacing(2);
			end
		end
	end

	EditorSetup.drawerOptions = {
		{label = L["Drawer Option CloseAfterClick"], dbKey = "SpellFlyout_CloseAfterClick", tooltip = L["Drawer Option CloseAfterClick Tooltip"], tooltip2 = L["Drawer Option Global Tooltip"]},
		{label = L["Drawer Option SingleRow"], dbKey = "SpellFlyout_SingleRow", tooltip = L["Drawer Option SingleRow Tooltip"], tooltip2 = L["Drawer Option Global Tooltip"]},
		{label = L["Drawer Option Hide Unusable"], dbKey = "SpellFlyout_HideUnusable", tooltip = L["Drawer Option Hide Unusable Tooltip"],
			tooltip2 = function()
				if GetDBValue("SpellFlyout_UpdateFrequently") then
					return L["Drawer Option Global Tooltip"]
				else
					return L["Drawer Option Hide Unusable Tooltip 2"].."\n\n"..L["Drawer Option Global Tooltip"]
				end
			end,
			onClickFunc = function()
				local forceUpdate = true;
				EditorUI:SearchInEditBoxForSupportedCommand(forceUpdate);
			end,
		},
		{label = L["Drawer Option Update Frequently"], dbKey = "SpellFlyout_UpdateFrequently", tooltip = L["Drawer Option Update Frequently Tooltip"], tooltip2 = L["Drawer Option Global Tooltip"], requiredDBValues = { SpellFlyout_HideUnusable = true }},
	};

	function EditorSetup.Drawer(body)
		EditorSetup.DrawerInitFrames();
		EditorSetup.ReceptorFrame:Hide();
		EditorSetup.IconButtonFrame:Show();

		local forEditor = true;
		local drawerInfo = MacroInterpreter:GetDrawerInfo(body, nil, nil, nil, forEditor);
		if drawerInfo and #drawerInfo > 0 then
			local refresh = false;

			if EditorUI.args.type ~= "drawer" then
				EditorUI.args.type = "drawer";
				refresh = true;
			else
				if EditorUI.args.drawerInfo then
					if #EditorUI.args.drawerInfo == #drawerInfo then
						for i, info in ipairs(EditorUI.args.drawerInfo) do
							if (info.actionType ~= drawerInfo[i].actionType) or (info.id ~= drawerInfo[i].id) then
								refresh = true;
								break
							end
						end
					else
						refresh = true;
					end
				else
					refresh = true;
				end
			end

			if refresh then
				EditorUI:ReleaseElements();
				local parent = EditorUI:GetParent();
				local container = EditorSetup.IconButtonFrame;
				local size = EditorSetup.itemButtonSize;
				local gap = EditorSetup.itemButtonGap;
				local frameWidth = 338; --parent:GetWidth()
				local span = #drawerInfo * (size + gap) - gap;
				local requiredWidth = span + 48;
				if requiredWidth > frameWidth then
					frameWidth = requiredWidth;
				end
				local fromX = 0.5*(frameWidth - span);
				local button;
				local baseFrameLevel = container:GetFrameLevel();

				for i, info in ipairs(drawerInfo) do
					button = EditorSetup.AcquireIconButton();
					button.index = i;
					button:SetFrameLevel(baseFrameLevel + i);
					button:Show();
					button:SetAction(info);
					button:SetPoint("LEFT", container, "LEFT", fromX + (size + gap) * (i - 1), 0);
				end

				EditorUI.ExtraFrame:ClearAllPoints();
				EditorUI.ExtraFrame:SetPoint("TOPLEFT", parent, "BOTTOMLEFT", 0, -4);
				EditorUI.ExtraFrame:SetWidth(frameWidth);

				EditorUI.ReorderController:SetAnchorInfo(container, "LEFT", fromX, 0, EditorSetup.itemButtonGap, 0);

				local visualOffsetY = 4;
				local padding = 8;
				local checkboxBaseOffsetX = 24;
				local extraHeight = padding - visualOffsetY;
				local checkbox;
				local valid;
				for _, info in ipairs(EditorSetup.drawerOptions) do
					valid = true;
					if info.requiredDBValues then
						for dbKey, value in pairs(info.requiredDBValues) do
							if GetDBValue(dbKey) ~= value then
								valid = false;
								break
							end
						end
						fromX = checkboxBaseOffsetX + 20;
					else
						fromX = checkboxBaseOffsetX;
					end

					if valid then
						checkbox = EditorSetup.AcquireCheckbox();
						checkbox:SetData(info);
						checkbox:SetChecked(GetDBValue(checkbox.dbKey));
						checkbox:SetPoint("TOPLEFT", EditorSetup.IconButtonFrame, "BOTTOMLEFT", fromX, -extraHeight);
						extraHeight = extraHeight + checkbox:GetHeight();
					end
				end
				extraHeight = extraHeight + padding + visualOffsetY;
				EditorUI.ExtraFrame:SetHeight(EditorSetup.iconButtonContainerHeight + extraHeight);

				local Divider = EditorUI.Divider;
				if not Divider then
					Divider = EditorUI.ExtraFrame:CreateTexture(nil, "OVERLAY");
					EditorUI.Divider = Divider;
					EditorUI:AddRemovableElements(Divider);
					Divider:SetHeight(2);
					Divider:SetVertexColor(1, 1, 1, 0.4);
					Divider:SetTexture("Interface/AddOns/Plumber/Art/Frame/MacroForge.png");
					Divider:SetTexCoord(64/512, 264/512, 72/512, 76/512)
				end
				Divider:SetPoint("CENTER", EditorUI.ExtraFrame, "TOP", 0, -EditorSetup.iconButtonContainerHeight + visualOffsetY);
				Divider:SetWidth(frameWidth - 24);
				Divider:Show();
			end
		else
			EditorUI:ReleaseElements();
			EditorUI:DisplayNote(L["Drag And Drop Item Here"]);
			EditorUI.ExtraFrame:SetHeight(EditorSetup.iconButtonContainerHeight);
		end
		EditorUI.args.drawerInfo = drawerInfo;
		EditorUI.ExtraFrame:RegisterEvent("CURSOR_CHANGED");
		EditorUI.ExtraFrame:SetScript("OnEvent", EditorSetup.DrawerOnEvent);
	end
	PlumberMacros["drawer"].editorSetupFunc = EditorSetup.Drawer;
end


do  --DrawerUpdator
	local UpdateEvents_Constant = {
		["ACTIVE_TALENT_GROUP_CHANGED"] = true,
		["NEW_TOY_ADDED"] = true,
		["NEW_PET_ADDED"] = true,
		["NEW_MOUNT_ADDED"] = true,
		["SKILL_LINES_CHANGED"] = true,
	};

	local UpdateEvents_Lazy = {
		["SPELLS_CHANGED"] = true,
		["BAG_UPDATE_DELAYED"] = true,
		["TRAIT_CONFIG_UPDATED"] = true,
	};

	function DrawerUpdator:SetEnabled(state)
		self.enabled = state;
		if state then
			for event in pairs(UpdateEvents_Constant) do
				self:RegisterEvent(event);
			end
			self:SetScript("OnEvent", self.OnEvent);

			if UPDATE_FREQUENTLY then
				self:EnableAdditionalChecks(true);
			else
				self:EnableAdditionalChecks(false);
			end
		else
			for event in pairs(UpdateEvents_Constant) do
				self:UnregisterEvent(event);
			end
			self:EnableAdditionalChecks(false);
		end
	end

	function DrawerUpdator:EnableAdditionalChecks(state)
		if not self.enabled then return end;

		if state then
			for event in pairs(UpdateEvents_Lazy) do
				self:RegisterEvent(event);
			end
		else
			for event in pairs(UpdateEvents_Lazy) do
				self:UnregisterEvent(event);
			end
		end
	end

	function DrawerUpdator:OnEvent(event, ...)
		if UpdateEvents_Constant[event] then
			if not self.drawerDirty then
				self.drawerDirty = true;
				self:RequestUpdate();
			end
		elseif UpdateEvents_Lazy[event] then
			if not self.drawerDirty then
				self.drawerDirty = true;
				self:RequestUpdate();
			end
		elseif event == "PLAYER_REGEN_ENABLED" then
			self:UnregisterEvent(event);
			if self.drawerDirty then
				self:UpdateDrawers();
			end
		end
	end

	function DrawerUpdator:RequestUpdate(delay)
		self.drawerDirty = true;
		delay = delay or 0.2;
		self.t = -delay;
		if InCombatLockdown() then
			self:SetScript("OnUpdate", nil);
			self:RegisterEvent("PLAYER_REGEN_ENABLED");
		else
			self:SetScript("OnUpdate", self.OnUpdate);
		end
	end

	function DrawerUpdator:UpdateIfDirty()
		if self.drawerDirty then
			if not InCombatLockdown() then
				self:UpdateDrawers();
			end
		end
	end

	function DrawerUpdator:OnUpdate(elapsed)
		self.t = self.t + elapsed;
		if self.t > 0 then
			self.t = 0;
			self:SetScript("OnUpdate", nil);
			if InCombatLockdown() then
				self:RequestUpdate();
			else
				self:UpdateDrawers();
			end
		end
	end

	function DrawerUpdator:UpdateDrawers()
		self.drawerDirty = false;
		EL:UpdateDrawers();
		SecureSpellFlyout:Close();
		--print("DRAWER UPDATED")
	end

	CallbackRegistry:Register("Macro.UpdateDrawers", DrawerUpdator.RequestUpdate, DrawerUpdator);
end


do  --Settings Registry
	local function UpdateAfterSettingsChanged()
		EL:RequestUpdateMacros(0.0);
	end

	CallbackRegistry:RegisterSettingCallback("SpellFlyout_CloseAfterClick", function(state, userInput)
		if userInput then
			UpdateAfterSettingsChanged();
		end
	end);

	CallbackRegistry:RegisterSettingCallback("SpellFlyout_SingleRow", function(state, userInput)
		if userInput then
			UpdateAfterSettingsChanged();
		end
	end);

	CallbackRegistry:RegisterSettingCallback("SpellFlyout_HideUnusable", function(state, userInput)
		HIDE_UNUSABLE = state;
		if userInput then
			UpdateAfterSettingsChanged();
		end
	end);

	CallbackRegistry:RegisterSettingCallback("SpellFlyout_UpdateFrequently", function(state, userInput)
		UPDATE_FREQUENTLY = state;
		DrawerUpdator:EnableAdditionalChecks(state);
		if userInput then
			UpdateAfterSettingsChanged();
		end
	end);

	CallbackRegistry:Register("Macro.UpdateMacros", UpdateAfterSettingsChanged);
end


do  --For other modules like Legion Remix
	local function AddPlumberMacro(commandInfo)
		local command = commandInfo.command;
		local type = commandInfo.modifyType and ModifyType[commandInfo.modifyType];
		if not commandInfo.conditionFunc then
			commandInfo.conditionFunc = function()
				return true
			end
		end
		if not PlumberMacros[command] then
			PlumberMacros[command] = commandInfo;
		end
	end
	addon.AddPlumberMacro = AddPlumberMacro;


	local function IsCharacterMarcoFull()
		local _, numCharacterMacros = GetNumMacros();
		if numCharacterMacros >= EL.maxCharacterMacros then
			return true
		end
	end
	API.IsCharacterMarcoFull = IsCharacterMarcoFull;


	local function CreateCharacterMacro(name, icon, body)
		local success, reason;
		if InCombatLockdown() then
			success = false;
			reason = 1;
		else
			local _, numCharacterMacros = GetNumMacros();
			if numCharacterMacros < EL.maxCharacterMacros then
				local perCharacter = true;
				local macroID = CreateMacro(name, icon, body, perCharacter);
				success = true;
				reason = macroID;
			else
				success = false;
				reason = 2;
			end
		end
		return success, reason
	end


	local function CanPickupOrCreateCommand(command)
		if not command then return end;

		local _, numCharacterMacros = GetNumMacros();
		local fromIndex = EL.macroIndexMin2;
		local toIndex = fromIndex + numCharacterMacros - 1;
		local body, _command;
		local macroID;

		if toIndex >= fromIndex then
			for index = fromIndex, toIndex do
				body = GetMacroBody(index);
				if body then
					_command = match(body, "#plumber:(%w+)");
					if _command == command then
						return true
					end
				end
			end
		end

		return not IsCharacterMarcoFull()
	end
	addon.CanPickupOrCreateCommand = CanPickupOrCreateCommand;


	local function AcquireCharacterMacro(command, generatorFunc)
		if not command then return end;
		local _, numCharacterMacros = GetNumMacros();
		local fromIndex = EL.macroIndexMin2;
		local toIndex = fromIndex + numCharacterMacros - 1;
		local body, _command;
		local macroID;

		if toIndex >= fromIndex then
			for index = fromIndex, toIndex do
				body = GetMacroBody(index);
				if body then
					_command = match(body, "#plumber:(%w+)");
					if _command == command then
						macroID = index;
						break
					end
				end
			end
		end

		if (not macroID) and generatorFunc then
			local name, icon, _body = generatorFunc();
			local success, arg = CreateCharacterMacro(name, icon, _body);
			if success then
				macroID = arg;
			end
		end

		return macroID
	end
	addon.AcquireCharacterMacro = AcquireCharacterMacro;
end


do  --Module Registry
	local moduleData = {
		name = L["ModuleName DrawerMacro"],
		dbKey = "DrawerMacro",
		description = L["ModuleDescription DrawerMacro"],
		categoryID = 1,
		uiOrder = 1,
		virtual = true,
		categoryKeys = {
			"Signature",
		},
		searchTags = {
			"ActionBar", "Combat",
		},
	};

	addon.ControlCenter:AddModule(moduleData);
end
