-- Show the info of your target's spell that's being cast

local _, addon = ...
if addon.IS_MIDNIGHT then return end;

local API = addon.API;
local L = addon.L;
local CallbackRegistry = addon.CallbackRegistry;
local GetUnitIDGeneral = API.GetUnitIDGeneral;      --Creature/Pet/GameObject/Vehicle

local IMPORTANT_CAST_TIME = 1000;                   --Important spell (millisecond). No info when the cast/channel time is below this value

local IsInInstance = IsInInstance;                  --Save open world boss unit
local UnitExists = UnitExists;
local UnitIsBossMob = UnitIsBossMob;                --Don't save boss spell
local UnitCastingInfo = UnitCastingInfo;
local UnitChannelInfo = UnitChannelInfo;
local DoesSpellExist = C_Spell.DoesSpellExist;
local tsort = table.sort;
local pairs = pairs;
local ipairs = ipairs;


local CURRENT_UID = nil;
local TOOLTIP_SPELL_ID = true;          --Add spellID to our tooltip (Controlled by PlumberDB.SpellcastingInfoSpellID)


local function GetUnitCastingSpellID(unit)
	local _, spellID;
	_, _, _, _, _, _, _, _, spellID = UnitCastingInfo(unit);
	if not spellID then
		_, _, _, _, _, _, _, spellID = UnitChannelInfo(unit);
	end
	return spellID
end


local SpellDatabase = {};
do
	SpellDatabase.data = {};
	SpellDatabase.sessionData = {};

	function SpellDatabase:AddSpell(uid, spellID)
		if uid and spellID then
			if (not self.sessionData[uid]) then
				self:LoadSessionData(uid);
			end
			self.sessionData[uid][spellID] = true;
		end
	end

	function SpellDatabase:GetSpell(uid)
		local spells;
		local n = 0;

		local sessionData = self.sessionData[uid];
		if sessionData then
			spells = {};
			for spellID in pairs(sessionData) do
				if DoesSpellExist(spellID) then
					n = n + 1;
					spells[n] = spellID;
				end
			end
		end

		if self.allData[uid] then
			if not spells then
				spells = {};
			end
			for _, spellID in ipairs(self.allData[uid]) do
				if DoesSpellExist(spellID) then
					if sessionData and sessionData[spellID] then

					else
						n = n + 1;
						spells[n] = spellID;
					end
				end
			end
		end

		if n > 0 then
			tsort(spells);
			return spells
		end
	end

	function SpellDatabase:LoadSessionData(uid)
		if self.allData[uid] then
			if not self.sessionData[uid] then
				self.sessionData[uid] = {};
			end

			for _, spellID in ipairs(self.allData[uid]) do
				self.sessionData[uid][spellID] = true;
			end
		else
			self.sessionData[uid] = {};
		end
	end

	function SpellDatabase:LoadDatabase()
		if PlumberStorage and not PlumberStorage.CreatureSpells then
			PlumberStorage.CreatureSpells = {};
		end
		self.allData = PlumberStorage and PlumberStorage.CreatureSpells or {};
	end

	function SpellDatabase:SaveDatabase()
		if self.sessionData and self.allData then
			for uid, v in pairs(self.sessionData) do
				local spells = {};
				local n = 0;
				for spellID in pairs(v) do
					n = n + 1;
					spells[n] = spellID;
				end
				self.allData[uid] = spells;
			end
		end
	end
end


local EL = CreateFrame("Frame");
do
	local SpellEvents = {
		"UNIT_SPELLCAST_CHANNEL_START",
		"UNIT_SPELLCAST_START",
		"UNIT_SPELLCAST_SUCCEEDED",         --Instant spells don't trigger START

		--"UNIT_SPELLCAST_CHANNEL_STOP",
		--"UNIT_SPELLCAST_STOP",
		--"UNIT_SPELLCAST_EMPOWER_START",   --Unused by monsters?
		--"UNIT_SPELLCAST_EMPOWER_STOP",
	};

	function EL:OnEvent(event, ...)
		if event == "PLAYER_TARGET_CHANGED" then
			if UnitExists("target") and (self.inInOpenWorld or (not UnitIsBossMob("target"))) then
				CURRENT_UID = GetUnitIDGeneral("target");
				if CURRENT_UID then
					local spellID = GetUnitCastingSpellID("target");
					if spellID then
						SpellDatabase:AddSpell(CURRENT_UID, spellID);
					end
				end
			else
				CURRENT_UID = nil;
			end
		elseif event == "UNIT_SPELLCAST_START" then
			self:ProcessSpellcast(...);
		elseif event == "UNIT_SPELLCAST_CHANNEL_START" then
			self:ProcessSpellChannel(...);
		elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
			local _, _, spellID = ...
			SpellDatabase:AddSpell(CURRENT_UID, spellID);
		elseif event == "PLAYER_LOGOUT" then
			SpellDatabase:SaveDatabase();
		elseif event == "PLAYER_ENTERING_WORLD" or event == "ZONE_CHANGED_NEW_AREA" then
			self:UpdateInInstance();
		end
	end

	function EL:ListenUnitEvents(unit)
		self.unit = unit;

		for _, event in ipairs(SpellEvents) do
			self:RegisterUnitEvent(event, unit);
		end

		self:RegisterEvent("PLAYER_TARGET_CHANGED");
		self:RegisterEvent("PLAYER_LOGOUT");
		self:RegisterEvent("PLAYER_ENTERING_WORLD");
		self:RegisterEvent("ZONE_CHANGED_NEW_AREA");

		self:SetScript("OnEvent", self.OnEvent);

		self:UpdateInInstance();
		self:OnEvent("PLAYER_TARGET_CHANGED");
	end

	function EL:UnlistenEvents()
		for _, event in ipairs(SpellEvents) do
			self:UnregisterEvent(event);
		end
		self:UnregisterEvent("PLAYER_TARGET_CHANGED");
		self:UnregisterEvent("PLAYER_ENTERING_WORLD");
		self:UnregisterEvent("ZONE_CHANGED_NEW_AREA");
		--Keep PLAYER_LOGOUT so it saves sessionData
	end

	function EL:ProcessSpellcast(unitTarget, castGUID, spellID)
		--local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellID2 = UnitCastingInfo(self.unit);
		--return self:EvaluateSpell(endTimeMS, startTimeMS, text, texture, spellID or spellID2, notInterruptible);
		SpellDatabase:AddSpell(CURRENT_UID, spellID);
	end

	function EL:ProcessSpellChannel(unitTarget, castGUID, spellID)
		--local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellID2, isEmpowered, numEmpowerStages = UnitChannelInfo(self.unit);
		--return self:EvaluateSpell(endTimeMS, startTimeMS, text, texture, spellID or spellID2, notInterruptible);
		SpellDatabase:AddSpell(CURRENT_UID, spellID);
	end

	function EL:EvaluateSpell(endTimeMS, startTimeMS, text, texture, spellID, notInterruptible)
		--Unused
		if (not spellID) or (endTimeMS - startTimeMS < IMPORTANT_CAST_TIME) then
			return
		end
		return true
	end

	function EL:UpdateInInstance()
		self.inInOpenWorld = not IsInInstance();
	end
end


local SpellBarOverlayMixin = {};
do  --CastingBar / SpellBar
	function SpellBarOverlayMixin:OnEvent(event, ...)
		if self:IsMouseMotionFocus() then
			self:OnEnter();
		end
	end

	function SpellBarOverlayMixin:OnEnter()
		local unit = self:GetParent().unit;
		if not unit then return end;

		local spellID = GetUnitCastingSpellID(unit);

		if spellID then
			GameTooltip:SetOwner(self, "ANCHOR_NONE");
			GameTooltip:SetPoint("TOPLEFT", self, "BOTTOMRIGHT", 0, 0);
			GameTooltip:SetSpellByID(spellID);
		end

		self:RegisterUnitEvent("UNIT_SPELLCAST_START", unit);
		self:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", unit);
		self:SetScript("OnEvent", self.OnEvent);
	end

	function SpellBarOverlayMixin:OnHide()
		self:UnregisterEvent("UNIT_SPELLCAST_START");
		self:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_START");
		self:SetScript("OnEvent", nil);
	end

	function SpellBarOverlayMixin:OnLeave()
		GameTooltip:Hide();
		self:OnHide();
	end

	function SpellBarOverlayMixin:OnLoad()
		self:SetIgnoreParentAlpha(true);
		self:SetScript("OnEnter", self.OnEnter);
		self:SetScript("OnLeave", self.OnLeave);
		self:SetScript("OnHide", self.OnHide);
	end

	function SpellBarOverlayMixin:Release()
		self:Hide();
		self:ClearAllPoints();
		self:SetParent(nil);
	end
end

local function AttachFrameToCastingBar(bar, overlay)
	if not bar then
		return false
	end

	local f;

	if overlay then
		f = overlay;
		f:ClearAllPoints();
		f:SetParent(bar);
	else
		f = CreateFrame("Frame", nil, bar);
		API.Mixin(f, SpellBarOverlayMixin);
		f:OnLoad();
	end

	if bar.Icon and false then
		--We temporarily disable this because we might encounter negative area if the icon is on the right
		f:SetPoint("TOPLEFT", bar.Icon, "TOPLEFT", 0, 0);
	else
		f:SetPoint("TOPLEFT", bar, "TOPLEFT", 0, 0);
	end
	f:SetPoint("BOTTOMRIGHT", bar, "BOTTOMRIGHT", 0, 0);

	return f
end


local ModifyUnitPopupMenu;
do  --UnitPopup / Menu
	local GetSpellTexture = C_Spell.GetSpellTexture;
	local GetSpellName = C_Spell.GetSpellName;

	local ICON_TEXT_GAP = 6;
	local UNIT_MENU_ENABLED = false;
	local MENU_MODIFIED = false;

	local function GetSpellNameForButton(spellID)
		local spellName = GetSpellName(spellID);
		if spellName and spellName ~= "" then
			return spellName, true
		else
			return "SpellID: "..spellID, false
		end
	end

	local function SubmenuButtonResponder(data, menuInputData, menu)
		return 1    --MenuResponse.Open
	end

	local function Menu_OnLeave()
		GameTooltip:Hide();
	end

	function ModifyUnitPopupMenu(state)
		UNIT_MENU_ENABLED = state;

		if state then
			if MENU_MODIFIED then
				return
			else
				MENU_MODIFIED = true;
			end

			if Menu and Menu.ModifyMenu then
				Menu.ModifyMenu("MENU_UNIT_TARGET", function(owner, rootDescription, contextData)
					if not UNIT_MENU_ENABLED then return end;

					local uid = GetUnitIDGeneral("target");
					local spells = uid and SpellDatabase:GetSpell(uid);

					if spells then
						rootDescription:CreateDivider();
						--rootDescription:CreateTitle("Bestiary");

						local submenu = rootDescription:CreateButton(L["Abilities"]);

						for _, spellID in ipairs(spells) do
							local buttonDescription = submenu:CreateButton(spellID);

							buttonDescription:AddInitializer(function(button, description, menu)
								local leftTexture = button:AttachTexture();
								leftTexture:SetPoint("LEFT");
								leftTexture:SetTexture(GetSpellTexture(spellID));
								leftTexture:SetSize(16, 16);
								leftTexture:SetTexCoord(0.0625, 0.9375, 0.0625, 0.9375);

								local fontString = button.fontString;
								fontString:ClearAllPoints();
								fontString:SetPoint("LEFT", leftTexture, "RIGHT", ICON_TEXT_GAP, 0);
								fontString:SetTextColor(1, 1, 1);

								local name, loaded = GetSpellNameForButton(spellID);
								fontString:SetText(name);

								if not loaded then
									CallbackRegistry:LoadSpell(spellID, function(id)
										if button:IsVisible() and spellID == id then
											name = GetSpellNameForButton(spellID);
											fontString:SetText(name);
											leftTexture:SetTexture(GetSpellTexture(spellID));
										end
									end);
								end

								local width = fontString:GetUnboundedStringWidth() + leftTexture:GetWidth() + ICON_TEXT_GAP;
								local height = 20;

								button:SetScript("OnClick", function(_button, buttonName)
									if buttonName == "LeftButton" and IsModifiedClick("CHATLINK") and (not InCombatLockdown()) then
										local link = C_Spell.GetSpellLink(spellID);
										ChatEdit_InsertLink(link);
									end
								end);

								return width, height;
							end);

							buttonDescription:SetResponder(SubmenuButtonResponder);

							buttonDescription:SetOnEnter(function(f)
								local tooltip = GameTooltip;
								tooltip:Hide();
								tooltip:SetOwner(f, "ANCHOR_NONE");
								tooltip:SetPoint("RIGHT", f, "LEFT", -8, 0);

								local tooltipPostCall;
								if TOOLTIP_SPELL_ID then
									function tooltipPostCall()
										local icon = GetSpellTexture(spellID) or 0;
										tooltip:AddLine(" ");
										tooltip:AddDoubleLine(L["Spell Colon"].."|cffffffff"..spellID.."|r", L["Icon Colon"].."|cffffffff"..icon.."|r", 1, 0.82, 0, 1, 0.82, 0);
									end
								end

								local isPet = nil;
								local showSubtext = true;
								API.SetTooltipWithPostCall(tooltip, tooltipPostCall, "GetSpellByID", spellID, isPet, showSubtext);
								tooltip:Show();
							end);

							buttonDescription:SetOnLeave(Menu_OnLeave);
						end
					end
				end);
			end
		else

		end
	end
end


local AttachedBars = {
	--[GlobalName] = {unit = unit},
	TargetFrameSpellBar = {unit = "target"},
	FocusFrameSpellBar = {unit = "focus"},

	ElvUF_Target_CastBar = {unit = "target"},
	ElvUF_Focus_CastBar = {unit = "focus"},
};


local function EnableModule(state)
	if state then
		SpellDatabase:LoadDatabase();

		local bar;
		for name, data in pairs(AttachedBars) do
			bar = _G[name];
			if bar then
				if not data.overlay then
					AttachedBars[name].overlay = AttachFrameToCastingBar(bar);
				else
					AttachFrameToCastingBar(bar, data.overlay);
				end
			end
		end

		EL:ListenUnitEvents("target");
	else
		for name, data in pairs(AttachedBars) do
			if data.overlay then
				data.overlay:Release();
			end
		end

		EL:UnlistenEvents();
	end

	ModifyUnitPopupMenu(state);
	TOOLTIP_SPELL_ID = PlumberDB and PlumberDB.SpellcastingInfoSpellID ~= false;
end


do
	local moduleData = {
		name = L["ModuleName SpellcastingInfo"],
		dbKey = "SpellcastingInfo",
		description = L["ModuleDescription SpellcastingInfo"],
		toggleFunc = EnableModule,
		categoryID = 1,
		uiOrder = 1155,
		moduleAddedTime = 1732700000,
		categoryKeys = {
			"UnitFrame",
		},
	};

	addon.ControlCenter:AddModule(moduleData);
end
