local _, T = ...
if select(4,GetBuildInfo()) < 8e4 then return end

local EV, L = T.Evie, T.L
local AB = assert(T.ActionBook:compatible(2,23), "A compatible version of ActionBook is required.")
local RW = assert(AB:compatible("Rewire", 1,7), "A compatible version of Rewire is required")
local CHARNAME = UnitName("player") .. "@" .. GetRealmName()

do -- RW/opiespecset
	local f = CreateFrame("Frame", nil, nil, "SecureFrameTemplate")
	f:SetAttribute("RunSlashCmd", [[self:CallMethod("SwitchSpec", ...)]])
	local esSpec, esSet, esDeadline
	function EV:PLAYER_SPECIALIZATION_CHANGED(unit)
		if esDeadline and unit == "player" and GetSpecialization() == esSpec and not InCombatLockdown() and esDeadline > GetTime() then
			local sid = C_EquipmentSet.GetEquipmentSetID(esSet)
			esDeadline, esSpec, esSet = sid and C_EquipmentSet.UseEquipmentSet(sid) and nil
		end
	end
	function f:SwitchSpec(_cmd, args)
		local sid, sname = args:match("(%d+) {(.*)}")
		sid = tonumber(sid)
		if GetSpecialization() ~= sid and not InCombatLockdown() then
			SetSpecialization(sid)
			if sname ~= "" then
				esSpec, esSet, esDeadline = sid, sname, GetTime()+9
			end
		end
	end
	RW:RegisterCommand("/opiespecset", false, false, f)
end
do -- AB/specset
	local slot, tspec, tset = {}, {}, {}
	local function SetSpecializationTooltip(self, id, set)
		local _, name, desc = GetSpecializationInfo(id)
		if name then
			self:SetText(name, 1,1,1)
			self:AddLine(desc, nil, nil, nil, 1)
			if (set or "") ~= "" then
				local _, ico, _, _isCur, total, eq, bags, miss = C_EquipmentSet.GetEquipmentSetInfo(C_EquipmentSet.GetEquipmentSetID(set) or -1)
				if ico then
					local eql
					if total == eq then
						eql = GREEN_FONT_COLOR_CODE .. ITEMS_EQUIPPED:format(eq)
					elseif miss > 0 then
						eql = RED_FONT_COLOR_CODE .. ITEMS_NOT_IN_INVENTORY:format(miss)
					else
						eql = NORMAL_FONT_COLOR_CODE .. ITEMS_IN_INVENTORY:format(bags)
					end
					self:AddLine(" ")
					self:AddLine("|T" .. ico .. ":0|t " .. set .. ": " .. eql, 1,1,1)
				end
			end
		else
			self:Hide()
		end
	end
	local function SetSpecSetTooltip(self, tok)
		local spec, set = tspec[tok], tset[tok]
		if spec == nil then
			spec, set = 0+tok:sub(1,1), tok:sub(3)
			tspec[tok], tset[tok] = spec, set
		end
		SetSpecializationTooltip(self, spec, set)
	end
	local function hintSpecSet(tok)
		local cs, spec, set = GetSpecialization(), tspec[tok], tset[tok]
		if spec == nil then
			spec, set = 0+tok:sub(1,1), tok:sub(3)
			tspec[tok], tset[tok] = spec, set
		end
		local _, name, _, ico = GetSpecializationInfo(spec)
		local state = (cs == spec and 1 or 0)
		return (HasFullControl() and not InCombatLockdown()), state, ico, name, 0, 0, 0, SetSpecSetTooltip, tok
	end
	local function createSpecSet(idx, sets)
		local _, specName = GetSpecializationInfo(idx)
		local setName = sets and sets[CHARNAME]
		setName = setName ~= false and (setName or specName) or ""
		local tk = idx .. "#" .. setName
		local ret = slot[tk]
		if specName and UnitLevel("player") >= 10 and GetNumSpecializations() >= idx and not ret then
			ret = AB:GetActionSlot("macrotext", ("/cancelform [nospec:%d,form:travel,flyable,noflying,nocombat]\n/opiespecset %d {%s}\n%s [spec:%d] %s"):format(idx, idx, setName, SLASH_EQUIP_SET1, setName ~= "" and idx or 5, setName))
			ret = AB:CreateActionSlot(hintSpecSet, tk, "clone", ret)
			slot[tk] = ret
		end
		return ret
	end
	local function describeSpecSet(idx, _sets)
		local _, name, _, ico = GetSpecializationInfo(idx)
		return SPECIALIZATION, name or idx, ico or "Interface/Icons/Temp", nil, SetSpecializationTooltip, idx
	end
	AB:RegisterActionType("specset", createSpecSet, describeSpecSet)
	AB:AugmentCategory("Miscellaneous", function(_, add)
		for i=1,GetNumSpecializations() do
			add("specset", i)
		end
	end)
end
do -- EditorUI
	local bg = CreateFrame("Frame")
	local drop = CreateFrame("Frame", "OPie_SS_Drop", bg, "UIDropDownMenuTemplate")
	local lab = drop:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
	local myPlayerMap, mySpecID, mySpecName
	lab:SetPoint("LEFT", bg, "TOPLEFT", 0, -18)
	lab:SetText(L"Equip set:")
	drop:SetPoint("TOPRIGHT", -3, -2)
	UIDropDownMenu_SetWidth(drop, 250)
	
	local function getCurrentValue()
		local value = nil
		if myPlayerMap and myPlayerMap[CHARNAME] ~= nil then
			value = myPlayerMap[CHARNAME]
		end
		if value == nil then
			value = mySpecName
		end
		return value
	end
	function drop:text()
		local name = getCurrentValue()
		local _, ico = C_EquipmentSet.GetEquipmentSetInfo(name and C_EquipmentSet.GetEquipmentSetID(name) or -1)
		UIDropDownMenu_SetText(drop, name == false and NONE or ((ico and "|T" .. ico .. ":0|t " or "|cffc02020") .. name))
	end
	function drop:set(name)
		if name == mySpecName then
			name = nil
		end
		if myPlayerMap or name ~= nil then
			myPlayerMap = myPlayerMap or {}
			myPlayerMap[CHARNAME] = name
			if name == nil and next(myPlayerMap) == nil then
				myPlayerMap = nil
			end
		end
		drop:text() -- SaveAction would normally cause a refresh via :SetAction.
		local p = bg:GetParent()
		p = p and p.SaveAction and p:SaveAction()
	end
	function drop:initialize()
		local value = getCurrentValue()
		local inf, hadSpec = {func=self.set, minWidth=self:GetWidth()-40}, false
		inf.text, inf.arg1, inf.checked = NONE, false, value == false
		UIDropDownMenu_AddButton(inf)
		for _, id in pairs(C_EquipmentSet.GetEquipmentSetIDs()) do
			local n, ico = C_EquipmentSet.GetEquipmentSetInfo(id)
			inf.text, inf.arg1, inf.checked = "|T" .. (ico or "Interface/Icons/Temp") .. ":0|t " .. n, n, value == n
			hadSpec = hadSpec or n == mySpecName
			UIDropDownMenu_AddButton(inf)
		end
		if not hadSpec and mySpecName then
			inf.text, inf.arg1, inf.checked = mySpecName, mySpecName, value == nil or value == mySpecName
			UIDropDownMenu_AddButton(inf)
		end
	end

	--[[ The action format is: "specset", specIndex, {CHARNAME => equipSetName}
	     The map in [3] is needed to allow different characters to use the
	     "same" specset action to equip different sets. This editor only changes
	     the set equipped by the current character. However, the table causes some
	     degree of alarm: other code has pointers to it (and could modify it while
	     we're not looking), this editor could modify the original table without
	     intending to save its changes (possibly bypassing the host's undo
	     functionality), and :GetAction(into) needs us to write the full action
	     into an empty table (so keeping just this character's set name
	     internally isn't an option).

	     Current editor, action, and editor host implementations would be fine
	     with us keeping, mutating, and handing back the original table. To avoid
	     headaches in the future, we nevertheless create copies in both :GetAction
	     and :SetAction. What's a little table churn between friends?
	--]]
	local function shallowCopy(t)
		if type(t) ~= "table" then
			return nil
		end
		local r = {}
		for k,v in pairs(t) do
			r[k] = v
		end
		return r
	end
	function bg:SetAction(owner, action)
		bg:SetParent(nil)
		bg:ClearAllPoints()
		bg:SetAllPoints(owner)
		bg:SetParent(owner)
		bg:Show()

		mySpecID, myPlayerMap = action[2], shallowCopy(action[3])
		if type(myPlayerMap) ~= "table" then
			myPlayerMap = nil
		end
		mySpecName = select(2, GetSpecializationInfo(mySpecID))
		drop:SetShown(not not mySpecName)
		if mySpecName then
			drop:text()
		end
	end
	function bg:GetAction(into)
		into[1], into[2], into[3] = "specset", mySpecID, shallowCopy(myPlayerMap)
	end
	function bg:Release(owner)
		if bg:IsOwned(owner) then
			bg:SetParent(nil)
			bg:ClearAllPoints()
			bg:Hide()
			myPlayerMap, mySpecID, mySpecName = nil
		end
	end
	function bg:IsOwned(owner)
		return bg:GetParent() == owner
	end
	
	AB:RegisterEditorPanel("specset", bg)
end