local L = LibStub("AceLocale-3.0"):GetLocale("IceHUD", false)
local StaggerBar = IceCore_CreateClass(IceUnitBar)
local IceHUD = _G.IceHUD

local tostring = tostring
local floor = math.floor
local min = math.min
local strform = string.format

local playerName = ""
local LightID = 124275
local ModerateID = 124274
local HeavyID = 124273
local StaggerID = 124255
local staggerNames = {"", "", ""}
local staggerIds = {LightID, ModerateID, HeavyID}

local MinLevel = 10

local STAGGER_YELLOW_TRANSITION, STAGGER_RED_TRANSITION = STAGGER_YELLOW_TRANSITION, STAGGER_RED_TRANSITION
if STAGGER_STATES then
	STAGGER_YELLOW_TRANSITION = STAGGER_STATES.YELLOW.threshold
	STAGGER_RED_TRANSITION = STAGGER_STATES.RED.threshold
end

local GetSpellInfo = GetSpellInfo
if not GetSpellInfo and C_Spell and C_Spell.GetSpellInfo then
	GetSpellInfo = function(spellID)
		if not spellID then
			return nil
		end

		local spellInfo = C_Spell.GetSpellInfo(spellID)
		if spellInfo then
			return spellInfo.name, nil, spellInfo.iconID, spellInfo.castTime, spellInfo.minRange, spellInfo.maxRange, spellInfo.spellID, spellInfo.originalIconID
		end
	end
end

local GetSpellName = GetSpellInfo
if C_Spell and C_Spell.GetSpellName then
	GetSpellName = C_Spell.GetSpellName
end

local UnitDebuff = UnitDebuff
if not UnitDebuff and C_UnitAuras and AuraUtil then
	UnitDebuff = function(unitToken, index, filter)
		local auraData = C_UnitAuras.GetDebuffDataByIndex(unitToken, index, filter)
		if not auraData then
			return nil
		end

		return IceHUD.UnpackAuraData(auraData)
	end
end

StaggerBar.prototype.StaggerDuration = 0
StaggerBar.prototype.StaggerEndTime = 0

function StaggerBar.prototype:init()
	StaggerBar.super.prototype.init(self, "Stagger", "player")

	if C_CurveUtil then
		self.healthCurve = C_CurveUtil.CreateCurve()
		---@diagnostic disable-next-line: undefined-field
		self.healthCurve:SetType(Enum.LuaCurveType.Linear)
	end

	if self.healthCurve then
		self:SetDefaultColor("Stagger", 200, 180, 20)
	else
		self:SetDefaultColor("Stagger1", 200, 180, 20)
		self:SetDefaultColor("Stagger2", 200, 90, 10)
		self:SetDefaultColor("Stagger3", 200, 0, 0)
		self:SetDefaultColor("StaggerTime", 255, 255, 255)
	end

	self.bTreatEmptyAsFull = true
end

function StaggerBar.prototype:Redraw()
	StaggerBar.super.prototype.Redraw(self)

	self:MyOnUpdate()
	self:UpdateStaggerBar()
end

function StaggerBar.prototype:GetDefaultSettings()
	local settings =  StaggerBar.super.prototype.GetDefaultSettings(self)

	settings["enabled"] = true
	settings["shouldAnimate"] = true
	settings["lowThreshold"] = 0
	settings["side"] = IceCore.Side.Left
	settings["offset"] = 3
	settings["upperText"]=""
	settings["showAsPercentOfMax"] = true
	settings["maxPercent"] = 20
	settings["timerAlpha"] = 0.3
	settings["lockLowerFontAlpha"] = false
	settings["lowerTextString"] = ""
	settings["lowerTextVisible"] = false
	settings["hideAnimationSettings"] = true
	settings["bAllowExpand"] = true
	settings["bShowWithNoTarget"] = true
	settings["upperText"] = "[PercentStagger]"
	settings["lowerText"] = "[FractionalStagger:Short:Bracket]"
	settings["textHorizontalOffset"] = -10

	return settings
end

function StaggerBar.prototype:GetOptions()
	local opts = StaggerBar.super.prototype.GetOptions(self)

	opts.reverse.hidden = true

	opts["maxPercent"] =
	{
		type = "range",
		name = "Max Percent",
		desc = "Maximum percentage of your maximum health for the Stagger bar to represent. I.e, if set to 20%, the bar will be full when the Stagger damage over time effect is dealing 20% of your maximum health per second.",
		min = 0,
		max = 100,
		step = 1,
		get = function()
			return self.moduleSettings.maxPercent
		end,
		set = function(info, v)
			self.moduleSettings.maxPercent = v
			self:Redraw()
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end
	}

	opts["timerAlpha"] =
	{
		type = "range",
		name = "Timer bar alpha",
		desc = "What alpha value to use for the bar that displays how long until Stagger wears off.",
		min = 0,
		max = 100,
		step = 5,
		get = function()
			return self.moduleSettings.timerAlpha * 100
		end,
		set = function(info, v)
			self.moduleSettings.timerAlpha = v / 100
			self:Redraw()
		end,
		disabled = function()
			return not self.moduleSettings.enabled
		end
	}

	return opts
end

function StaggerBar.prototype:Enable(core)
	StaggerBar.super.prototype.Enable(self, core)

	playerName = UnitName(self.unit)
	staggerNames[1] = GetSpellName(LightID)
	staggerNames[2] = GetSpellName(ModerateID)
	staggerNames[3] = GetSpellName(HeavyID)

	if self.healthCurve then
		self:RegisterEvent("UNIT_MAXHEALTH", "UpdateMaxHealth")
		self:RegisterEvent(IceHUD.UnitPowerEvent, "UpdateStaggerBar")
		self:RegisterEvent("UNIT_AURA", "UpdateStaggerBar")
	else
		self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
	end
	self:RegisterEvent("PLAYER_ENTERING_WORLD")
	self:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED")

	self:UpdateCurves()
	self:UpdateShown()
end

function StaggerBar.prototype:UpdateMaxHealth()
	self:UpdateCurves()
end

function StaggerBar.prototype:Disable(core)
	StaggerBar.super.prototype.Disable(self, core)
end

function StaggerBar.prototype:CreateFrame()
	StaggerBar.super.prototype.CreateFrame(self)
	self:CreateTimerBar()

	self:UpdateShown()
	self:UpdateAlpha()
end

function StaggerBar.prototype:CreateTimerBar()
	if self.healthCurve then
		return
	end

	self.timerFrame = self:BarFactory(self.timerFrame, IceElement.aboveDefaultStrata, "ARTWORK", "Timer")

	self.CurrScale = 0

	self:SetBarFrameColorRGBA(self.timerFrame, self:GetColor("StaggerTime", self.moduleSettings.timerAlpha))
	if self.timerFrame.SetValue then
		self.timerFrame:SetValue(0)
	else
		self.timerFrame.texture:SetHeight(0)
	end

	self:UpdateBar(1, "undef")
	self:UpdateTimerFrame()
end

function StaggerBar.prototype:UpdateShown()
	if IceHUD.GetSpecialization() == SPEC_MONK_BREWMASTER and not UnitInVehicle(self.unit) and UnitLevel(self.unit) >= MinLevel then
		self:Show(true)
	else
		self:Show(false)
	end
end

function StaggerBar.prototype:PLAYER_ENTERING_WORLD()
	self:TargetChanged()
	self:UpdateStaggerBar()
end

function StaggerBar.prototype:ACTIVE_TALENT_GROUP_CHANGED()
	self:UpdateStaggerBar()
end

function StaggerBar.prototype:GetDebuffInfo()
	if UnitStagger then
		self.amount = UnitStagger(self.unit) or 0
		self.staggerLevel = 1

		local healthMax = UnitHealthMax(self.unit)
		local percent = self.amount / healthMax
		if percent >= STAGGER_YELLOW_TRANSITION then
			self.staggerLevel = 2
		elseif percent >= STAGGER_RED_TRANSITION then
			self.staggerLevel = 3
		end
		return
	end

	local amount = 0
	local duration = 0
	local staggerLevel = 1

	for i = 1, IceCore.BuffLimit do
		local debuffID = select(IceHUD.SpellFunctionsReturnRank and 11 or 10, UnitDebuff(self.unit, i))

		if debuffID == LightID or debuffID == ModerateID or debuffID == HeavyID then
			local spellName = UnitDebuff(self.unit, i)

			duration = select(IceHUD.SpellFunctionsReturnRank and 6 or 5, IceHUD.UnitAura(self.unit, spellName, "", "HARMFUL"))
			amount = select(IceHUD.SpellFunctionsReturnRank and 15 or 14, IceHUD.UnitAura(self.unit, spellName, "", "HARMFUL"))
			staggerLevel = (debuffID == LightID) and 1 or (debuffID == ModerateID) and 2 or 3

			break
		end
	end

	self.amount = amount or 0
	self.duration = duration or 0
	self.staggerLevel = staggerLevel or 1
end

function StaggerBar.prototype:COMBAT_LOG_EVENT_UNFILTERED(...)
	local eventArgs = {...}
    if (CombatLogGetCurrentEventInfo) then
        eventArgs = {CombatLogGetCurrentEventInfo()}
    end

	if eventArgs[9] == playerName then
		if eventArgs[12] == StaggerID or eventArgs[2] == "SWING_DAMAGE" or eventArgs[2] == "SPELL_AURA_APPLIED" or eventArgs[2] == "SPELL_AURA_REMOVED" then
			self:UpdateStaggerBar()
		end
	end
end

function StaggerBar.prototype:UpdateStaggerBar()
	if self:IsInConfigMode() then
		self:Show(true)
		self:UpdateBar(0.25, "Stagger")
		return
	end

	if self.healthCurve then
		local stagger = UnitStagger(self.unit)
		---@diagnostic disable-next-line: undefined-field
		local eval = self.healthCurve:Evaluate(stagger)

		self:UpdateBar(eval, "Stagger")
		self:UpdateShown()
		return
	end

	self:GetDebuffInfo()

	local maxHealth = UnitHealthMax(self.unit)
	local scale = IceHUD:Clamp((self.amount / maxHealth) * (100 / self.moduleSettings.maxPercent), 0, 1)

	if self.amount > 0 and (not self.duration or self.duration <= 10) then
		self:UpdateBar(scale or 0, "Stagger"..self.staggerLevel)
		self:UpdateShown()
		self:UpdateTimerFrame()
	else
		self:UpdateBar(0, "Stagger1")
		self:Show(false)
	end
end

function StaggerBar.prototype:GetDebuffDuration(unitName, buffId)
	local name, _, duration, endTime
	if IceHUD.SpellFunctionsReturnRank then
		name, _, _, _, _, duration, endTime = UnitDebuff(unitName, buffId)
	else
		for i = 1, IceCore.BuffLimit do
			local id
			name, _, _, _, duration, endTime, _, _, _, id = UnitDebuff(unitName, i)

			if id == buffId then
				break
			end
		end
	end

	if name then
		return duration, endTime - GetTime()
	end

	return nil, nil
end

function StaggerBar.prototype:MyOnUpdate()
	StaggerBar.super.prototype.MyOnUpdate(self)

	if self.bUpdateTimer then
		self:UpdateTimerFrame(nil, self.unit, true)
	end
end

function StaggerBar.prototype:UpdateCurves()
	if not self.healthCurve then
		return
	end

	local hpMax = UnitHealthMax(self.unit)
	---@diagnostic disable-next-line: undefined-field
	self.healthCurve:ClearPoints()
	---@diagnostic disable-next-line: undefined-field
	self.healthCurve:AddPoint(0, 0)
	---@diagnostic disable-next-line: undefined-field
	self.healthCurve:AddPoint(hpMax, 1)
end

function StaggerBar.prototype:UpdateTimerFrame(event, unit, fromUpdate)
	if self.healthCurve then
		return
	end

	if unit and unit ~= self.unit then
		return
	end

	local now = GetTime()
	local remaining = nil

	if not fromUpdate then
		for i = 1, #staggerIds do
			self.StaggerDuration, remaining = self:GetDebuffDuration(self.unit, staggerIds[i])

			if remaining then
				break
			end
		end

		if not remaining then
			self.StaggerEndTime = 0
		else
			self.StaggerEndTime = remaining + now
		end
	end

	if self.StaggerEndTime and self.StaggerEndTime >= now then
		if not fromUpdate then
			self.bUpdateTimer = true
		end

		if not remaining and (self.StaggerEndTime and self.StaggerEndTime >= now) then
			remaining = self.StaggerEndTime - now
		end

		if remaining then
			self:SetBarCoord(self.timerFrame, IceHUD:Clamp(remaining / 10, 0, 1))
			self.timerFrame:Show()
		else
			self:SetBarCoord(self.timerFrame, 0)
			self.timerFrame:Hide()
		end
	else
		self:SetBarCoord(self.timerFrame, 0)
		self.timerFrame:Hide()
		self.bUpdateTimer = false
	end
end

local _, unitClass = UnitClass("player")
if unitClass == "MONK" then
	IceHUD.StaggerBar = StaggerBar:new()
end
