local E, L, V, P, G = unpack(ElvUI)
local mod = E:GetModule("Enhanced_Blizzard")

local _G = _G
local select = select
local tonumber = tonumber
local unpack = unpack
local band = bit.band
local ceil, floor = math.ceil, math.floor
local format, upper, split, sub = string.format, string.upper, string.split, string.sub
local tsort, twipe = table.sort, table.wipe

local CannotBeResurrected = CannotBeResurrected
local CopyTable = CopyTable
local CreateFrame = CreateFrame
local GetReleaseTimeRemaining = GetReleaseTimeRemaining
local GetSpellInfo = GetSpellInfo
local GetSpellLink = GetSpellLink
local HasSoulstone = HasSoulstone
local IsActiveBattlefieldArena = IsActiveBattlefieldArena
local IsFalling = IsFalling
local IsOutOfBounds = IsOutOfBounds
local RepopMe = RepopMe
local UnitHealth = UnitHealth
local UnitHealthMax = UnitHealthMax
local UseSoulstone = UseSoulstone

local ACTION_SWING = ACTION_SWING
local ARENA_SPECTATOR = ARENA_SPECTATOR
local COMBATLOG_FILTER_ME = COMBATLOG_FILTER_ME
local COMBATLOG_UNKNOWN_UNIT = COMBATLOG_UNKNOWN_UNIT
local DEATH_RELEASE_NOTIMER = DEATH_RELEASE_NOTIMER
local DEATH_RELEASE_SPECTATOR = DEATH_RELEASE_SPECTATOR
local DEATH_RELEASE_TIMER = DEATH_RELEASE_TIMER
local MINUTES = MINUTES
local SECONDS = SECONDS
local TEXT_MODE_A_STRING_VALUE_SCHOOL = TEXT_MODE_A_STRING_VALUE_SCHOOL

local lastDeathEvents
local index = 0
local deathList = {}
local eventList = {}

local function AddEvent(timestamp, event, sourceName, spellId, spellName, environmentalType, amount, overkill, school, resisted, blocked, absorbed)
	if index > 0 and eventList[index].timestamp + 10 <= timestamp then
		index = 0
		twipe(eventList)
	end

	if index < 5 then
		index = index + 1
	else
		index = 1
	end

	if not eventList[index] then
		eventList[index] = {}
	else
		twipe(eventList[index])
	end

	eventList[index].timestamp = timestamp
	eventList[index].event = event
	eventList[index].sourceName = sourceName
	eventList[index].spellId = spellId
	eventList[index].spellName = spellName
	eventList[index].environmentalType = environmentalType
	eventList[index].amount = amount
	eventList[index].overkill = overkill
	eventList[index].school = school
	eventList[index].resisted = resisted
	eventList[index].blocked = blocked
	eventList[index].absorbed = absorbed
	eventList[index].currentHP = UnitHealth("player")
	eventList[index].maxHP = UnitHealthMax("player")
end

local function HasEvents()
	if lastDeathEvents then
		return #deathList > 0, #deathList
	else
		return false, #deathList
	end
end

local function EraseEvents()
	if index > 0 then
		index = 0
		twipe(eventList)
	end
end

local function AddDeath()
	if #eventList > 0 then
		local _, deathEvents = HasEvents()
		local deathIndex = deathEvents + 1
		deathList[deathIndex] = CopyTable(eventList)
		EraseEvents()

		DEFAULT_CHAT_FRAME:AddMessage("|cff71d5ff|Hdeath:"..deathIndex.."|h["..L["You died."].."]|h|r")

		return true
	end
end

local function GetDeathEvents(recapID)
	if recapID and deathList[recapID] then
		local deathEvents = deathList[recapID]
		tsort(deathEvents, function(a, b) return a.timestamp > b.timestamp end)
		return deathEvents
	end
end

local function GetTableInfo(data)
	local texture
	local nameIsNotSpell = false

	local event = data.event
	local spellId = data.spellId
	local spellName = data.spellName

	if event == "SWING_DAMAGE" then
		spellId = 6603
		spellName = ACTION_SWING

		nameIsNotSpell = true
	elseif event == "RANGE_DAMAGE" then
		nameIsNotSpell = true
--	elseif sub(event, 1, 5) == "SPELL" then
	elseif event == "ENVIRONMENTAL_DAMAGE" then
		local environmentalType = data.environmentalType
		environmentalType = upper(environmentalType)
		spellName = _G["ACTION_ENVIRONMENTAL_DAMAGE_"..environmentalType]
		nameIsNotSpell = true

		if environmentalType == "DROWNING" then
			texture = "spell_shadow_demonbreath"
		elseif environmentalType == "FALLING" then
			texture = "ability_rogue_quickrecovery"
		elseif environmentalType == "FIRE" or environmentalType == "LAVA" then
			texture = "spell_fire_fire"
		elseif environmentalType == "SLIME" then
			texture = "inv_misc_slime_01"
		elseif environmentalType == "FATIGUE" then
			texture = "ability_creature_cursed_05"
		else
			texture = "ability_creature_cursed_05"
		end

		texture = "Interface\\Icons\\" .. texture
	end

	if spellName and nameIsNotSpell then
		spellName = format("|Haction:%s|h%s|h", event, spellName)
	end

	if spellId and not texture then
		texture = select(3, GetSpellInfo(spellId))
	end

	return spellId, spellName, texture
end

local function OpenRecap(recapID)
	local self = ElvUI_DeathRecapFrame

	if self:IsShown() and self.recapID == recapID then
		self:Hide()
		return
	end

	local deathEvents = GetDeathEvents(recapID)
	if not deathEvents then return end

	self.recapID = recapID
	self:Show()

	if not deathEvents or #deathEvents <= 0 then
		for i = 1, 5 do
			self.DeathRecapEntry[i]:Hide()
		end

		self.Unavailable:Show()

		return
	end

	self.Unavailable:Hide()

	local highestDmgIdx, highestDmgAmount = 1, 0
	self.DeathTimeStamp = nil

	for i = 1, #deathEvents do
		local entry = self.DeathRecapEntry[i]
		local dmgInfo = entry.DamageInfo
		local evtData = deathEvents[i]
		local spellId, spellName, texture = GetTableInfo(evtData)

		entry:Show()
		self.DeathTimeStamp = self.DeathTimeStamp or evtData.timestamp

		if evtData.amount then
			local amountStr = -evtData.amount
			dmgInfo.Amount:SetText(amountStr)
			dmgInfo.AmountLarge:SetText(amountStr)
			dmgInfo.amount = evtData.amount

			dmgInfo.dmgExtraStr = ""
			if evtData.overkill and evtData.overkill > 0 then
				dmgInfo.dmgExtraStr = format(L["(%d Overkill)"], evtData.overkill)
				dmgInfo.amount = evtData.amount - evtData.overkill
			end
			if evtData.absorbed and evtData.absorbed > 0 then
				dmgInfo.dmgExtraStr = dmgInfo.dmgExtraStr.." "..format(L["(%d Absorbed)"], evtData.absorbed)
				dmgInfo.amount = evtData.amount - evtData.absorbed
			end
			if evtData.resisted and evtData.resisted > 0 then
				dmgInfo.dmgExtraStr = dmgInfo.dmgExtraStr.." "..format(L["(%d Resisted)"], evtData.resisted)
				dmgInfo.amount = evtData.amount - evtData.resisted
			end
			if evtData.blocked and evtData.blocked > 0 then
				dmgInfo.dmgExtraStr = dmgInfo.dmgExtraStr.." "..format(L["(%d Blocked)"], evtData.blocked)
				dmgInfo.amount = evtData.amount - evtData.blocked
			end

			if evtData.amount > highestDmgAmount then
				highestDmgIdx = i
				highestDmgAmount = evtData.amount
			end

			dmgInfo.Amount:Show()
			dmgInfo.AmountLarge:Hide()
		else
			dmgInfo.Amount:SetText("")
			dmgInfo.AmountLarge:SetText("")
			dmgInfo.amount = nil
			dmgInfo.dmgExtraStr = nil
		end

		dmgInfo.timestamp = evtData.timestamp
		dmgInfo.hpPercent = floor(evtData.currentHP / evtData.maxHP * 100)

		dmgInfo.spellName = spellName

		dmgInfo.caster = evtData.sourceName or COMBATLOG_UNKNOWN_UNIT

		if evtData.school and evtData.school > 1 then
			local colorArray = CombatLog_Color_ColorArrayBySchool(evtData.school)
			entry.SpellInfo.FrameIcon:SetBackdropBorderColor(colorArray.r, colorArray.g, colorArray.b)
		else
			entry.SpellInfo.FrameIcon:SetBackdropBorderColor(unpack(E.media.bordercolor))
		end

		dmgInfo.school = evtData.school

		entry.SpellInfo.Caster:SetText(dmgInfo.caster)

		entry.SpellInfo.Name:SetText(spellName)
		entry.SpellInfo.Icon:SetTexture(texture)

		entry.SpellInfo.spellId = spellId
	end

	for i = #deathEvents + 1, #self.DeathRecapEntry do
		self.DeathRecapEntry[i]:Hide()
	end

	local entry = self.DeathRecapEntry[highestDmgIdx]
	if entry.DamageInfo.amount then
		entry.DamageInfo.Amount:Hide()
		entry.DamageInfo.AmountLarge:Show()
	end

	local deathEntry = self.DeathRecapEntry[1]
	local tombstoneIcon = deathEntry.tombstone
	if entry == deathEntry then
		tombstoneIcon:Point("RIGHT", deathEntry.DamageInfo.AmountLarge, "LEFT", -10, 0)
	end
end

local function Spell_OnEnter(self)
	if self.spellId then
		GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
		GameTooltip:SetHyperlink(GetSpellLink(self.spellId))
		GameTooltip:Show()
	end
end

local function Amount_OnEnter(self)
	GameTooltip:SetOwner(self, "ANCHOR_LEFT")
	GameTooltip:ClearLines()

	if self.amount then
		local valueStr = self.school and format(TEXT_MODE_A_STRING_VALUE_SCHOOL, self.amount, CombatLog_String_SchoolString(self.school)) or self.amount
		GameTooltip:AddLine(format(L["%s %s"], valueStr, self.dmgExtraStr), 1, 0, 0, false)
	end

	if self.spellName then
		if self.caster then
			GameTooltip:AddLine(format(L["%s by %s"], self.spellName, self.caster), 1, 1, 1, true)
		else
			GameTooltip:AddLine(self.spellName, 1, 1, 1, true)
		end
	end

	local seconds = ElvUI_DeathRecapFrame.DeathTimeStamp - self.timestamp
	if seconds > 0 then
		GameTooltip:AddLine(format(L["%s sec before death at %s%% health."], format("%.1F", seconds), self.hpPercent), 1, 0.824, 0, true)
	else
		GameTooltip:AddLine(format(L["Killing blow at %s%% health."], self.hpPercent), 1, 0.824, 0, true)
	end

	GameTooltip:Show()
end

function mod:HideDeathPopup()
	E:StaticPopup_Hide("DEATH")
end

function mod:PLAYER_DEAD()
	if StaticPopup_FindVisible("DEATH") then
		if AddDeath() then
			lastDeathEvents = true
		else
			lastDeathEvents = false
		end

		StaticPopup_Hide("DEATH")
		E:StaticPopup_Show("DEATH", GetReleaseTimeRemaining(), SECONDS)
	end
end

function mod:COMBAT_LOG_EVENT_UNFILTERED(_, timestamp, event, _, sourceName, sourceFlags, destGUID, destName, destFlags, ...)
	if (band(destFlags, COMBATLOG_FILTER_ME) ~= COMBATLOG_FILTER_ME) or (band(sourceFlags, COMBATLOG_FILTER_ME) == COMBATLOG_FILTER_ME) then return end
	if event ~= "ENVIRONMENTAL_DAMAGE"
	and event ~= "RANGE_DAMAGE"
	and event ~= "SPELL_DAMAGE"
	and event ~= "SPELL_EXTRA_ATTACKS"
	and event ~= "SPELL_INSTAKILL"
	and event ~= "SPELL_PERIODIC_DAMAGE"
	and event ~= "SWING_DAMAGE"
	then return end

	local subVal = sub(event, 1, 5)
	local environmentalType, spellId, spellName, amount, overkill, school, resisted, blocked, absorbed

	if event == "SWING_DAMAGE" then
		amount, overkill, school, resisted, blocked, absorbed = ...
	elseif subVal == "SPELL" then
		spellId, spellName, _, amount, overkill, school, resisted, blocked, absorbed = ...
	elseif event == "ENVIRONMENTAL_DAMAGE" then
		environmentalType, amount, overkill, school, resisted, blocked, absorbed = ...
	end

	if not tonumber(amount) then return end

	AddEvent(timestamp, event, sourceName, spellId, spellName, environmentalType, amount, overkill, school, resisted, blocked, absorbed)
end

function mod:SetItemRef(link, ...)
	if sub(link, 1, 5) == "death" then
		local _, id = split(":", link)
		OpenRecap(tonumber(id))
		return
	else
		self.hooks.SetItemRef(link, ...)
	end
end

function mod:DeathRecap()
	if DeathRecapFrame then return end
	if not E.private.enhanced.deathRecap then return end

	local S = E:GetModule("Skins")

	local frame = CreateFrame("Frame", "ElvUI_DeathRecapFrame", UIParent)
	frame:SetFrameStrata("HIGH")
	frame:Size(340, 326)
	frame:Point("CENTER")
	frame:SetTemplate("Transparent")
	frame:SetMovable(true)
	frame:Hide()
	frame:SetScript("OnHide", function(self) self.recapID = nil end)
	tinsert(UISpecialFrames, frame:GetName())

	frame.Title = frame:CreateFontString("ARTWORK", nil, "GameFontNormal")
	frame.Title:Point("TOPLEFT", 12, -9)
	frame.Title:SetText(L["Death Recap"])

	frame.Unavailable = frame:CreateFontString("ARTWORK", nil, "GameFontNormal")
	frame.Unavailable:Point("CENTER")
	frame.Unavailable:SetText(L["Death Recap unavailable."])

	frame.CloseXButton = CreateFrame("Button", "$parentCloseXButton", frame)
	frame.CloseXButton:Size(32, 32)
	frame.CloseXButton:Point("TOPRIGHT", 2, 1)
	frame.CloseXButton:SetScript("OnClick", function(self) self:GetParent():Hide() end)
	S:HandleCloseButton(frame.CloseXButton)

	frame.DragButton = CreateFrame("Button", "$parentDragButton", frame)
	frame.DragButton:Point("TOPLEFT", 0, 0)
	frame.DragButton:Point("BOTTOMRIGHT", frame, "TOPRIGHT", 0, -32)
	frame.DragButton:RegisterForDrag("LeftButton")
	frame.DragButton:SetScript("OnDragStart", function(self) self:GetParent():StartMoving() end)
	frame.DragButton:SetScript("OnDragStop", function(self) self:GetParent():StopMovingOrSizing() end)

	frame.DeathRecapEntry = {}

	for i = 1, 5 do
		local button = CreateFrame("Frame", nil, frame)
		button:Size(308, 32)
		frame.DeathRecapEntry[i] = button

		button.DamageInfo = CreateFrame("Button", nil, button)
		button.DamageInfo:Point("TOPLEFT", 0, 0)
		button.DamageInfo:Point("BOTTOMRIGHT", button, "BOTTOMLEFT", 80, 0)
		button.DamageInfo:SetScript("OnEnter", Amount_OnEnter)
		button.DamageInfo:SetScript("OnLeave", GameTooltip_Hide)

		button.DamageInfo.Amount = button.DamageInfo:CreateFontString("ARTWORK", nil, "GameFontNormalRight")
		button.DamageInfo.Amount:SetJustifyH("RIGHT")
		button.DamageInfo.Amount:SetJustifyV("CENTER")
		button.DamageInfo.Amount:Size(0, 32)
		button.DamageInfo.Amount:Point("TOPRIGHT", 0, 0)
		button.DamageInfo.Amount:SetTextColor(0.75, 0.05, 0.05, 1)

		button.DamageInfo.AmountLarge = button.DamageInfo:CreateFontString("ARTWORK", nil, "NumberFont_Outline_Large")
		button.DamageInfo.AmountLarge:SetJustifyH("RIGHT")
		button.DamageInfo.AmountLarge:SetJustifyV("CENTER")
		button.DamageInfo.AmountLarge:Size(0, 32)
		button.DamageInfo.AmountLarge:Point("TOPRIGHT", 0, 0)
		button.DamageInfo.AmountLarge:SetTextColor(1, 0.07, 0.07, 1)

		button.SpellInfo = CreateFrame("Button", nil, button)
		button.SpellInfo:Point("TOPLEFT", button.DamageInfo, "TOPRIGHT", 16, 0)
		button.SpellInfo:Point("BOTTOMRIGHT", 0, 0)
		button.SpellInfo:SetScript("OnEnter", Spell_OnEnter)
		button.SpellInfo:SetScript("OnLeave", GameTooltip_Hide)

		button.SpellInfo.FrameIcon = CreateFrame("Button", nil, button.SpellInfo)
		button.SpellInfo.FrameIcon:Size(34, 34)
		button.SpellInfo.FrameIcon:Point("LEFT", 0, 0)
		button.SpellInfo.FrameIcon:SetTemplate("Default")

		button.SpellInfo.Icon = button.SpellInfo:CreateTexture("ARTWORK")
		button.SpellInfo.Icon:SetParent(button.SpellInfo.FrameIcon)
		button.SpellInfo.Icon:SetTexCoord(unpack(E.TexCoords))
		button.SpellInfo.Icon:SetInside()

		button.SpellInfo.Name = button.SpellInfo:CreateFontString("ARTWORK", nil, "GameFontNormal")
		button.SpellInfo.Name:SetJustifyH("LEFT")
		button.SpellInfo.Name:SetJustifyV("BOTTOM")
		button.SpellInfo.Name:Point("BOTTOMLEFT", button.SpellInfo.Icon, "RIGHT", 8, 1)
		button.SpellInfo.Name:Point("TOPRIGHT", 0, 0)

		button.SpellInfo.Caster = button.SpellInfo:CreateFontString("ARTWORK", nil, "SystemFont_Shadow_Small")
		button.SpellInfo.Caster:SetJustifyH("LEFT")
		button.SpellInfo.Caster:SetJustifyV("TOP")
		button.SpellInfo.Caster:Point("TOPLEFT", button.SpellInfo.Icon, "RIGHT", 8, -2)
		button.SpellInfo.Caster:Point("BOTTOMRIGHT", 0, 0)
		button.SpellInfo.Caster:SetTextColor(0.5, 0.5, 0.5, 1)

		if i == 1 then
			button:Point("BOTTOMLEFT", 16, 64)
			button.tombstone = button:CreateTexture("ARTWORK")
			button.tombstone:Size(15, 20)
			button.tombstone:Point("RIGHT", button.DamageInfo.Amount, "LEFT", -10, 0)
			button.tombstone:SetTexCoord(0.658203125, 0.6875, 0.00390625, 0.08203125)
			button.tombstone:SetTexture("Interface\\AddOns\\ElvUI_Enhanced\\media\\textures\\DeathRecap")
		else
			button:Point("BOTTOM", frame.DeathRecapEntry[i - 1], "TOP", 0, 14)
		end
	end

	frame.CloseButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
	frame.CloseButton:Size(144, 21)
	frame.CloseButton:Point("BOTTOM", 0, 15)
	frame.CloseButton:SetText(CLOSE)
	frame.CloseButton:SetScript("OnClick", function(self) ElvUI_DeathRecapFrame:Hide() end)
	S:HandleButton(frame.CloseButton)

	self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
	self:RegisterEvent("PLAYER_DEAD")
	self:RegisterEvent("PLAYER_ENTERING_WORLD", "HideDeathPopup")
	self:RegisterEvent("RESURRECT_REQUEST", "HideDeathPopup")
	self:RegisterEvent("PLAYER_ALIVE", "HideDeathPopup")
	self:RegisterEvent("RAISED_AS_GHOUL", "HideDeathPopup")

	self:RawHook("SetItemRef", true)

	E.PopupDialogs["DEATH"] = {
		text = DEATH_RELEASE_TIMER,
		button1 = DEATH_RELEASE,
		button2 = USE_SOULSTONE,
		button3 = L["Death Recap"],
		OnShow = function(self)
			self.timeleft = GetReleaseTimeRemaining()
			local text = HasSoulstone()
			if text then
				self.button2:SetText(text)
			end

			if IsActiveBattlefieldArena() then
				self.text:SetText(DEATH_RELEASE_SPECTATOR)
			elseif self.timeleft == -1 then
				self.text:SetText(DEATH_RELEASE_NOTIMER)
			end
			if HasEvents() then
				self.button3:Enable()
				self.button3:SetScript("OnEnter", S.SetModifiedBackdrop)
				self.button3:SetScript("OnLeave", S.SetOriginalBackdrop)
			else
				self.button3:Disable()
				self.button3:SetScript("OnEnter", function(self)
					GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT")
					GameTooltip:SetText(L["Death Recap unavailable."])
					GameTooltip:Show()
				end)
				self.button3:SetScript("OnLeave", GameTooltip_Hide)
			end
		end,
		OnHide = function(self)
			self.button3:SetScript("OnEnter", nil)
			self.button3:SetScript("OnLeave", nil)
			ElvUI_DeathRecapFrame:Hide()
		end,
		OnAccept = function(self)
			if IsActiveBattlefieldArena() then
				local info = ChatTypeInfo["SYSTEM"]
				DEFAULT_CHAT_FRAME:AddMessage(ARENA_SPECTATOR, info.r, info.g, info.b, info.id)
			end
			RepopMe()
			if CannotBeResurrected() then
				return 1
			end
		end,
		OnCancel = function(self, data, reason)
			if reason == "override" then
				StaticPopup_Show("RECOVER_CORPSE")
				return
			end
			if reason == "timeout" then
				return
			end
			if reason == "clicked" then
				if HasSoulstone() then
					UseSoulstone()
				else
					RepopMe()
				end
				if CannotBeResurrected() then
					return 1
				end
			end
		end,
		OnAlt = function()
			local _, recapID = HasEvents()
			OpenRecap(recapID)
		end,
		OnUpdate = function(self, elapsed)
			if self.timeleft > 0 then
				local text = _G[self:GetName().."Text"]
				local timeleft = self.timeleft
				if timeleft < 60 then
					text:SetFormattedText(DEATH_RELEASE_TIMER, timeleft, SECONDS)
				else
					text:SetFormattedText(DEATH_RELEASE_TIMER, ceil(timeleft / 60), MINUTES)
				end
			end

			if IsFalling() and not IsOutOfBounds() then
				self.button1:Disable()
				self.button2:Disable()
				return
			else
				self.button1:Enable()
			end
			if HasSoulstone() then
				self.button2:Enable()
			else
				self.button2:Disable()
			end
		end,
		DisplayButton2 = HasSoulstone,

		timeout = 0,
		whileDead = 1,
		interruptCinematic = 1,
		noCancelOnReuse = 1,
		hideOnEscape = false,
		noCloseOnAlt = true
	}
end