-- DeathKnightUnholy.lua
-- Updated August 27, 2025

-- MoP: Use UnitClass instead of UnitClassBase
local _, playerClass = UnitClass("player")
if playerClass ~= "DEATHKNIGHT" then
	return
end

local addon, ns = ...
local Hekili = _G[addon]
local class, state = Hekili.Class, Hekili.State

-- Ensure state.runes exists before any resource registration to prevent metatable lookups from erroring.
do
	local runes = rawget(state, "runes")
	if not runes then
		runes = { expiry = { 0, 0, 0, 0, 0, 0 }, cooldown = 10, max = 6 }
		rawset(state, "runes", runes)
	end
	if not runes.expiry then
		runes.expiry = { 0, 0, 0, 0, 0, 0 }
	end
	for i = 1, 6 do
		if not runes.expiry[i] then
			runes.expiry[i] = 0
		end
	end
end

-- Safe local references to WoW API (helps static analyzers and emulation)
local GetRuneCooldown = rawget(_G, "GetRuneCooldown") or function()
	return 0, 10, true
end
local GetRuneType = rawget(_G, "GetRuneType") or function()
	return 1
end

local FindUnitBuffByID, FindUnitDebuffByID = ns.FindUnitBuffByID, ns.FindUnitDebuffByID
local strformat = string.format

local spec = Hekili:NewSpecialization(252, true)

spec.name = "Unholy"
spec.role = "DAMAGER"
spec.primaryStat = 1 -- Strength

-- Local shim for resource changes to avoid global gain/spend errors and normalize names.
local function _normalizeResource(res)
	if res == "runicpower" or res == "rp" then
		return "runic_power"
	end
	return res
end

local function gain(amount, resource, overcap, noforecast)
	local r = _normalizeResource(resource)
	if r == "runes" and state.runes and state.runes.expiry then
		local n = tonumber(amount) or 0
		if n >= 6 then
			for i = 1, 6 do
				state.runes.expiry[i] = 0
			end
		else
			for _ = 1, n do
				local worstIdx, worstVal = 1, -math.huge
				for i = 1, 6 do
					local e = state.runes.expiry[i] or 0
					if e > worstVal then
						worstVal, worstIdx = e, i
					end
				end
				state.runes.expiry[worstIdx] = 0
			end
		end
		return
	end
	if state.gain then
		return state.gain(amount, r, overcap, noforecast)
	end
end

local function spend(amount, resource, noforecast)
	local r = _normalizeResource(resource)
	if state.spend then
		return state.spend(amount, r, noforecast)
	end
end

-- Local aliases for core state helpers and tables (improves static checks and readability).
local applyBuff, removeBuff, applyDebuff, removeDebuff =
	state.applyBuff, state.removeBuff, state.applyDebuff, state.removeDebuff
local removeDebuffStack = state.removeDebuffStack
local summonPet, dismissPet, setDistance, interrupt =
	state.summonPet, state.dismissPet, state.setDistance, state.interrupt
local buff, debuff, cooldown, active_dot, pet, totem, action =
	state.buff, state.debuff, state.cooldown, state.active_dot, state.pet, state.totem, state.action

-- Runes (unified model on the resource itself to avoid collision with a state table)
do
	local function buildTypeCounter(indices, typeId)
		return setmetatable({}, {
			__index = function(_, k)
				if k == "count" then
					local ready = 0
					if typeId == 4 then
						for i = 1, 6 do
							local start, duration, isReady = GetRuneCooldown(i)
							local rtype = GetRuneType(i)
							if
								(isReady or (start and duration and (start + duration) <= state.query_time))
								and rtype == 4
							then
								ready = ready + 1
							end
						end
					else
						for _, i in ipairs(indices) do
							local start, duration, isReady = GetRuneCooldown(i)
							if isReady or (start and duration and (start + duration) <= state.query_time) then
								ready = ready + 1
							end
						end
					end
					return ready
				end
				return 0
			end,
		})
	end

	spec:RegisterResource(
		5,
		{},
		setmetatable({
			expiry = { 0, 0, 0, 0, 0, 0 },
			cooldown = 10,
			max = 6,
			reset = function()
				local t = state.runes
				for i = 1, 6 do
					local start, duration, ready = GetRuneCooldown(i)
					start = start or 0
					duration = duration or (10 * state.haste)
					t.expiry[i] = ready and 0 or (start + duration)
					t.cooldown = duration
				end
			end,
		}, {
			__index = function(t, k)
				-- runes.time_to_1 .. runes.time_to_6
				local idx = tostring(k):match("time_to_(%d)")
				if idx then
					local i = tonumber(idx)
					local e = t.expiry[i] or 0
					return math.max(0, e - state.query_time)
				end
				if k == "blood" then
					return buildTypeCounter({ 1, 2 }, 1)
				end
				if k == "frost" then
					return buildTypeCounter({ 5, 6 }, 2)
				end
				if k == "unholy" then
					return buildTypeCounter({ 3, 4 }, 3)
				end
				if k == "death" then
					return buildTypeCounter({}, 4)
				end
				if k == "count" or k == "current" then
					local c = 0
					for i = 1, 6 do
						if t.expiry[i] <= state.query_time then
							c = c + 1
						end
					end
					return c
				end
				return rawget(t, k)
			end,
		})
	) -- Runes = 5 in MoP with unified state

	-- Keep expiry fresh when we reset the simulation step
	spec:RegisterHook("reset_precast", function()
		if state.runes and state.runes.reset then
			state.runes.reset()
		end
	end)
end

-- Register resources
spec:RegisterResource(6) -- RunicPower = 6 in MoP

-- Register individual rune types for MoP 5.5.0
spec:RegisterResource(
	20,
	{ -- Blood Runes = 20 in MoP
		rune_regen = {
			last = function()
				return state.query_time
			end,
			stop = function(x)
				return x == 2
			end,
			interval = function(time, val)
				local r = state.blood_runes
				if val == 2 then
					return -1
				end
				return r.expiry[val + 1] - time
			end,
			value = 1,
		},
	},
	setmetatable({
		expiry = { 0, 0 },
		cooldown = 10,
		regen = 0,
		max = 2,
		forecast = {},
		fcount = 0,
		times = {},
		values = {},
		resource = "blood_runes",

	reset = function()
		local t = state.blood_runes
		local count = 0
		-- Blood runes are in slots 1 and 2
		-- Death runes in these slots can also be used as blood runes
		for i = 1, 2 do
			local start, duration, ready = GetRuneCooldown(i)
			local runeType = GetRuneType(i)
			start = start or 0
			duration = duration or (10 * state.haste)
			
			-- Count blood runes (type 1) AND death runes (type 4) in blood slots
			if runeType == 1 or runeType == 4 then
				count = count + 1
				t.expiry[count] = ready and 0 or (start + duration)
				t.cooldown = duration
			end
		end
		for i = count + 1, 2 do
			t.expiry[i] = 999
		end
		table.sort(t.expiry)
		t.actual = nil
	end,

		gain = function(amount)
			local t = state.blood_runes
			for i = 1, amount do
				table.insert(t.expiry, 0)
				t.expiry[3] = nil
			end
			table.sort(t.expiry)
			t.actual = nil
		end,

		spend = function(amount)
			local t = state.blood_runes
			for i = 1, amount do
				local nextReady = (t.expiry[1] > 0 and t.expiry[1] or state.query_time) + t.cooldown
				table.remove(t.expiry, 1)
				table.insert(t.expiry, nextReady)
			end
			t.actual = nil
		end,

		timeTo = function(x)
			return state:TimeToResource(state.blood_runes, x)
		end,
	}, {
		__index = function(t, k)
			if k == "actual" then
				local amount = 0
				for i = 1, 2 do
					if t.expiry[i] <= state.query_time then
						amount = amount + 1
					end
				end
				return amount
			elseif k == "current" or k == "count" then
				return t.actual
			end
			return rawget(t, k)
		end,
	})
)

spec:RegisterResource(
	21,
	{ -- Frost Runes = 21 in MoP
		rune_regen = {
			last = function()
				return state.query_time
			end,
			stop = function(x)
				return x == 2
			end,
			interval = function(time, val)
				local r = state.frost_runes
				if val == 2 then
					return -1
				end
				return r.expiry[val + 1] - time
			end,
			value = 1,
		},
	},
	setmetatable({
		expiry = { 0, 0 },
		cooldown = 10,
		regen = 0,
		max = 2,
		forecast = {},
		fcount = 0,
		times = {},
		values = {},
		resource = "frost_runes",

	reset = function()
		local t = state.frost_runes
		local count = 0
		-- Frost runes are in slots 5 and 6
		-- Death runes in these slots can also be used as frost runes
		for i = 5, 6 do
			local start, duration, ready = GetRuneCooldown(i)
			local runeType = GetRuneType(i)
			start = start or 0
			duration = duration or (10 * state.haste)
			
			-- Count frost runes (type 3) AND death runes (type 4) in frost slots
			if runeType == 3 or runeType == 4 then
				count = count + 1
				t.expiry[count] = ready and 0 or (start + duration)
				t.cooldown = duration
			end
		end
		for i = count + 1, 2 do
			t.expiry[i] = 999
		end
		table.sort(t.expiry)
		t.actual = nil
	end,

		gain = function(amount)
			local t = state.frost_runes
			for i = 1, amount do
				table.insert(t.expiry, 0)
				t.expiry[3] = nil
			end
			table.sort(t.expiry)
			t.actual = nil
		end,

		spend = function(amount)
			local t = state.frost_runes
			for i = 1, amount do
				local nextReady = (t.expiry[1] > 0 and t.expiry[1] or state.query_time) + t.cooldown
				table.remove(t.expiry, 1)
				table.insert(t.expiry, nextReady)
			end
			t.actual = nil
		end,

		timeTo = function(x)
			return state:TimeToResource(state.frost_runes, x)
		end,
	}, {
		__index = function(t, k)
			if k == "actual" then
				local amount = 0
				for i = 1, 2 do
					if t.expiry[i] <= state.query_time then
						amount = amount + 1
					end
				end
				return amount
			elseif k == "current" or k == "count" then
				return t.actual
			end
			return rawget(t, k)
		end,
	})
)

spec:RegisterResource(
	22,
	{ -- Unholy Runes = 22 in MoP
		rune_regen = {
			last = function()
				return state.query_time
			end,
			stop = function(x)
				return x == 2
			end,
			interval = function(time, val)
				local r = state.unholy_runes
				if val == 2 then
					return -1
				end
				return r.expiry[val + 1] - time
			end,
			value = 1,
		},
	},
	setmetatable({
		expiry = { 0, 0 },
		cooldown = 10,
		regen = 0,
		max = 2,
		forecast = {},
		fcount = 0,
		times = {},
		values = {},
		resource = "unholy_runes",

	reset = function()
		local t = state.unholy_runes
		local count = 0
		-- Unholy runes are in slots 3 and 4
		-- Death runes in these slots can also be used as unholy runes
		for i = 3, 4 do
			local start, duration, ready = GetRuneCooldown(i)
			local runeType = GetRuneType(i)
			start = start or 0
			duration = duration or (10 * state.haste)
			
			-- Count unholy runes (type 2) AND death runes (type 4) in unholy slots
			if runeType == 2 or runeType == 4 then
				count = count + 1
				t.expiry[count] = ready and 0 or (start + duration)
				t.cooldown = duration
			end
		end
		for i = count + 1, 2 do
			t.expiry[i] = 999
		end
		table.sort(t.expiry)
		t.actual = nil
	end,

		gain = function(amount)
			local t = state.unholy_runes
			for i = 1, amount do
				table.insert(t.expiry, 0)
				t.expiry[3] = nil
			end
			table.sort(t.expiry)
			t.actual = nil
		end,

		spend = function(amount)
			local t = state.unholy_runes
			for i = 1, amount do
				local nextReady = (t.expiry[1] > 0 and t.expiry[1] or state.query_time) + t.cooldown
				table.remove(t.expiry, 1)
				table.insert(t.expiry, nextReady)
			end
			t.actual = nil
		end,

		timeTo = function(x)
			return state:TimeToResource(state.unholy_runes, x)
		end,
	}, {
		__index = function(t, k)
			if k == "actual" then
				local amount = 0
				for i = 1, 2 do
					if t.expiry[i] <= state.query_time then
						amount = amount + 1
					end
				end
				return amount
			elseif k == "current" or k == "count" then
				return t.actual
			end
			return rawget(t, k)
		end,
	})
)

spec:RegisterResource(6, {
	-- Frost Fever Tick RP (20% chance to generate 4 RP)
	frost_fever_tick = {

		aura = "frost_fever",

		last = function()
			local app = state.dot.frost_fever.applied

			return app + floor(state.query_time - app)
		end,

		interval = 1,
		value = function()
			-- 20% chance * 4 RP = 0.8 RP per tick
			-- We'll lowball to 0.6 RP for conservative estimate
			return 0.6 * min(state.active_dot.frost_fever or 0, 5)
		end,
	},

	-- Runic Attenuation (mainhand swings 50% chance to generate 3 RP)
	runic_attenuation = {
		talent = "runic_attenuation",
		swing = "mainhand",

		last = function()
			local swing = state.swings.mainhand
			local t = state.query_time
			if state.mainhand_speed == 0 then
				return 0
			else
				return swing + floor((t - swing) / state.mainhand_speed) * state.mainhand_speed
			end
		end,

		interval = "mainhand_speed",

		stop = function()
			return state.swings.mainhand == 0
		end,

		value = function()
			-- 50% chance * 3 RP = 1.5 RP per swing
			-- We'll lowball to 1.0 RP
			return state.talent.runic_attenuation.enabled and 1.0 or 0
		end,
	},

	-- Scourge Strike - Core Unholy ability
	scourge_strike = {
		id = 55090,
		cast = 0,
		cooldown = 0,
		gcd = "spell",
		
		spend_runes = {0, 0, 1}, -- 0 Blood, 0 Frost, 1 Unholy
		
		gain = 10,
		gainType = "runic_power",
		
		startsCombat = true,
		
		handler = function ()
			-- Shadow damage component scales with diseases
			local disease_count = 0
			if debuff.frost_fever.up then disease_count = disease_count + 1 end
			if debuff.blood_plague.up then disease_count = disease_count + 1 end
			
			-- Shadow damage is 50% of physical damage per disease
			-- This is handled by the simulation engine
		end,
	},

	-- Death Coil - Core Unholy ability
	death_coil = {
		id = 47541,
		cast = 0,
		cooldown = 0,
		gcd = "spell",
		
		spend = 40,
		spendType = "runic_power",
		
		startsCombat = true,
		
		handler = function ()
			-- Shadow Infusion proc chance (100% when active)
			if buff.shadow_infusion.up then
				removeBuff("shadow_infusion")
				-- Empower ghoul
			end
			
			-- Sudden Doom consumption
			if buff.sudden_doom.up then
				removeBuff("sudden_doom")
				-- Free cast
			end
		end,
	},

	-- Festering Strike - Disease application
	festering_strike = {
		id = 85948,
		cast = 0,
		cooldown = 0,
		gcd = "spell",
		
		spend_runes = {1, 1, 0}, -- 1 Blood, 1 Frost, 0 Unholy
		
		gain = 15,
		gainType = "runic_power",
		
		startsCombat = true,
		
		handler = function ()
			-- Apply both diseases
			applyDebuff("target", "frost_fever", 30)
			applyDebuff("target", "blood_plague", 30)
		end,
	},

	-- Summon Gargoyle - Major cooldown
	summon_gargoyle = {
		id = 49206,
		cast = 0,
		cooldown = 180,
		gcd = "spell",
		
		startsCombat = false,
		
		toggle = "cooldowns",
		
		handler = function ()
			summonPet("gargoyle", 30)
		end,
	},

	-- Dark Transformation - Pet enhancement
	dark_transformation = {
		id = 63560,
		cast = 0,
		cooldown = 60,
		gcd = "spell",
		
		startsCombat = false,
		
		usable = function ()
			return pet.ghoul.active
		end,
		
		handler = function ()
			applyBuff("dark_transformation")
			-- Enhance pet abilities
		end,
	},

	-- Blood Strike - Basic strike
	blood_strike = {
		id = 45529,
		cast = 0,
		cooldown = 0,
		gcd = "spell",
		
		spend_runes = {1, 0, 0}, -- 1 Blood, 0 Frost, 0 Unholy
		
		gain = 10,
		gainType = "runic_power",
		
		startsCombat = true,
		
		handler = function ()
			-- Basic damage ability
		end,
	},

	-- Death and Decay - AoE ground effect
	death_and_decay = {
		id = 43265,
		cast = 0,
		cooldown = 0,
		gcd = "spell",
		
		spend_runes = {0, 0, 1}, -- 0 Blood, 0 Frost, 1 Unholy
		
		gain = 10,
		gainType = "runic_power",
		
		startsCombat = true,
		
		handler = function ()
			applyDebuff("target", "death_and_decay", 10)
		end,
	},

	-- Soul Reaper - Execute ability
	soul_reaper = {
		id = 114867,
		cast = 0,
		cooldown = 0,
		gcd = "spell",
		
		spend_runes = {0, 0, 1}, -- 0 Blood, 0 Frost, 1 Unholy
		
		gain = 10,
		gainType = "runic_power",
		
		startsCombat = true,
		
		handler = function ()
			applyDebuff("target", "soul_reaper", 5)
		end,
	},

	-- Additional missing abilities for APL compatibility
	-- Pillar of Frost - Major cooldown
	pillar_of_frost = {
		id = 51271,
		cast = 0,
		cooldown = 60,
		gcd = "spell",
		
		startsCombat = false,
		
		toggle = "cooldowns",
		
		handler = function ()
			applyBuff("pillar_of_frost", 20)
		end,
	},

	-- Raise Dead - Pet summon
	raise_dead = {
		id = 46584,
		cast = 0,
		cooldown = 180,
		gcd = "spell",
		
		startsCombat = false,
		
		handler = function ()
			summonPet("ghoul", 60)
		end,
	},

	-- Outbreak - Disease application
	outbreak = {
		id = 77575,
		cast = 0,
		cooldown = 60,
		gcd = "spell",
		
		startsCombat = true,
		
		handler = function ()
			applyDebuff("target", "frost_fever", 30)
			applyDebuff("target", "blood_plague", 30)
		end,
	},

	-- Plague Strike - Blood Plague application
	plague_strike = {
		id = 45462,
		cast = 0,
		cooldown = 0,
		gcd = "spell",
		
		spend_runes = {1, 0, 0}, -- 1 Blood, 0 Frost, 0 Unholy
		
		gain = 10,
		gainType = "runic_power",
		
		startsCombat = true,
		
		handler = function ()
			applyDebuff("target", "blood_plague", 30)
		end,
	},

	-- Icy Touch - Frost Fever application
	icy_touch = {
		id = 45477,
		cast = 0,
		cooldown = 0,
		gcd = "spell",
		
		spend_runes = {0, 1, 0}, -- 0 Blood, 1 Frost, 0 Unholy
		
		gain = 10,
		gainType = "runic_power",
		
		startsCombat = true,
		
		handler = function ()
			applyDebuff("target", "frost_fever", 30)
		end,
	},

	-- Empower Rune Weapon - Rune refresh
	empower_rune_weapon = {
		id = 47568,
		cast = 0,
		cooldown = 300,
		gcd = "spell",
		
		startsCombat = false,
		
		toggle = "cooldowns",
		
		handler = function ()
			gain(6, "runes")
			gain(25, "runic_power")
		end,
	},

	-- Horn of Winter - Runic power generation
	horn_of_winter = {
		id = 57330,
		cast = 0,
		cooldown = 20,
		gcd = "spell",
		
		startsCombat = false,
		
		handler = function ()
			gain(25, "runic_power")
			applyBuff("horn_of_winter", 10)
		end,
	},

	-- Mind Freeze - Interrupt
	mind_freeze = {
		id = 47528,
		cast = 0,
		cooldown = 15,
		gcd = "off",
		
		startsCombat = true,
		
		toggle = "interrupts",
		
		debuff = "casting",
		readyTime = state.timeToInterrupt,
		
		handler = function ()
			interrupt()
		end,
	},

	-- Anti-Magic Shell - Defensive
	antimagic_shell = {
		id = 48707,
		cast = 0,
		cooldown = 45,
		gcd = "off",
		
		startsCombat = false,
		
		toggle = function()
			if settings.dps_shell then
				return
			end
			return "defensives"
		end,
		
		handler = function ()
			applyBuff("antimagic_shell")
		end,
	},

	-- Unholy Blight - Disease spread
	unholy_blight = {
		id = 115989,
		cast = 0,
		cooldown = 60,
		gcd = "spell",
		
		talent = "unholy_blight",
		startsCombat = true,
		
		handler = function ()
			applyDebuff("target", "unholy_blight", 10)
		end,
	},

	-- Plague Leech - Rune recovery
	plague_leech = {
		id = 123693,
		cast = 0,
		cooldown = 25,
		gcd = "spell",
		
		talent = "plague_leech",
		startsCombat = true,
		
		usable = function ()
			return debuff.frost_fever.up and debuff.blood_plague.up
		end,
		
		handler = function ()
			removeDebuff("target", "frost_fever")
			removeDebuff("target", "blood_plague")
			gain(1, "runes")
		end,
	},
})

local spendHook = function(amt, resource, noHook)
	if amt > 0 and resource == "runes" and active_dot.shackle_the_unworthy > 0 then
		reduceCooldown("shackle_the_unworthy", 4 * amt)
	end
end

spec:RegisterHook("spend", spendHook)

-- Talents
spec:RegisterTalents({
	-- Tier 1 (Level 56)
	roiling_blood = { 1, 1, 108170 }, -- Your Blood Boil ability now also triggers Pestilence if it strikes a diseased target.
	plague_leech = { 1, 2, 123693 }, -- Extract diseases from an enemy target, consuming up to 2 diseases on the target to gain 1 Rune of each type that was removed.
	unholy_blight = { 1, 3, 115989 }, -- Causes you to spread your diseases to all enemy targets within 10 yards.

	-- Tier 2 (Level 57)
	lichborne = { 2, 1, 49039 }, -- Draw upon unholy energy to become undead for 10 sec. While undead, you are immune to Charm, Fear, and Sleep effects.
	anti_magic_zone = { 2, 2, 51052 }, -- Places a large, stationary Anti-Magic Zone that reduces spell damage taken by party or raid members by 40%. The Anti-Magic Zone lasts for 30 sec or until it absorbs a massive amount of spell damage.
	purgatory = { 2, 3, 114556 }, -- An unholy pact that prevents fatal damage, instead absorbing incoming healing equal to the damage that would have been fatal for 3 sec.

	-- Tier 3 (Level 58)
	deaths_advance = { 3, 1, 96268 }, -- For 8 sec, you are immune to movement impairing effects and your movement speed is increased by 50%.
	chilblains = { 3, 2, 50041 }, -- Victims of your Chains of Ice take 5% increased damage from your abilities for 8 sec.
	asphyxiate = { 3, 3, 108194 }, -- Lifts the enemy target off the ground, crushing their throat and stunning them for 5 sec.

	-- Tier 4 (Level 60)
	death_pact = { 4, 1, 48743 }, -- Drains 50% of your summoned minion's health to heal you for 25% of your maximum health.
	death_siphon = { 4, 2, 108196 }, -- Deals Shadow damage to the target and heals you for 150% of the damage dealt.
	conversion = { 4, 3, 119975 }, -- Continuously converts 2% of your maximum health per second into 20% of maximum health as healing.

	-- Tier 5 (Level 75)
	blood_tap = { 5, 1, 45529 }, -- Consume 5 charges from your Blood Charges to immediately activate a random depleted rune.
	runic_empowerment = { 5, 2, 81229 }, -- When you use a rune, you have a 45% chance to immediately regenerate that rune.
	runic_corruption = { 5, 3, 51462 }, -- When you hit with a Death Coil, Frost Strike, or Rune Strike, you have a 45% chance to regenerate a rune.

	-- Tier 6 (Level 90)
	gorefiends_grasp = { 6, 1, 108199 }, -- Shadowy tendrils coil around all enemies within 20 yards of a hostile target, pulling them to the target's location.
	remorseless_winter = { 6, 2, 108200 }, -- Surrounds the Death Knight with a swirling blizzard that grows over 8 sec, slowing enemies by up to 50% and reducing their melee and ranged attack speed by up to 20%.
	desecrated_ground = { 6, 3, 108201 }, -- Corrupts the ground beneath you, causing all nearby enemies to deal 10% less damage for 30 sec.
})

-- Glyphs
spec:RegisterGlyphs({
	-- Major Glyphs (affecting tanking and mechanics)
	[58623] = "antimagic_shell", -- Causes your Anti-Magic Shell to absorb all incoming magical damage, up to the absorption limit.
	[58620] = "chains_of_ice", -- Your Chains of Ice also causes 144 to 156 Frost damage, with additional damage depending on your attack power.
	[63330] = "dancing_rune_weapon", -- Increases your threat generation by 100% while your Dancing Rune Weapon is active, but reduces its damage dealt by 25%.
	[63331] = "dark_simulacrum", -- Reduces the cooldown of Dark Simulacrum by 30 sec and increases its duration by 4 sec.
	[96279] = "dark_succor", -- When you kill an enemy that yields experience or honor, while in Frost or Unholy Presence, your next Death Strike within 15 sec is free and will restore at least 20% of your maximum health.
	[58629] = "death_and_decay", -- Your Death and Decay also reduces the movement speed of enemies within its radius by 50%.
	[63333] = "death_coil", -- Your Death Coil spell is now usable on all allies.  When cast on a non-undead ally, Death Coil shrouds them with a protective barrier that absorbs up to [(1133 + 0.514 * Attack Power) * 1] damage.
	[62259] = "death_grip", -- Increases the range of your Death Grip ability by 5 yards.
	[58671] = "enduring_infection", -- Your diseases are undispellable, but their damage dealt is reduced by 15%.
	[146650] = "festering_blood", -- Blood Boil will now treat all targets as though they have Blood Plague or Frost Fever applied.
	[58673] = "icebound_fortitude", -- Reduces the cooldown of your Icebound Fortitude by 50%, but also reduces its duration by 75%.
	[58631] = "icy_touch", -- Your Icy Touch dispels one helpful Magic effect from the target.
	[58686] = "mind_freeze", -- Reduces the cooldown of your Mind Freeze ability by 1 sec, but also raises its cost by 10 Runic Power.
	[59332] = "outbreak", -- Your Outbreak spell no longer has a cooldown, but now costs 30 Runic Power.
	[58657] = "pestilence", -- Increases the radius of your Pestilence effect by 5 yards.
	[58635] = "pillar_of_frost", -- Empowers your Pillar of Frost, making you immune to all effects that cause loss of control of your character, but also reduces your movement speed by 70% while the ability is active.
	[146648] = "regenerative_magic", -- If Anti-Magic Shell expires after its full duration, the cooldown is reduced by up to 50%, based on the amount of damage absorbtion remaining.
	[58647] = "shifting_presences", -- You retain 70% of your Runic Power when switching Presences.
	[58618] = "strangulate", -- Increases the Silence duration of your Strangulate ability by 2 sec when used on a target who is casting a spell.
	[146645] = "swift_death", -- The haste effect granted by Soul Reaper now also increases your movement speed by 30% for the duration.
	[146646] = "loud_horn", -- Your Horn of Winter now generates an additional 10 Runic Power, but the cooldown is increased by 100%.
	[59327] = "unholy_command", -- Immediately finishes the cooldown of your Death Grip upon dealing a killing blow to a target that grants experience or honor.
	[58616] = "unholy_frenzy", -- Causes your Unholy Frenzy to no longer deal damage to the affected target.
	[58676] = "vampiric_blood", -- Increases the bonus healing received while your Vampiric Blood is active by an additional 15%, but your Vampiric Blood no longer grants you health.

	-- Minor Glyphs (convenience and visual)
	[58669] = "army_of_the_dead", -- The ghouls summoned by your Army of the Dead no longer taunt their target.
	[59336] = "corpse_explosion", -- Teaches you the ability Corpse Explosion.
	[60200] = "death_gate", -- Reduces the cast time of your Death Gate spell by 60%.
	[58677] = "deaths_embrace", -- Your Death Coil refunds 20 Runic Power when used to heal an allied minion, but will no longer trigger Blood Tap when used this way.
	[58642] = "foul_menagerie", -- Causes your Army of the Dead spell to summon an assortment of undead minions.
	[58680] = "horn_of_winter", -- When used outside of combat, your Horn of Winter ability causes a brief, localized snow flurry.
	[59307] = "path_of_frost", -- Your Path of Frost ability allows you to fall from a greater distance without suffering damage.
	[59309] = "resilient_grip", -- When your Death Grip ability fails because its target is immune, its cooldown is reset.
	[58640] = "geist", -- Your Raise Dead spell summons a geist instead of a ghoul.
	[146653] = "long_winter", -- The effect of your Horn of Winter now lasts for 1 hour.
	[146652] = "skeleton", -- Your Raise Dead spell summons a skeleton instead of a ghoul.
	[63335] = "tranquil_grip", -- Your Death Grip spell no longer taunts the target.
})

-- Auras
spec:RegisterAuras({
	-- Talent: Absorbing up to $w1 magic damage. Immune to harmful magic effects.
	-- https://wowhead.com/spell=48707
	antimagic_shell = {
		id = 48707,
		duration = 5,
		max_stack = 1,
	},
	-- Talent: Stunned.
	-- https://wowhead.com/spell=108194
	asphyxiate = {
		id = 108194,
		duration = 5.0,
		mechanic = "stun",
		type = "Magic",
		max_stack = 1,
	},
	-- Talent: Movement slowed $w1% $?$w5!=0[and Haste reduced $w5% ][]by frozen chains.
	-- https://wowhead.com/spell=45524
	chains_of_ice = {
		id = 45524,
		duration = 8,
		mechanic = "snare",
		type = "Magic",
		max_stack = 1,
	},
	-- Taunted.
	-- https://wowhead.com/spell=56222
	dark_command = {
		id = 56222,
		duration = 3,
		mechanic = "taunt",
		max_stack = 1,
	},
	-- Your next Death Strike is free and heals for an additional $s1% of maximum health.
	-- https://wowhead.com/spell=101568
	dark_succor = {
		id = 101568,
		duration = 15,
		max_stack = 1,
	},
	-- Talent: $?$w2>0[Transformed into an undead monstrosity.][Gassy.] Damage dealt increased by $w1%.
	-- https://wowhead.com/spell=63560
	dark_transformation = {
		id = 63560,
		duration = 30,
		max_stack = 1,
		generate = function(t)
			local name, _, count, _, duration, expires, caster = FindUnitBuffByID("pet", 63560)

			if name then
				t.name = name
				t.count = 1
				t.expires = expires
				t.applied = expires - duration
				t.caster = caster
				return
			end

			t.count = 0
			t.expires = 0
			t.applied = 0
			t.caster = "nobody"
		end,
	},
	-- Inflicts $s1 Shadow damage every sec.
	death_and_decay = {
		id = 43265,
		duration = 10,
		tick_time = 1.0,
		max_stack = 1,
	},
	-- Your movement speed is increased by $w1%, you cannot be slowed below $s2% of normal speed, and you are immune to forced movement effects and knockbacks.
	deaths_advance = {
		id = 96268,
		duration = 8,
		type = "Magic",
		max_stack = 1,
	},
	-- Defile the targeted ground, dealing Shadow damage to all enemies over $d. While you remain within your Defile, your Scourge Strike will hit multiple enemies near the target. If any enemies are standing in the Defile, it grows in size and deals increasing damage every sec.
	defile = {
		id = 43265, -- In MoP, Defile uses the same spell ID as Death and Decay
		duration = 30,
		tick_time = 1,
		max_stack = 1,
	},
	-- Suffering $w1 Frost damage every $t1 sec.
	-- https://wowhead.com/spell=55095
	frost_fever = {
		id = 55095,
		duration = 30,
		tick_time = 3,
		max_stack = 1,
		type = "Disease",
	},
	-- Talent: Damage taken reduced by $w3%. Immune to Stun effects.
	-- https://wowhead.com/spell=48792
	icebound_fortitude = {
		id = 48792,
		duration = 8,
		max_stack = 1,
	},
	-- Leech increased by $s1%$?a389682[, damage taken reduced by $s8%][] and immune to Charm, Fear and Sleep. Undead.
	-- https://wowhead.com/spell=49039
	lichborne = {
		id = 49039,
		duration = 10,
		tick_time = 1,
		max_stack = 1,
	},
	-- A necrotic strike shield that absorbs the next $w1 healing received.
	necrotic_strike = {
		id = 73975,
		duration = 10,
		max_stack = 1,
	},
	-- Grants the ability to walk across water.
	-- https://wowhead.com/spell=3714
	path_of_frost = {
		id = 3714,
		duration = 600,
		tick_time = 0.5,
		max_stack = 1,
	},
	-- Inflicted with a plague that spreads to nearby enemies when dispelled.
	plague_leech = {
		id = 123693,
		duration = 3,
		max_stack = 1,
	},
	-- An unholy pact that prevents fatal damage.
	purgatory = {
		id = 114556,
		duration = 3,
		max_stack = 1,
	},
	-- TODO: Is a pet.
	raise_dead = {
		id = 46584,
		max_stack = 1,
	},
	-- Frost damage taken from the Death Knight's abilities increased by $s1%.
	-- https://wowhead.com/spell=51714
	razorice = {
		id = 51714,
		duration = 20,
		tick_time = 1,
		type = "Magic",
		max_stack = 5,
	},
	-- Increases your rune regeneration rate for 3 sec.
	runic_corruption = {
		id = 51460,
		duration = function()
			return 3 * haste
		end,
		max_stack = 1,
	},
	-- Talent: Afflicted by Soul Reaper, if the target is below $s3% health this effect will explode dealing additional Shadowfrost damage.
	-- https://wowhead.com/spell=130736
	soul_reaper = {
		id = 130736,
		duration = 5,
		tick_time = 5,
		type = "Magic",
		max_stack = 1,
	},
	-- Your next Death Coil cost no Runic Power and is guaranteed to critically strike.
	sudden_doom = {
		id = 81340,
		duration = 10,
		max_stack = 1,
	},

	-- Grants your successful Death Coils a chance to empower your active Ghoul, increasing its damage dealt by 10% for 30 sec.  Stacks up to 5 times.
	shadow_infusion = {
		id = 91342,
		duration = 30,
		max_stack = 5,
		generate = function(t)
			local name, _, count, _, duration, expires, caster = FindUnitBuffByID("pet", 91342)

			if name then
				t.name = name
				t.count = count
				t.expires = expires
				t.applied = expires - duration
				t.caster = caster
				return
			end

			t.count = 0
			t.expires = 0
			t.applied = 0
			t.caster = "nobody"
		end,
	},
	-- Silenced.
	strangulate = {
		id = 47476,
		duration = 5,
		max_stack = 1,
	},
	-- Presence Tracking
	blood_presence = {
		id = 48263,
		duration = 3600,
		max_stack = 1,
	},

	unholy_presence = {
		id = 48265,
		duration = 3600,
		max_stack = 1,
	},

	frost_presence = {
		id = 48266,
		duration = 3600,
		max_stack = 1,
	},
	-- Suffering $w1 Shadow damage every $t1 sec. Erupts for damage split among all nearby enemies when the infected dies.
	-- https://wowhead.com/spell=191587
	virulent_plague = {
		id = 191587,
		duration = 21,
		tick_time = 3,
		type = "Disease",
		max_stack = 1,
	},
	-- The touch of the spirit realm lingers....
	-- https://wowhead.com/spell=97821
	voidtouched = {
		id = 97821,
		duration = 300,
		max_stack = 1,
	},
	-- Talent: Movement speed increased by $w1%. Cannot be slowed below $s2% of normal movement speed. Cannot attack.
	-- https://wowhead.com/spell=212552
	wraith_walk = {
		id = 212552,
		duration = 4,
		max_stack = 1,
	},

	-- PvP Talents
	-- Your next spell with a mana cost will be copied by the Death Knight's runeblade.
	dark_simulacrum = {
		id = 77606,
		duration = 12,
		max_stack = 1,
	},
	-- Your runeblade contains trapped magical energies, ready to be unleashed.
	dark_simulacrum_buff = {
		id = 77616,
		duration = 12,
		max_stack = 1,
	},

	-- Blood Tap charges for converting runes to Death Runes
	blood_charge = {
		id = 114851,
		duration = 25,
		max_stack = 12,
	},

	-- Horn of Winter buff - increases Attack Power
	horn_of_winter = {
		id = 57330,
		duration = 300,
		max_stack = 1,
	},

	-- Unholy Frenzy buff - increase Attack Speed
	unholy_frenzy = {
		id = 49016,
		duration = 30,
		max_stack = 1,
	},

	-- Orc Racial: Blood Fury (AP/Haste variant used in MoP APL windows)
	blood_fury = {
		id = 33697,
		duration = 15,
		max_stack = 1,
	},

	-- Troll Racial: Berserking (used in MoP APL windows)
	berserking = {
		id = 26297,
		duration = 10,
		max_stack = 1,
	},

	-- Engineering: Synapse Springs (on-use gloves)
	synapse_springs = {
		id = 126734,
		duration = 10,
		max_stack = 1,
	},

	-- Unholy Blight area effect
	unholy_blight = {
		id = 115989,
		duration = 10,
		max_stack = 1,
	},

	-- Inflicted with a disease that deals Shadow damage over time.
	blood_plague = {
		id = 55078,
		duration = 30,
		tick_time = 3,
		type = "Disease",
		max_stack = 1,
	},
	fallen_crusader = {
		id = 53344,
		duration = 15,
		max_stack = 1,
		type = "Physical",
	},
	-- Unholy Strength buff - increases Strength by 15% for 15 seconds, procs from Fallen Crusader
	-- Used for snapshotting other abilities
	unholy_strength = {
		id = 53365,
		duration = 15,
		max_stack = 1,
		type = "Physical",
	},

	-- Shadow Infusion - Empowers ghoul
	shadow_infusion = {
		id = 91342,
		duration = 30,
		max_stack = 5,
	},

	-- Sudden Doom - Free Death Coil
	sudden_doom = {
		id = 81340,
		duration = 10,
		max_stack = 1,
	},

	-- Dark Transformation - Enhanced pet
	dark_transformation = {
		id = 63560,
		duration = 20,
		max_stack = 1,
	},

	-- Frost Fever - Disease
	frost_fever = {
		id = 55095,
		duration = 30,
		max_stack = 1,
		tick_time = 3,
		type = "Disease",
	},

	-- Death and Decay - Ground effect
	death_and_decay = {
		id = 43265,
		duration = 10,
		max_stack = 1,
		tick_time = 1,
	},

	-- Soul Reaper - Execute debuff
	soul_reaper = {
		id = 114867,
		duration = 5,
		max_stack = 1,
		tick_time = 1,
	},

	-- Blood Charge - Blood Tap resource
	blood_charge = {
		id = 114851,
		duration = 30,
		max_stack = 5,
	},

	-- Ebon Plaguebringer - Disease enhancement
	ebon_plaguebringer = {
		id = 51160,
		duration = 30,
		max_stack = 1,
	},

	-- Unholy Might - Mastery buff
	unholy_might = {
		id = 53365,
		duration = 15,
		max_stack = 1,
	},
})

-- Pets
spec:RegisterPets({
	ghoul = {
		id = 26125,
		spell = "raise_dead",
		duration = 3600,
	},
	risen_skulker = {
		id = 99541,
		spell = "raise_dead",
		duration = function()
			return talent.raise_dead_2.enabled and 3600 or 60
		end,
	},
})

-- Totems (which are sometimes pets in MoP)
spec:RegisterTotems({
	gargoyle = {
		id = 49206,
		duration = 30,
	},
	ghoul = {
		id = 26125,
		duration = 3600,
	},
	army_ghoul = {
		id = 24207,
		duration = 40,
	},
})

local dmg_events = {
	SPELL_DAMAGE = 1,
	SPELL_PERIODIC_DAMAGE = 1,
}

local aura_removals = {
	SPELL_AURA_REMOVED = 1,
	SPELL_AURA_REMOVED_DOSE = 1,
}

local dnd_damage_ids = {
	[43265] = "death_and_decay",
}

local last_dnd_tick, dnd_spell = 0, "death_and_decay"

local sd_consumers = {
	death_coil = "doomed_bidding_magus_coil",
	epidemic = "doomed_bidding_magus_epi",
}

local db_casts = {}
local doomed_biddings = {}

local last_bb_summon = 0

-- 20250426: Decouple Death and Decay *buff* from dot.death_and_decay.ticking
spec:RegisterCombatLogEvent(
	function(
		_,
		subtype,
		_,
		sourceGUID,
		sourceName,
		sourceFlags,
		_,
		destGUID,
		destName,
		destFlags,
		_,
		spellID,
		spellName
	)
		if sourceGUID ~= state.GUID then
			return
		end

		if dnd_damage_ids[spellID] and dmg_events[subtype] then
			last_dnd_tick = GetTime()
			dnd_spell = dnd_damage_ids[spellID]
			return
		end

		if state.talent.doomed_bidding.enabled then
			if subtype == "SPELL_CAST_SUCCESS" then
				local consumer = class.abilities[spellID]
				if not consumer then
					return
				end
				consumer = consumer and consumer.key

				if sd_consumers[consumer] then
					db_casts[GetTime()] = consumer
				end
				return
			end

			if spellID == class.auras.sudden_doom.id and aura_removals[subtype] and #doomed_biddings > 0 then
				local now = GetTime()
				for time, consumer in pairs(db_casts) do
					if now - time < 0.5 then
						doomed_biddings[now + 6] = sd_consumers[consumer]
						db_casts[time] = nil
					end
				end
				return
			end
		end

		if subtype == "SPELL_SUMMON" and spellID == 434237 then
			last_bb_summon = GetTime()
			return
		end
	end
)

local dnd_model = setmetatable({}, {
	__index = function(t, k)
		if k == "ticking" then
			-- Disabled
			-- if state.query_time - class.abilities.any_dnd.lastCast < 10 then return true end
			return debuff.death_and_decay.up
		elseif k == "remains" then
			return debuff.death_and_decay.remains
		end

		return false
	end,
})

spec:RegisterStateTable("death_and_decay", dnd_model)
spec:RegisterStateTable("defile", dnd_model)

-- Death Knight state table with runeforge support
local mt_runeforges = {
	__index = function(t, k)
		return false
	end,
}

spec:RegisterStateTable(
	"death_knight",
	setmetatable({
		disable_aotd = false,
		delay = 6,
		runeforge = setmetatable({}, mt_runeforges),
	}, {
		__index = function(t, k)
			if k == "disable_iqd_execute" then
				return state.settings.disable_iqd_execute and 1 or 0
			end
			return 0
		end,
	})
)

spec:RegisterStateExpr("dnd_ticking", function()
	return state.debuff.death_and_decay.up
end)

spec:RegisterStateExpr("unholy_frenzy_haste", function()
	if buff.unholy_frenzy.up and buff.unholy_frenzy.on_self then
		-- TODO: Add haste increase
	end
end)

spec:RegisterStateExpr("fallen_crusader_up", function()
	return buff.unholy_strength.up
end)

spec:RegisterStateExpr("dnd_remains", function()
	return state.debuff.death_and_decay.remains
end)

spec:RegisterHook("step", function(time)
	if Hekili.ActiveDebug then
		Hekili:Debug(
			"Rune Regeneration Time: 1=%.2f, 2=%.2f, 3=%.2f, 4=%.2f, 5=%.2f, 6=%.2f\n",
			runes.time_to_1,
			runes.time_to_2,
			runes.time_to_3,
			runes.time_to_4,
			runes.time_to_5,
			runes.time_to_6
		)
	end
end)

local Glyphed = IsSpellKnownOrOverridesKnown

spec:RegisterGear({
	-- Mists of Pandaria Tier Sets
	tier16 = {
		items = { 99369, 99370, 99371, 99372, 99373 }, -- Death Knight T16
		auras = {
			death_shroud = {
				id = 144901,
				duration = 30,
				max_stack = 5,
			},
		},
	},
	tier15 = {
		items = { 95339, 95340, 95341, 95342, 95343 }, -- Death Knight T15
		auras = {
			unholy_vigor = {
				id = 138547,
				duration = 15,
				max_stack = 1,
			},
		},
	},
	tier14 = {
		items = { 84407, 84408, 84409, 84410, 84411 }, -- Death Knight T14
		auras = {
			shadow_clone = {
				id = 123556,
				duration = 8,
				max_stack = 1,
			},
		},
	},
})

spec:RegisterHook("TALENTS_UPDATED", function()
	class.abilityList.any_dnd = "|T136144:0|t |cff00ccff[Any " .. class.abilities.death_and_decay.name .. "]|r"
	local dnd = talent.defile.enabled and "defile" or "death_and_decay"

	class.abilities.any_dnd = class.abilities[dnd]
	rawset(cooldown, "any_dnd", nil)
	rawset(cooldown, "death_and_decay", nil)
	rawset(cooldown, "defile", nil)

	if dnd == "defile" then
		rawset(cooldown, "death_and_decay", cooldown.defile)
	else
		rawset(cooldown, "defile", cooldown.death_and_decay)
	end
end)

-- MoP ghoul/pet summoning system - much simpler than later expansions
local ghoul_applicators = {
	army_of_the_dead = {
		army_ghoul = { 40 },
	},
	summon_gargoyle = {
		gargoyle = { 30 },
	},
}

spec:RegisterHook("reset_precast", function()
	if totem.gargoyle.remains > 0 then
		summonPet("gargoyle", totem.gargoyle.remains)
	end

	local control_expires = action.control_undead.lastCast + 300
	if control_expires > state.query_time and pet.up and not pet.ghoul.up then
		summonPet("controlled_undead", control_expires - state.query_time)
	end

	for spell, ghouls in pairs(ghoul_applicators) do
		local cast_time = action[spell].lastCast

		for ghoul, info in pairs(ghouls) do
			dismissPet(ghoul)

			if cast_time > 0 then
				local expires = cast_time + info[1]

				if expires > state.query_time then
					summonPet(ghoul, expires - state.query_time)
				end
			end
		end
	end

	if buff.death_and_decay.up then
		local duration = buff.death_and_decay.duration
		if duration > 4 then
			if Hekili.ActiveDebug then
				Hekili:Debug(
					"Death and Decay buff extended by 4; %.2f to %.2f.",
					buff.death_and_decay.remains,
					buff.death_and_decay.remains + 4
				)
			end
			buff.death_and_decay.expires = buff.death_and_decay.expires + 4
		else
			if Hekili.ActiveDebug then
				Hekili:Debug(
					"Death and Decay buff with duration of %.2f not extended; %.2f remains.",
					duration,
					buff.death_and_decay.remains
				)
			end
		end
	end

	-- Death and Decay tick time is 1s; if we haven't seen a tick in 2 seconds, it's not ticking.
	local last_dnd = action[dnd_spell].lastCast
	local dnd_expires = last_dnd + 10
	if state.query_time - last_dnd_tick < 2 and dnd_expires > state.query_time then
		applyDebuff("target", "death_and_decay", dnd_expires - state.query_time)
		debuff.death_and_decay.duration = 10
		debuff.death_and_decay.applied = debuff.death_and_decay.expires - 10
	end

	if Hekili.ActiveDebug then
		Hekili:Debug("Pet is %s.", pet.alive and "alive" or "dead")
	end
end)

-- MoP runeforges are different
local runeforges = {
	[3370] = "razorice",
	[3368] = "fallen_crusader",
	[3847] = "stoneskin_gargoyle",
}

local function ResetRuneforges()
	if not state.death_knight then
		state.death_knight = {}
	end
	if not state.death_knight.runeforge then
		state.death_knight.runeforge = {}
	end
	table.wipe(state.death_knight.runeforge)
end

local function UpdateRuneforge(slot, item)
	if slot == 16 or slot == 17 then
		if not state.death_knight then
			state.death_knight = {}
		end
		if not state.death_knight.runeforge then
			state.death_knight.runeforge = {}
		end

		local link = GetInventoryItemLink("player", slot)
		local enchant = link and link:match("item:%d+:(%d+)")

		if enchant then
			enchant = tonumber(enchant)
			local name = runeforges[enchant]

			if name then
				state.death_knight.runeforge[name] = true

				if name == "razorice" and slot == 16 then
					state.death_knight.runeforge.razorice_mh = true
				elseif name == "razorice" and slot == 17 then
					state.death_knight.runeforge.razorice_oh = true
				elseif name == "fallen_crusader" and slot == 16 then
					state.death_knight.runeforge.fallen_crusader_mh = true
				elseif name == "fallen_crusader" and slot == 17 then
					state.death_knight.runeforge.fallen_crusader_oh = true
				end
			end
		end
	end
end

-- Keep runeforge state fresh using classic-safe events.
do
	local f = CreateFrame("Frame")
	f:RegisterEvent("PLAYER_ENTERING_WORLD")
	f:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
	f:SetScript("OnEvent", function(_, evt, ...)
		if evt == "PLAYER_ENTERING_WORLD" then
			ResetRuneforges()
			UpdateRuneforge(16)
			UpdateRuneforge(17)
		elseif evt == "PLAYER_EQUIPMENT_CHANGED" then
			local slot = ...
			if slot == 16 or slot == 17 then
				ResetRuneforges()
				UpdateRuneforge(16)
				UpdateRuneforge(17)
			end
		end
	end)
end

Hekili:RegisterGearHook(ResetRuneforges, UpdateRuneforge)

-- Abilities
spec:RegisterAbilities({
	-- Talent: Surrounds you in an Anti-Magic Shell for $d, absorbing up to $<shield> magic ...
	antimagic_shell = {
		id = 48707,
		cast = 0,
		cooldown = 45,
		gcd = "off",

		startsCombat = false,

		toggle = function()
			if settings.dps_shell then
				return
			end
			return "defensives"
		end,

		handler = function()
			applyBuff("antimagic_shell")
		end,
	},

	-- Talent: Places an Anti-Magic Zone that reduces spell damage taken by party or raid me...
	antimagic_zone = {
		id = 51052,
		cast = 0,
		cooldown = 120,
		gcd = "spell",

		talent = "antimagic_zone",
		startsCombat = false,

		toggle = "cooldowns",

		handler = function()
			applyBuff("antimagic_zone")
		end,
	},

	-- Talent: Summons a legion of ghouls who swarms your enemies, fighting anything they ca...
	army_of_the_dead = {
		id = 42650,
		cast = 4,
		cooldown = 600,
		gcd = "spell",

		spend_runes = { 1, 1, 1 }, -- 1 Blood, 1 Frost, 1 Unholy

		startsCombat = false,
		texture = 237511,

		toggle = "cooldowns",

		handler = function()
			applyBuff("army_of_the_dead", 4)
			summonPet("army_ghoul", 40)
		end,
	},

	-- Talent: Lifts the enemy target off the ground, crushing their throat with dark energy...
	asphyxiate = {
		id = 108194,
		cast = 0,
		cooldown = 30,
		gcd = "spell",

		talent = "asphyxiate",
		startsCombat = true,

		toggle = "interrupts",

		debuff = "casting",
		readyTime = state.timeToInterrupt,

		handler = function()
			applyDebuff("target", "asphyxiate")
		end,
	},

	-- Talent: Convert Blood Charges to Death Runes.
	blood_tap = {
		id = 45529,
		cast = 0,
		cooldown = 1,
		gcd = "off",

		talent = "blood_tap",
		startsCombat = false,

		usable = function()
			return buff.blood_charge.stack >= 5
		end,

		handler = function()
			removeBuff("blood_charge", 5)
			--TODO: Add in generation of Death rune
		end,
	},

	-- Empower Rune Weapon: Instantly activates all your runes and grants RP
	empower_rune_weapon = {
		id = 47568,
		cast = 0,
		cooldown = 300,
		gcd = "off",

		startsCombat = false,
		toggle = "cooldowns",

		handler = function()
			gain(6, "runes")
			gain(25, "runic_power")
		end,
	},

	-- Festering Strike: A vicious strike that deals weapon damage and infects the target with a disease
	festering_strike = {
		id = 85948,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		spend_runes = { 1, 1, 0 }, -- 1 Blood, 1 Frost, 0 Unholy

		startsCombat = true,

		--handler = function()
		--TODO: Increase the duration of Blood Plague, Frost Fever, and Chains of Ice effects on the target by up to 6 seconds
		--end,
	},

	-- Outbreak: Instantly applies both Frost Fever and Blood Plague to the target
	outbreak = {
		id = 77575,
		cast = 0,
		cooldown = function()
			return spec.blood and 30 or 60
		end,
		gcd = "spell",

		startsCombat = true,

		handler = function()
			applyDebuff("target", "frost_fever")
			applyDebuff("target", "blood_plague")
		end,
	},

	-- Pestilence: Spreads diseases from the target to nearby enemies
	pestilence = {
		id = 50842,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		spend_runes = { 1, 0, 0 }, -- 1 Blood, 0 Frost, 0 Unholy

		startsCombat = true,

		usable = function()
			return debuff.frost_fever.up or debuff.blood_plague.up
		end,

		handler = function()
			gain(10, "runic_power")
			if debuff.frost_fever.up then
				active_dot.frost_fever = min(active_enemies, active_dot.frost_fever + active_enemies - 1)
			end
			if debuff.blood_plague.up then
				active_dot.blood_plague = min(active_enemies, active_dot.blood_plague + active_enemies - 1)
			end
		end,
	},

	-- Talent: Extract diseases from target to gain runes (MoP 5.5.0)
	plague_leech = {
		id = 123693,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		talent = "plague_leech",
		startsCombat = true,

		usable = function()
			-- MoP: Can only use when diseases are about to expire or for rune generation
			local deficit = state.rune_deficit
			return (debuff.frost_fever.up and debuff.frost_fever.remains < 3)
				or (debuff.blood_plague.up and debuff.blood_plague.remains < 3)
				or (deficit >= 2 and (debuff.frost_fever.up or debuff.blood_plague.up))
		end,

		handler = function()
			local runes_gained = 0
			if debuff.frost_fever.up then
				removeDebuff("target", "frost_fever")
				runes_gained = runes_gained + 1
			end
			if debuff.blood_plague.up then
				removeDebuff("target", "blood_plague")
				runes_gained = runes_gained + 1
			end
			gain(runes_gained, "runes")
		end,
	},

	-- Talent: Surrounds the caster with unholy energy that damages nearby enemies
	unholy_blight = {
		id = 115989,
		cast = 0,
		cooldown = 90,
		gcd = "spell",

		talent = "unholy_blight",
		startsCombat = true,

		handler = function()
			applyBuff("unholy_blight")
			applyDebuff("target", "frost_fever")
			applyDebuff("target", "blood_plague")
		end,
	},

	-- Talent: Shackles the target $?a373930[and $373930s1 nearby enemy ][]with frozen chain...
	chains_of_ice = {
		id = 45524,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		spend_runes = { 0, 1, 0 }, -- 0 Blood, 1 Frost, 0 Unholy

		startsCombat = true,

		handler = function()
			applyDebuff("target", "chains_of_ice")
		end,
	},

	-- Command the target to attack you.
	dark_command = {
		id = 56222,
		cast = 0,
		cooldown = 8,
		gcd = "off",

		startsCombat = true,

		handler = function()
			applyDebuff("target", "dark_command")
		end,
	},

	dark_simulacrum = {
		id = 77606,
		cast = 0,
		cooldown = 60,
		gcd = "off",

		spend = 20,
		spendType = "runic_power",

		pvptalent = "dark_simulacrum",
		startsCombat = false,
		texture = 135888,

		usable = function()
			if not target.is_player then
				return false, "target is not a player"
			end
			return true
		end,
		handler = function()
			applyDebuff("target", "dark_simulacrum")
		end,
	},

	-- Pet transformed into an undead monstrosity. Damage dealt increased by 100%.
	dark_transformation = {
		id = 63560,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		spend_runes = { 0, 0, 1 },

		startsCombat = false,
		texture = 342913,
		usable = function()
			if pet.ghoul.down then
				return false, "requires a living ghoul"
			end
			if buff.shadow_infusion.stacks < 5 then
				return false, "requires five stacks of shadow_infusion"
			end
			return true
		end,

		handler = function()
			applyBuff("dark_transformation")
			removeBuff("shadow_infusion")
		end,
	},

	-- Corrupts the targeted ground, causing ${$52212m1*11} Shadow damage over $d to...
	death_and_decay = {
		id = 43265,
		cast = 0,
		cooldown = 30,
		gcd = "spell",

		spend_runes = { 0, 0, 1 }, -- 0 Blood, 0 Frost, 1 Unholy

		startsCombat = true,
		notalent = "defile",

		usable = function()
			return (settings.dnd_while_moving or not moving), "cannot cast while moving"
		end,

		handler = function()
			applyBuff("death_and_decay")
			applyDebuff("target", "death_and_decay")
		end,

		bind = { "defile", "any_dnd" },

		copy = "any_dnd",
	},

	-- Fires a blast of unholy energy at the target$?a377580[ and $377580s2 addition...
	death_coil = {
		id = 47541,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		spend = function()
			return buff.sudden_doom.up and 0 or 32
		end,
		spendType = "runic_power",

		startsCombat = true,
		texture = 136145,

		handler = function()
			removeBuff("sudden_doom")
		end,
	},

	-- Opens a gate which you can use to return to Ebon Hold.    Using a Death Gate ...
	death_gate = {
		id = 50977,
		cast = 4,
		cooldown = 60,
		gcd = "spell",

		spend_runes = { 0, 0, 1 }, -- 0 Blood, 0 Frost, 1 Unholy

		startsCombat = false,

		handler = function() end,
	},

	-- Harnesses the energy that surrounds and binds all matter, drawing the target ...
	death_grip = {
		id = 49576,
		cast = 0,
		cooldown = 35,
		gcd = "off",
		icd = 0.5,

		startsCombat = true,

		handler = function()
			applyDebuff("target", "death_grip")
			setDistance(5)
		end,
	},

	-- Talent: Create a death pact that heals you for $s1% of your maximum health, but absor...
	death_pact = {
		id = 48743,
		cast = 0,
		cooldown = 120,
		gcd = "off",

		talent = "death_pact",
		startsCombat = false,

		toggle = "defensives",

		usable = function()
			return pet.alive, "requires an undead pet"
		end,

		handler = function()
			gain(health.max * 0.25, "health")
		end,
	},

	-- Talent: Focuses dark power into a strike$?s137006[ with both weapons, that deals a to...
	death_strike = {
		id = 49998,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		spend_runes = function()
			if buff.dark_succor.up then
				return { 0, 0, 0 }
			end
			return { 0, 1, 1 }
		end,

		startsCombat = true,

		handler = function()
			removeBuff("dark_succor")
		end,
	},

	-- For $d, your movement speed is increased by $s1%, you cannot be slowed below ...
	deaths_advance = {
		id = 96268,
		cast = 0,
		cooldown = 120,
		gcd = "off",

		talent = "deaths_advance",
		startsCombat = false,

		handler = function()
			applyBuff("deaths_advance")
		end,
	},

	-- Defile the targeted ground, dealing Shadow damage over time. While you remain within your Defile, your Scourge Strike will hit multiple enemies near the target. If any enemies are standing in the Defile, it grows in size and deals increasing damage every sec.
	defile = {
		id = 43265, -- In MoP, Defile uses the same spell ID as Death and Decay
		cast = 0,
		cooldown = 30,
		gcd = "spell",

		spend_runes = { 1, 0, 0 }, -- 1 Blood, 0 Frost, 0 Unholy

		talent = "defile",
		startsCombat = true,

		usable = function()
			return (settings.dnd_while_moving or not moving), "cannot cast while moving"
		end,

		handler = function()
			applyDebuff("target", "defile")
			applyBuff("death_and_decay")
			applyDebuff("target", "death_and_decay")
		end,

		bind = { "death_and_decay", "any_dnd" },
	},

	-- Strike an enemy for Frost damage and infect them with Frost Fever
	icy_touch = {
		id = 45477,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		spend_runes = { 0, 1, 0 }, -- 0 Blood, 1 Frost, 0 Unholy

		startsCombat = true,

		handler = function()
			applyDebuff("target", "frost_fever")
		end,
	},

	-- Talent: Your blood freezes, granting immunity to Stun effects and reducing all damage...
	icebound_fortitude = {
		id = 48792,
		cast = 0,
		cooldown = 180,
		gcd = "off",

		talent = "icebound_fortitude",
		startsCombat = false,

		toggle = "defensives",

		handler = function()
			applyBuff("icebound_fortitude")
		end,
	},

	-- Draw upon unholy energy to become Undead for $d, increasing Leech by $s1%$?a3...
	lichborne = {
		id = 49039,
		cast = 0,
		cooldown = 120,
		gcd = "off",

		talent = "lichborne",
		startsCombat = false,

		toggle = "defensives",

		handler = function()
			applyBuff("lichborne")
		end,
	},

	-- Talent: Smash the target's mind with cold, interrupting spellcasting and preventing a...
	mind_freeze = {
		id = 47528,
		cast = 0,
		cooldown = 15,
		gcd = "off",

		startsCombat = true,

		toggle = "interrupts",

		debuff = "casting",
		readyTime = state.timeToInterrupt,

		handler = function()
			interrupt()
		end,
	},

	-- A necrotic strike that deals weapon damage and applies a Necrotic Strike shield
	necrotic_strike = {
		id = 73975,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		spend_runes = { 0, 0, 1 }, -- 0 Blood, 0 Frost, 1 Unholy

		talent = "necrotic_strike",
		startsCombat = true,

		handler = function()
			applyDebuff("target", "necrotic_strike")
		end,
	},

	-- Activates a freezing aura for $d that creates ice beneath your feet, allowing...
	path_of_frost = {
		id = 3714,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		spend_runes = { 0, 1, 0 }, -- 0 Blood, 1 Frost, 0 Unholy

		startsCombat = false,

		handler = function()
			applyBuff("path_of_frost")
		end,
	},

	-- Strike an enemy for Unholy damage and infect them with Blood Plague
	plague_strike = {
		id = 45462,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		spend_runes = { 0, 0, 1 }, -- 0 Blood, 0 Frost, 1 Unholy

		startsCombat = true,

		handler = function()
			applyDebuff("target", "blood_plague")
		end,
	},

	-- An unholy pact that prevents fatal damage, instead absorbing incoming healing
	purgatory = {
		id = 114556,
		cast = 0,
		cooldown = 240,
		gcd = "off",

		talent = "purgatory",
		startsCombat = false,

		toggle = "defensives",

		handler = function()
			applyBuff("purgatory")
		end,
	},

	raise_ally = {
		id = 61999,
		cast = 0,
		cooldown = 600,
		gcd = "spell",

		spend = 30,
		spendType = "runic_power",

		startsCombat = false,
		texture = 136143,

		toggle = "cooldowns",

		handler = function() end,
	},

	-- Talent: Raises $?s207313[an abomination]?s58640[a geist][a ghoul] to fight by your si...
	raise_dead = {
		id = 46584,
		cast = 0,
		cooldown = 120,
		gcd = "spell",

		startsCombat = false,

		essential = true,
		nomounted = true,

		usable = function()
			return not pet.alive
		end,
		handler = function()
			summonPet("ghoul", 3600)
		end,
	},

	-- An unholy strike that deals Physical damage and Shadow damage
	scourge_strike = {
		id = 55090,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		spend_runes = { 0, 0, 1 }, -- 0 Blood, 0 Frost, 1 Unholy

		startsCombat = true,
	},

	-- Talent: Strike an enemy for Shadow damage and mark their soul. After 5 sec, if they are below 45% health, the mark will detonate for massive Shadow damage.
	soul_reaper = {
		id = 130736,
		cast = 0,
		cooldown = 6,
		gcd = "spell",

		spend_runes = { 0, 0, 1 }, -- 0 Blood, 0 Frost, 1 Unholy

		startsCombat = true,
        gain = 10,
        gainType = "runic_power",
        texture = 636333,

		handler = function()
			applyDebuff("target", "soul_reaper")
		end,
	},

	strangulate = {
		id = 47476,
		cast = 0,
		cooldown = 120,
		gcd = "off",

		startsCombat = false,
		texture = 136214,

		toggle = "interrupts",

		debuff = "casting",
		readyTime = state.timeToInterrupt,

		handler = function()
			interrupt()
			applyDebuff("target", "strangulate")
		end,
	},

	-- Blood Boil: Deals damage to nearby enemies and spreads diseases
	blood_boil = {
		id = 48721,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		spend_runes = { 1, 0, 0 }, -- 1 Blood, 0 Frost, 0 Unholy

		startsCombat = true,
		usable = function()
			local bloodReady = (state.blood or 0) > 0 or (state.death or 0) > 0
			if not bloodReady then
				return false, "no blood/death rune"
			end
			local enemies = active_enemies or state.active_enemies or 1
			if enemies > 1 then
				if
					(debuff.frost_fever.up and active_dot.frost_fever < enemies)
					or (debuff.blood_plague.up and active_dot.blood_plague < enemies)
				then
					return true
				end
				if enemies >= 2 and debuff.frost_fever.up and debuff.blood_plague.up then
					return true
				end
			end
			return false, "single target / no spread need"
		end,

		handler = function()
			-- Blood Boil base functionality for MoP
			-- Spreads diseases to nearby enemies
			if debuff.frost_fever.up then
				active_dot.frost_fever = min(active_enemies, active_dot.frost_fever + active_enemies - 1)
			end
			if debuff.blood_plague.up then
				active_dot.blood_plague = min(active_enemies, active_dot.blood_plague + active_enemies - 1)
			end
		end,
	},

	-- Horn of Winter: Increases Attack Power and generates 10 runic power
	horn_of_winter = {
		id = 57330,
		cast = 0,
		cooldown = 20,
		gcd = "spell",

		startsCombat = false,

		handler = function()
			applyBuff("horn_of_winter")
			gain(10, "runic_power") -- MoP gives 10 RP on cast
		end,
	},

	-- Racials and Engineering (for APL alignment)
	blood_fury = {
		id = 33697,
		cast = 0,
		cooldown = 120,
		gcd = "off",

		startsCombat = false,
		toggle = "cooldowns",

		handler = function()
			applyBuff("blood_fury")
		end,
	},

	berserking = {
		id = 26297,
		cast = 0,
		cooldown = 180,
		gcd = "off",

		startsCombat = false,
		toggle = "cooldowns",

		handler = function()
			applyBuff("berserking")
		end,
	},

	synapse_springs = {
		id = 126734,
		cast = 0,
		cooldown = 60,
		gcd = "off",

		startsCombat = false,
		toggle = "cooldowns",

		handler = function()
			applyBuff("synapse_springs")
		end,
	},

	-- Unholy Frenzy: Increases Attack Speed
	unholy_frenzy = {
		id = 49016,
		cast = 0,
		cooldown = 180,
		gcd = "off",

		startsCombat = false,

		handler = function()
			applyBuff("unholy_frenzy")
			buff.unholy_frenzy.on_self = UnitIsUnit("target", "player") and true or false
		end,
	},

	-- Talent: Summon a Gargoyle into the area to bombard the target for $61777d.    The Gar...
	summon_gargoyle = {
		id = 49206,
		cast = 0,
		cooldown = 180,
		gcd = "off",

		startsCombat = true,

		toggle = "cooldowns",

		handler = function()
			summonPet("gargoyle", 30)
		end,
	},
	-- Presence switching
	blood_presence = {
		id = 48263,
		cast = 0,
		cooldown = 1,
		gcd = "off",

		startsCombat = false,

		handler = function()
			if buff.frost_presence.up then
				removeBuff("frost_presence")
			end
			if buff.unholy_presence.up then
				removeBuff("unholy_presence")
			end
			applyBuff("blood_presence")
		end,
	},

	frost_presence = {
		id = 48266,
		cast = 0,
		cooldown = 1,
		gcd = "off",

		startsCombat = false,

		handler = function()
			if buff.blood_presence.up then
				removeBuff("blood_presence")
			end
			if buff.unholy_presence.up then
				removeBuff("unholy_presence")
			end
			applyBuff("frost_presence")
		end,
	},

	unholy_presence = {
		id = 48265,
		cast = 0,
		cooldown = 1,
		gcd = "off",

		startsCombat = false,

		handler = function()
			if buff.frost_presence.up then
				removeBuff("frost_presence")
			end
			if buff.blood_presence.up then
				removeBuff("blood_presence")
			end
			applyBuff("unholy_presence")
		end,
	},

	-- Stub.
	any_dnd = {
		name = function()
			return "|T136144:0|t |cff00ccff[Any "
				.. (class.abilities.death_and_decay and class.abilities.death_and_decay.name or "Death and Decay")
				.. "]|r"
		end,
		cast = 0,
		cooldown = 0,
		copy = "any_dnd_stub",
	},

	control_undead = {
		id = 111673,
		cast = 0,
		cooldown = 0,
		gcd = "spell",

		startsCombat = true,

		handler = function()
			-- Control Undead: Take control of an undead target
		end,
	},
})

-- MoP-specific rune state expressions
-- Note: Do not register state expressions named blood_runes/frost_runes/unholy_runes/death_runes,
-- since they would shadow the resource/state tables registered above and break rune queries.

-- Legacy rune type expressions for SimC compatibility
-- Death Rune tracking system for MoP (based on Cataclysm implementation)
local death_rune_tracker = { 0, 0, 0, 0, 0, 0 }
spec:RegisterStateExpr("get_death_rune_tracker", function()
	return death_rune_tracker
end)

spec:RegisterStateTable(
	"death_runes",
	setmetatable({
		state = {},

		reset = function()
			for i = 1, 6 do
				local start, duration, ready = GetRuneCooldown(i)
				local type = GetRuneType(i)
				local expiry = ready and 0 or start + duration
				state.death_runes.state[i] = {
					type = type,
					start = start,
					duration = duration,
					ready = ready,
					expiry = expiry,
				}
			end
		end,

		spend = function(neededRunes)
			local usedRunes, err = state.death_runes.getRunesForRequirement(neededRunes)
			if not usedRunes then
				return
			end

			local runeMapping = {
				blood = { 1, 2 },
				frost = { 5, 6 },
				unholy = { 3, 4 },
			}

			for _, runeIndex in ipairs(usedRunes) do
				local rune = state.death_runes.state[runeIndex]
				rune.ready = false

				-- Determine other rune in the group
				local otherRuneIndex
				for type, runes in pairs(runeMapping) do
					if runes[1] == runeIndex then
						otherRuneIndex = runes[2]
						break
					elseif runes[2] == runeIndex then
						otherRuneIndex = runes[1]
						break
					end
				end

				local otherRune = state.death_runes.state[otherRuneIndex]
				local expiryTime = (otherRune.expiry > 0 and otherRune.expiry or state.query_time) + rune.duration
				rune.expiry = expiryTime
			end
		end,

		getActiveDeathRunes = function()
			local activeRunes = {}
			local state_array = state.death_runes.state
			for i = 1, #state_array do
				if state_array[i].type == 4 and state_array[i].expiry < state.query_time then
					table.insert(activeRunes, i)
				end
			end
			return activeRunes
		end,

		getLeftmostActiveDeathRune = function()
			local activeRunes = state.death_runes.getActiveDeathRunes()
			return #activeRunes > 0 and activeRunes[1] or nil
		end,

		getActiveRunes = function()
			local activeRunes = {}
			local state_array = state.death_runes.state
			for i = 1, #state_array do
				if state_array[i].expiry < state.query_time then
					table.insert(activeRunes, i)
				end
			end
			return activeRunes
		end,

		getRunesForRequirement = function(neededRunes)
			local bloodNeeded, frostNeeded, unholyNeeded = unpack(neededRunes)
			-- for rune mapping, see the following in game...
			-- /run for i=1,6 do local t=GetRuneType(i); local s,d,r=GetRuneCooldown(i); print(i,"type",t,"ready",r) end
			local runeMapping = {
				blood = { 1, 2 },
				frost = { 5, 6 },
				unholy = { 3, 4 }, --
				any = { 1, 2, 3, 4, 5, 6 },
			}

			local activeRunes = state.death_runes.getActiveRunes()
			local usedRunes = {}
			local usedDeathRunes = {}

			local function useRunes(runetype, needed)
				local runes = runeMapping[runetype]
				for _, runeIndex in ipairs(runes) do
					if needed == 0 then
						break
					end
					if
						state.death_runes.state[runeIndex].expiry < state.query_time
						and state.death_runes.state[runeIndex].type ~= 4
					then
						table.insert(usedRunes, runeIndex)
						needed = needed - 1
					end
				end
				return needed
			end

			-- Use specific runes first
			bloodNeeded = useRunes("blood", bloodNeeded)
			frostNeeded = useRunes("frost", frostNeeded)
			unholyNeeded = useRunes("unholy", unholyNeeded)

			-- Use death runes if needed
			for _, runeIndex in ipairs(activeRunes) do
				if bloodNeeded == 0 and frostNeeded == 0 and unholyNeeded == 0 then
					break
				end
				if state.death_runes.state[runeIndex].type == 4 and not usedDeathRunes[runeIndex] then
					if bloodNeeded > 0 then
						table.insert(usedRunes, runeIndex)
						bloodNeeded = bloodNeeded - 1
					elseif frostNeeded > 0 then
						table.insert(usedRunes, runeIndex)
						frostNeeded = frostNeeded - 1
					elseif unholyNeeded > 0 then
						table.insert(usedRunes, runeIndex)
						unholyNeeded = unholyNeeded - 1
					end
					usedDeathRunes[runeIndex] = true
				end
			end

			return usedRunes
		end,
	}, {
		__index = function(t, k)
			local countDeathRunes = function()
				local state_array = t.state
				local count = 0
				for i = 1, #state_array do
					if state_array[i].type == 4 and state_array[i].expiry < state.query_time then
						count = count + 1
					end
				end
				return count
			end
			local runeMapping = {
				blood = { 1, 2 },
				frost = { 5, 6 },
				unholy = { 3, 4 },
				any = { 1, 2, 3, 4, 5, 6 },
			}
			-- Function to access the mappings
			local function getRuneSet(runeType)
				return runeMapping[runeType]
			end

			local countDRForType = function(type)
				local state_array = t.state
				local count = 0
				local runes = getRuneSet(type)
				if runes then
					for _, rune in ipairs(runes) do
						if state_array[rune].type == 4 and state_array[rune].expiry < state.query_time then
							count = count + 1
						end
					end
				end
				return count
			end

			if k == "state" then
				return t.state
			elseif k == "actual" then
				return countDRForType("any")
			elseif k == "current" then
				return countDRForType("any")
			elseif k == "current_frost" then
				return countDRForType("frost")
			elseif k == "current_blood" then
				return countDRForType("blood")
			elseif k == "current_unholy" then
				return countDRForType("unholy")
			elseif k == "current_non_frost" then
				return countDRForType("blood") + countDRForType("unholy")
			elseif k == "current_non_blood" then
				return countDRForType("frost") + countDRForType("unholy")
			elseif k == "current_non_unholy" then
				return countDRForType("blood") + countDRForType("frost")
			elseif k == "cooldown" then
				return t.state[1].duration
			elseif k == "active_death_runes" then
				return t.getActiveDeathRunes()
			elseif k == "leftmost_active_death_rune" then
				return t.getLeftmostActiveDeathRune()
			elseif k == "active_runes" then
				return t.getActiveRunes()
			elseif k == "runes_for_requirement" then
				return t.getRunesForRequirement
			end
		end,
	})
)

-- Legacy rune type expressions for SimC compatibility
spec:RegisterStateExpr("blood", function()
	if GetRuneCooldown then
		local count = 0
		for i = 1, 2 do
			local start, duration, ready = GetRuneCooldown(i)
			if ready then
				count = count + 1
			end
		end
		return count
	else
		return 2 -- Fallback for emulation
	end
end)
spec:RegisterStateExpr("pure_blood", function()
	if GetRuneCooldown then
		local count = 0
		for i = 1, 2 do
			local start, duration, ready = GetRuneCooldown(i)
			local type = GetRuneType(i)
			if ready and type ~= 4 then
				count = count + 1
			end
		end
		return count
	else
		return 2 -- Fallback for emulation
	end
end)
spec:RegisterStateExpr("frost", function()
	if GetRuneCooldown then
		local count = 0
		for i = 5, 6 do
			local start, duration, ready = GetRuneCooldown(i)
			if ready then
				count = count + 1
			end
		end
		return count
	else
		return 2 -- Fallback for emulation
	end
end)
spec:RegisterStateExpr("pure_frost", function()
	if GetRuneCooldown then
		local count = 0
		for i = 5, 6 do
			local start, duration, ready = GetRuneCooldown(i)
			local type = GetRuneType(i)
			if ready and type ~= 4 then
				count = count + 1
			end
		end
		return count
	else
		return 2 -- Fallback for emulation
	end
end)
spec:RegisterStateExpr("unholy", function()
	if GetRuneCooldown then
		local count = 0
		for i = 3, 4 do
			local start, duration, ready = GetRuneCooldown(i)
			if ready then
				count = count + 1
			end
		end
		return count
	else
		return 2 -- Fallback for emulation
	end
end)
spec:RegisterStateExpr("death", function()
	if GetRuneCooldown and GetRuneType then
		local count = 0
		for i = 1, 6 do
			local start, duration, ready = GetRuneCooldown(i)
			local type = GetRuneType(i)
			if ready and type == 4 then
				count = count + 1
			end
		end
		return count
	else
		return 0 -- Fallback for emulation
	end
end)

-- MoP rune regeneration tracking
spec:RegisterStateExpr("rune_regeneration", function()
	local total = 0
	if state.blood_runes and state.blood_runes.current then
		total = total + state.blood_runes.current
	end
	if state.frost_runes and state.frost_runes.current then
		total = total + state.frost_runes.current
	end
	if state.unholy_runes and state.unholy_runes.current then
		total = total + state.unholy_runes.current
	end
	if state.death_runes and state.death_runes.count then
		total = total + state.death_runes.count
	end
	return 6 - total
end)

-- MoP Death Rune conversion tracking
spec:RegisterStateExpr("death_rune_conversion_available", function()
	local br = state.blood_runes and state.blood_runes.current or 0
	local fr = state.frost_runes and state.frost_runes.current or 0
	local ur = state.unholy_runes and state.unholy_runes.current or 0
	return br > 0 or fr > 0 or ur > 0
end)

-- Rune state expressions for MoP 5.5.0
spec:RegisterStateExpr("rune", function()
	local total = 0
	for i = 1, 6 do
		local start, duration, ready = GetRuneCooldown(i)
		if ready then
			total = total + 1
		end
	end
	return total
end)

spec:RegisterStateExpr("rune_deficit", function()
	local total = 0
	if state.blood_runes and state.blood_runes.current then
		total = total + state.blood_runes.current
	end
	if state.frost_runes and state.frost_runes.current then
		total = total + state.frost_runes.current
	end
	if state.unholy_runes and state.unholy_runes.current then
		total = total + state.unholy_runes.current
	end
	if state.death_runes and state.death_runes.count then
		total = total + state.death_runes.count
	end
	return 6 - total
end)

spec:RegisterStateExpr("rune_current", function()
	if state.runes and state.runes.ready then
		return state.runes.ready
	end
	local total = 0
	for i = 1, 6 do
		local _, _, ready = GetRuneCooldown(i)
		if ready then
			total = total + 1
		end
	end
	return total
end)

-- Alias for APL compatibility
spec:RegisterStateExpr("runes_current", function()
	return state.rune_current
end)

spec:RegisterStateExpr("rune_max", function()
	return 6
end)

-- MoP Unholy DK-specific state expressions
spec:RegisterStateExpr("sudden_doom_proc", function()
	return buff.sudden_doom.up
end)

spec:RegisterStateExpr("dark_transformation_available", function()
	return pet.alive and cooldown.dark_transformation.ready
end)

spec:RegisterStateExpr("ghoul_empowered", function()
	return buff.dark_transformation.up or buff.dark_empowerment.up
end)

spec:RegisterStateExpr("diseases_maintained", function()
	return (debuff.frost_fever.up and debuff.frost_fever.remains > 3)
		and (debuff.blood_plague.up and debuff.blood_plague.remains > 3)
end)

spec:RegisterStateExpr("diseases_refresh_needed", function()
	return (debuff.frost_fever.up and debuff.frost_fever.remains < 3)
		or (debuff.blood_plague.up and debuff.blood_plague.remains < 3)
end)

-- MoP Unholy rotation tracking
spec:RegisterStateExpr("unholy_rotation_phase", function()
	if not state.diseases_maintained then
		return "disease_setup"
	end
	if state.sudden_doom_proc then
		return "sudden_doom"
	end
	if (state.runic_power and state.runic_power.current or 0) >= 80 then
		return "runic_power_spend"
	end
	return "standard_rotation"
end)

-- Expose disease state for APL compatibility
spec:RegisterStateExpr("disease", function()
	local frost_fever_remains = debuff.frost_fever.remains or 0
	local blood_plague_remains = debuff.blood_plague.remains or 0

	local min_remains = 0
	if frost_fever_remains > 0 and blood_plague_remains > 0 then
		min_remains = math.min(frost_fever_remains, blood_plague_remains)
	elseif frost_fever_remains > 0 then
		min_remains = frost_fever_remains
	elseif blood_plague_remains > 0 then
		min_remains = blood_plague_remains
	end

	return {
		min_remains = min_remains,
	}
end)

-- Expose disease min remains (legacy)
spec:RegisterStateExpr("disease_min_remains", function()
	local frost_fever_remains = debuff.frost_fever.remains or 0
	local blood_plague_remains = debuff.blood_plague.remains or 0

	if frost_fever_remains > 0 and blood_plague_remains > 0 then
		return math.min(frost_fever_remains, blood_plague_remains)
	elseif frost_fever_remains > 0 then
		return frost_fever_remains
	elseif blood_plague_remains > 0 then
		return blood_plague_remains
	else
		return 0
	end
end)

spec:RegisterRanges("festering_strike", "mind_freeze", "death_coil")

spec:RegisterSetting("dnd_while_moving", true, {
	name = strformat("Allow %s while moving", Hekili:GetSpellLinkWithTexture(43265)),
	desc = strformat(
		"If checked, then allow recommending %s while the player is moving otherwise only recommend it if the player is standing still.",
		Hekili:GetSpellLinkWithTexture(43265)
	),
	type = "toggle",
	width = "full",
})

spec:RegisterSetting("dps_shell", false, {
	name = strformat("Use %s Offensively", Hekili:GetSpellLinkWithTexture(48707)),
	desc = strformat(
		"If checked, %s will not be on the Defensives toggle by default.",
		Hekili:GetSpellLinkWithTexture(48707)
	),
	type = "toggle",
	width = "full",
})

spec:RegisterSetting("pl_macro", nil, {
	name = function()
		local plague_strike = spec.abilities and spec.abilities.plague_strike
		return plague_strike and strformat("%s Macro", Hekili:GetSpellLinkWithTexture(plague_strike.id))
			or "Plague Strike Macro"
	end,
	desc = function()
		local plague_strike = spec.abilities and spec.abilities.plague_strike
		local blood_plague = spec.auras and spec.auras.blood_plague
		if plague_strike and blood_plague then
			return strformat(
				"Using a mouseover macro makes it easier to apply %s and %s to other enemies without retargeting.",
				Hekili:GetSpellLinkWithTexture(plague_strike.id),
				Hekili:GetSpellLinkWithTexture(blood_plague.id)
			)
		else
			return "Using a mouseover macro makes it easier to apply Plague Strike and Blood Plague to other enemies without retargeting."
		end
	end,
	type = "input",
	width = "full",
	multiline = true,
	get = function()
		local plague_strike = class.abilities and class.abilities.plague_strike
		return plague_strike and "#showtooltip\n/use [@mouseover,harm,nodead][] " .. plague_strike.name
			or "#showtooltip\n/use [@mouseover,harm,nodead][] Plague Strike"
	end,
	set = function() end,
})

spec:RegisterSetting("it_macro", nil, {
	name = strformat("%s Macro", Hekili:GetSpellLinkWithTexture(45477)),
	desc = strformat(
		"Using a mouseover macro makes it easier to apply %s and %s to other enemies without retargeting.",
		Hekili:GetSpellLinkWithTexture(45477),
		Hekili:GetSpellLinkWithTexture(59921)
	),
	type = "input",
	width = "full",
	multiline = true,
	get = function()
		return "#showtooltip\n/use [@mouseover,harm,nodead][] Icy Touch"
	end,
	set = function() end,
})

-- MoP Unholy DK-specific settings
spec:RegisterSetting("plague_leech_priority", "expire", {
	name = "Plague Leech Priority",
	desc = "When to use Plague Leech: 'expire' = only when diseases are about to expire, 'rune_generation' = for rune generation when needed",
	type = "select",
	values = {
		expire = "Disease Expiration Only",
		rune_generation = "Rune Generation Priority",
	},
	width = "full",
})

spec:RegisterSetting("sudden_doom_priority", true, {
	name = "Sudden Doom Priority",
	desc = "Prioritize Sudden Doom procs over other abilities",
	type = "toggle",
	width = "full",
})

spec:RegisterSetting("dark_transformation_auto", true, {
	name = "Auto Dark Transformation",
	desc = "Automatically use Dark Transformation when available",
	type = "toggle",
	width = "full",
})

-- Unholy Death Knight specific settings
spec:RegisterSetting( "unholy_runic_power_threshold", 50, {
	name = "Runic Power Threshold for Death Coil",
	desc = "Minimum runic power to spend on Death Coil",
	type = "range",
	min = 20,
	max = 100,
	step = 5,
	width = "full",
} )

spec:RegisterSetting( "unholy_shadow_infusion_priority", true, {
	name = "Prioritize Shadow Infusion",
	desc = "Always use Shadow Infusion procs to empower ghoul",
	type = "toggle",
	width = "full",
} )

spec:RegisterSetting( "unholy_sudden_doom_priority", true, {
	name = "Prioritize Sudden Doom",
	desc = "Always use Sudden Doom procs for free Death Coil",
	type = "toggle",
	width = "full",
} )

spec:RegisterSetting( "unholy_execute_threshold", 35, {
	name = "Execute Threshold for Soul Reaper",
	desc = "Target health percentage to use Soul Reaper",
	type = "range",
	min = 20,
	max = 50,
	step = 5,
	width = "full",
} )

spec:RegisterSetting( "unholy_dnd_while_moving", false, {
	name = "Use Death and Decay While Moving",
	desc = "Cast Death and Decay even when moving",
	type = "toggle",
	width = "full",
} )

spec:RegisterSetting( "unholy_blood_tap_threshold", 5, {
	name = "Blood Tap Threshold",
	desc = "Minimum Blood Charge stacks to use Blood Tap",
	type = "range",
	min = 1,
	max = 10,
	step = 1,
	width = "full",
} )

spec:RegisterOptions( {
	enabled = true,
	aoe = 4,
	cycle = false,
	nameplates = true,
	nameplateRange = 8,
	rangeFilter = false,
	damage = true,
	damageExpiration = 6,
	potion = "mogu_power_potion",
	package = "Unholy",
} )

spec:RegisterPack(	"Unholy",	20251012,	[[Hekili:LR1wVnUTs4FllkGXM2av5RDZbr5H2IItZbDXc4TVAzAjABbllkOljRxyOF7DiPUqsrkj74w02Zd7gBkYVzi5mFZf5vJx95vl9rz4vFCI9K5JThpXAY0XlMmD1YStX4vlJrEhq7GpeHoc))VhTNeEIo8PqcYNU8usEIh8Ovl3KheM9RrR2OdZPtNaZng7bdph(4(aFFmFU4uVvl)8(G0I10)Hkwxk1I1KTW39YcirfRddsZGhVLKuS()IpeegyTAjBqMAeeTle7MHs2HZGb(iBVHJqBcX(R(Xvl9scYWjbiyNHcXrzwXHOD5y3qm2BVv5elwpQy9liyAW3S8jzPU5XSbFFXA4RwBcjeFx(sTsWhrbrGo9yX6PfRpFMpNTjK0m3T4xWjQt5UkSsYJWPw5SJtlpsEuwXANI12Cu4p1hJY2l)W7wTKFCaxbcQ)Qm4uTV9lxyUBcd2Tpt)gocJ9DP76gXiTkQCMAuoDbdjpBtcgDGIWmJi8onhYzbEhG72wB80SKGdykEZ7dpXlKwWf4DYnJKZpex0)Md)fSxEgMDWP7w8j4IQbDW7i0f24X4ek()qFxs8TEgkw6cAt(2TLpYBp1c3kndCraHbMfJfex9YPc7dgfMw9McvL5jfVxWU4i8XaCk)Ht42M1heXesOl4n6Ud0iYPqSK5jZ41ffbgcyp0jQ(8WLRpchKWWWgx4Ah23DJh7WO92J)qMnHbzTfNsbkANO0mZNuFIaih45saZmpum)0IDZLMt56apcYrGqaKI4Z2J8jV6geTnpfeU4f7m1ttpsqitxm7RZWCpjjYLS191GiyyGg71OgOKFidoZU08Dum5vkm4TbEbLNwlSVDmz4Jmb4sNR7RGVcmiLQb21Br5HnS5vl4yaywTnbJ)kwuV1qcwTcpuyOl)lU0qg8ahU8GAv3FPdKElpf765Noq09a3e6nqA3uF684emaGZgJsar6HgufBa6PddAVqm6fSoMXHSA5yYSR04eSh54gu7lvf7YHDVwgDcqnfhrZcrG7N5kOmbR846l5kmqjhprfC2EmWvbj1il6zIZnbfKQDwZfNvmP0WwygzIwcDNEsWrm3lPdDSZa(nu0jep6bkiv1yeBZtonq7D9OGtsXjSqPDAxBKbzI02Zdb((zKKeiiy3gZ6ulTPT1YkbymI(6PbgMxt0nrzmCHNMF8i4Cubt3zbeJZS2ThYzWI7Z2e9VJ4eZfItGso4MLGIsbv)iInyNzcCvcCwZS0iqw8g2mQm41oRMmJbe355BDe9ftb8uZCOMZaYViTpJd4kiKKvnzDjoubheSp6aoBCFiYdTXP1SOURGLRRFaMVxMmVsI14Pl)bfHo5gl0j9KOGrlztsyHDlcUUZDqBgZVHegQsEtpDYm7bKqrtKEnmWeWPnfItvdt1SPFkmh(Zy2yLb3QYcqdtC)ivEiVhJcH9zmnJq6z805IsO8CthlD)sWuDuStrJff1i8kEmnK7xHWfldEw)vkptutAQMutCH(1fX0aG05Xr7Gt8CX0Z1Xojnb2wjmhQxqA494esq6r5bz(nVIsILhgf5faH2C3FIvAbsJayHKvgVoilnXfHZer6nnXZg4Ps3u89C(iQokarl41)KUiD9Ryco4Cn5bBrjjvILUqB9lH6OskrMLJin2UBcxzx1wzluMXhp95H0nQBu3zgaHFpDNXmH(1rPuhZakQoiKLLENjm(VZUZzoF2RVXpFqnN(Y((yo72b0Nfn9TXC6QM7ZIHZjfHXv7nLz5P6kxNbTqp06l)Io2HEN8QlgnL6r2rILkDBAyhH6eGXCnB1JPbDS2z7R0k)BzBRmLEopnZ6KY7OqEkeK3aH4vX81rxL572H0M4)gsrEfuglmqzyME66OmwiAqi4Llzm0Iy5VahEZvcF7Ch()esLH5VtR6cYILot27d0(HXqClib5iqnHuq(vOwTKmQT7pO8Q(SkEMLO72aAYBFtX6FMEkxS()frD5)pfR5VmsiG9Vr(uX65wZTgFxXZfp)nWC)uvFglEMdAQvDRh)oNVFBik9W90xXPdxTtD3ahu0vRB6YBr9ZrPtJ6NKAF80pRMUmQ)58YVR2Q)ejbCf(LqO(9QzdZrOh5IdJIGmzr7ad8094WW79rW3WoLfIEe9LV12AQT4kuBU990mEDQRKU)Pw32Z7d2wVUQMOlUCLwjZxneUGUo64nTW(jNz9UqEU3TxRZ0ExQuFRfTjQ31WkR(mFjLBN7jXoq1h3Zk1Wz8qwzjzM8kB1FGhDMoFiOvMuSmAMIknYqePHiO6Qi6ruLPy)4SZNnK99JZgI8eQ4vwIgQV)85oQDT8HI11xoutn9LdiwpF5qAQLxeWQ64RgtSg(HSt1wmTM9S2Q3bpJo35drbKcaklyHQZFY5b7HGwRsJLrSVQXF0zS95ZTRcN7q0Oa1SmAyzPKa0L6yRF(nxBsuuIDgv)6QVAVS1j)6oORvtBnFYzIb1v61zysYJAvNSwSuo1Lrt9MB0BqqAmhPct99pmQlt75gGUo7RlgXzJ68TymAiVbJhD255B4McIkaPvC8EAR5DyVaIlZsrgGQ3NGjm07NmXWXMwSNCBWMNHIrBtDqTWG1UMM5lHBz8ZrVVDs0oaZrRMq4yF3iTUBZSf5tKYbGUJeAOdJpXCBQA5rm69gdlo1CyXP3Dj7jZAUuLYcQU2cSB096a8MrUQMyP7JbSoPFbx0f)oJ1nBeJ6F2w1RVZezuxUqjMATMAFW)KTz0QRLw44Tv15Jmuz(tqeoZqRuCDzWcvDZz8O3ZP7AsrEY5ZMPY7WErUmvJc0maQvH2aHq1NuvUvvNDcRmnF9wtkBLYCFAvQE14gIcywOYv(rfmpnX2)cRmJHbgmTKqlSVmNEbotw9o3u)9AihQJE9cA6KfDjxF9hna(pg63Av(nsk8bnqouYaJAZMsVhDUJA2y6HsK7uQ9qoJhkxQECLjE0a9qrQfdKrSUigjL7HRJksdCQTxQzkiYf7rRUyrFxJXzpFU)qOC4UvE2C0EJ(iluX7YDqe1dQ3H6tUXg7IQ5B1(PubFREnCy(tYLHdUAlIr5zWqRw(Jj5hqhiS2eV6pc]]
)
