---@alias Color {r: number, g: number, b: number}
---@class Colorer
---@field colors table<number, Color>
---@field breakpoints table<number>
aura_env.Colorer = {
	--- Make sure colors and breakpoints always have the same number of entries! VERY IMPORTANT!
	---@type table<number, Color>
	colors = {
		{ r = 0.62, g = 0.62, b = 0.62 }, -- Grey
		-- { r = 1,    g = 1,    b = 1 }, -- White
		{ r = 0.12, g = 1, b = 0 }, -- Green
		{ r = 0, g = 0.44, b = 0.87 }, -- Blue
		{ r = 0.64, g = 0.21, b = 0.93 }, -- Purple
		{ r = 1, g = 0.5, b = 0 }, -- Orange
		{ r = 0.9, g = 0.8, b = 0.5 }, -- Light Gold
		{ r = 0, g = 0.8, b = 1.0 }, -- Blizzard Blue
	},
	breakpoints = { 0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1000.0 },

	---@param value number
	---@return Color, nil|string
	Interpolate = function(value)
		local color = { r = 0, g = 0, b = 0 }

		---@type table<number, table<number, number>>
		local bracket = { { 0, 0 }, { 1, 1 } }
		for i = 1, #aura_env.Colorer.breakpoints do
			if value < aura_env.Colorer.breakpoints[i] then
				bracket[2] = { i, aura_env.Colorer.breakpoints[i] }
				break
			end
			bracket[1] = { i, aura_env.Colorer.breakpoints[i] }
		end

		---@type Color
		local startColor = aura_env.Colorer.colors[bracket[1][1]]
		---@type Color
		local endColor = aura_env.Colorer.colors[bracket[2][1]]

		if startColor == nil or endColor == nil then return color, "Color not found" end

		local fraction = (value - bracket[1][2]) / (bracket[2][2] - bracket[1][2])

		for k, v in pairs(startColor) do
			color[k] = aura_env.Colorer.lerp(v, endColor[k], fraction)
		end

		return color, nil
	end,

	---@param a number
	---@param b number
	---@param t number
	---@return number
	lerp = function(a, b, t) return a * (1 - t) + b * t end,
}
setmetatable(aura_env.Colorer, { __index = aura_env.Colorer })

---@class ShieldBuffer
---@field records table<number, number>
---@field recordsToKeep number
---@field pointer number
aura_env.ShieldBuffer = {
	---@param recordsToKeep number
	---@return ShieldBuffer
	new = function(recordsToKeep)
		local self = setmetatable({}, {
			__index = aura_env.ShieldBuffer,
		})
		self.recordsToKeep = recordsToKeep
		self.records = {}
		self.pointer = 1
		for i = 1, recordsToKeep do
			self.records[i] = 0
		end
		return self
	end,

	---@param self ShieldBuffer
	---@param shieldValue number
	---@return nil
	Append = function(self, shieldValue)
		self.records[self.pointer] = shieldValue
		self.pointer = (self.pointer % self.recordsToKeep) + 1
	end,
	---@param self ShieldBuffer
	---@return number
	GetMax = function(self)
		local max = 0
		for _, value in pairs(self.records) do
			if value > max then max = value end
		end
		if max == nil then return 0 end
		return max
	end,
	---@param self ShieldBuffer
	---@return number
	GetCurrent = function(self)
		local val = self.records[(self.pointer + self.recordsToKeep - 2) % self.recordsToKeep + 1]
		if val == nil then return 0 end
		return val
	end,
}

---@class Shield
---@field name string
---@field value number
---@field buffer ShieldBuffer
aura_env.Shield = {
	---@param name string
	---@return Shield
	new = function(name)
		local self = setmetatable({}, {
			__index = aura_env.Shield,
		})
		self.name = name
		self.value = 0
		self.buffer = aura_env.ShieldBuffer.new(30)
		return self
	end,

	---@param self Shield
	---@return nil, nil|string
	Update = function(self)
		local shieldValue = select(17, UnitAura("player", self.name))
		if shieldValue then
			self.value = shieldValue
		else
			self.value = 0
		end
		self.buffer:Append(self.value)
	end,
}

---@class ShieldManager
aura_env.ShieldManager = {
	---@return number
	GetMax = function()
		local value = 0
		for _, shield in pairs(aura_env.Shields) do
			value = value + shield.buffer:GetMax()
		end
		return value
	end,
	---@return number
	GetCurrent = function()
		local value = 0
		for _, shield in pairs(aura_env.Shields) do
			value = value + shield.buffer:GetCurrent()
		end
		return value
	end,
	---@return number current
	---@return number max
	GetStats = function()
		local max = 0
		local current = 0
		for _, shield in pairs(aura_env.Shields) do
			max = max + shield.buffer:GetMax()
			current = current + shield.buffer:GetCurrent()
		end
		return current, max
	end,
	---@return string current
	---@return string max
	---@return number current (raw)
	---@return number max (raw)
	GetStatsNice = function()
		local current, max = aura_env.ShieldManager.GetStats()
		if max > 1e6 or current > 1e6 then
			return string.format("%.1fM", current / 1e6), string.format("%.1fM", max / 1e6), current, max
		end
		return string.format("%.0fk", current / 1000), string.format("%.0fk", max / 1000), current, max
	end,
}

---@class Display
---@field current number
---@field max number
---@field currentRaw number
---@field maxRaw number
---@field ofHp string
---@field ofHpRaw number
---@field ofHpRawFraction number
---@field color Color
aura_env.Display = {
	Update = function()
		local current, max, currentRaw, maxRaw = aura_env.ShieldManager.GetStatsNice()
		local hp = UnitHealthMax("player")
		aura_env.Display.current = current
		aura_env.Display.max = max
		aura_env.Display.currentRaw = currentRaw
		aura_env.Display.maxRaw = maxRaw
		aura_env.Display.ofHpRaw = currentRaw / hp * 100
		aura_env.Display.ofHpRawFraction = aura_env.Display.ofHpRaw / 100
		aura_env.Display.ofHp = string.format("%.0f%%", aura_env.Display.ofHpRaw)
		aura_env.Display.color = aura_env.Colorer.Interpolate(aura_env.Display.ofHpRawFraction)
		return nil
	end,
}

---@type table<Shield>
aura_env.Shields = {
	[108366] = aura_env.Shield.new("Soul Leech"),
	[108416] = aura_env.Shield.new("Dark Pact"),
	[207472] = aura_env.Shield.new("Xavaric's Magnum Opus"),
	[252208] = aura_env.Shield.new("Refractive Shell"),
}
