if not WeakAuras.IsLibsOK() then return end
local AddonName = ...
local Private = select(2, ...)

-- Lua APIs
local tinsert, tsort = table.insert, table.sort
local tostring = tostring
local select, pairs, type = select, pairs, type
local ceil = ceil

-- WoW APIs
local GetTalentInfo = GetTalentInfo
local UnitClass = UnitClass
local GetSpellInfo, GetItemInfo, GetItemCount, GetItemIcon = GetSpellInfo, GetItemInfo, GetItemCount, GetItemIcon
local GetShapeshiftFormInfo, GetShapeshiftForm = GetShapeshiftFormInfo, GetShapeshiftForm
local GetRuneCooldown, UnitCastingInfo, UnitChannelInfo = GetRuneCooldown, UnitCastingInfo, UnitChannelInfo
local UnitDetailedThreatSituation = UnitDetailedThreatSituation
local MAX_NUM_TALENTS = MAX_NUM_TALENTS or 40

local WeakAuras = WeakAuras
local L = WeakAuras.L

local SpellRange = LibStub("SpellRange-1.0")
function WeakAuras.IsSpellInRange(spellId, unit)
  return SpellRange.IsSpellInRange(spellId, unit)
end

local LibRangeCheck = LibStub("LibRangeCheck-2.0")

function WeakAuras.GetRange(unit, checkVisible)
  return LibRangeCheck:GetRange(unit, checkVisible);
end

function WeakAuras.CheckRange(unit, range, operator)
  local min, max = LibRangeCheck:GetRange(unit, true);
  if (type(range) ~= "number") then
    range = tonumber(range);
  end
  if (not range) then
    return
  end
  if (operator == "<=") then
    return (max or 999) <= range;
  else
    return (min or 0) >= range;
  end
end

local RangeCacheStrings = {friend = "", harm = "", misc = ""}
local function RangeCacheUpdate()
  local friend, harm, misc = {}, {}, {}
  local friendString, harmString, miscString

  for range in LibRangeCheck:GetFriendCheckers() do
    tinsert(friend, range)
  end
  tsort(friend)
  for range in LibRangeCheck:GetHarmCheckers() do
    tinsert(harm, range)
  end
  tsort(harm)
  for range in LibRangeCheck:GetMiscCheckers() do
    tinsert(misc, range)
  end
  tsort(misc)

  for _, key in pairs(friend) do
    friendString = (friendString and (friendString .. ", ") or "") .. key
  end
  for _, key in pairs(harm) do
    harmString = (harmString and (harmString .. ", ") or "") .. key
  end
  for _, key in pairs(misc) do
      miscString = (miscString and (miscString .. ", ") or "") .. key
  end
  RangeCacheStrings.friend, RangeCacheStrings.harm, RangeCacheStrings.misc = friendString, harmString, miscString
end

LibRangeCheck:RegisterCallback(LibRangeCheck.CHECKERS_CHANGED, RangeCacheUpdate)

function WeakAuras.UnitDetailedThreatSituation(unit1, unit2)
  local ok, aggro, status, threatpct, rawthreatpct, threatvalue = pcall(UnitDetailedThreatSituation, unit1, unit2)
  if ok then
    return aggro, status, threatpct, rawthreatpct, threatvalue
  end
end

local constants = {
  nameRealmFilterDesc = L[" Filter formats: 'Name', 'Name-Realm', '-Realm'. \n\nSupports multiple entries, separated by commas\nCan use \\ to escape -."],
  instanceFilterDeprecated = L["This filter has been moved to the Location trigger. Change your aura to use the new Location trigger or join the WeakAuras Discord server for help."],
  guildFilterDesc = L["Supports multiple entries, separated by commas. Escape with \\. Prefix with '-' for negation."],
  encounterDBMDesc = (WeakAuras.IsDBMRegistered() and "" or "|cFFFF0000") .. L["Requires Deadly Boss Mods (DBM) to detect encounters."] .. (WeakAuras.IsDBMRegistered() and "" or "|r")
}

WeakAuras.UnitRaidRole = function(unit)
  local raidID = UnitInRaid(unit)
  if raidID then
    return select(10, GetRaidRosterInfo(raidID + 1)) or "NONE"
  end
end

function WeakAuras.SpellSchool(school)
  return Private.combatlog_spell_school_types[school] or ""
end

function WeakAuras.RaidFlagToIndex(flag)
  return Private.combatlog_raidFlags[flag] or 0
end

Private.function_strings = {
  count = [[
    return function(count)
      if(count %s %s) then
        return true
      else
        return false
      end
    end
  ]],
  count_fraction = [[
    return function(count, max)
      if max == 0 then
        return false
      end
      local fraction = count/max
      if(fraction %s %s) then
        return true
      else
        return false
      end
    end
  ]],
  always = [[
    return function()
      return true
    end
  ]]
};

local hsvFrame = CreateFrame("ColorSelect")

-- HSV transition, for a much prettier color transition in many cases
-- see http://www.wowinterface.com/forums/showthread.php?t=48236
function WeakAuras.GetHSVTransition(perc, r1, g1, b1, a1, r2, g2, b2, a2)
  --get hsv color for colorA
  hsvFrame:SetColorRGB(r1, g1, b1)
  local h1, s1, v1 = hsvFrame:GetColorHSV() -- hue, saturation, value
  --get hsv color for colorB
  hsvFrame:SetColorRGB(r2, g2, b2)
  local h2, s2, v2 = hsvFrame:GetColorHSV() -- hue, saturation, value
  local h3 = floor(h1 - (h1 - h2) * perc)
  -- find the shortest arc through the color circle, then interpolate
  local diff = h2 - h1
  if diff < -180 then
    diff = diff + 360
  elseif diff > 180 then
    diff = diff - 360
  end

  h3 = (h1 + perc * diff) % 360
  local s3 = s1 - ( s1 - s2 ) * perc
  local v3 = v1 - ( v1 - v2 ) * perc
  --get the RGB values of the new color
  hsvFrame:SetColorHSV(h3, s3, v3)
  local r, g, b = hsvFrame:GetColorRGB()
  --interpolate alpha
  local a = a1 - ( a1 - a2 ) * perc
  --return the new color
  return r, g, b, a
end


Private.anim_function_strings = {
straight = [[
function(progress, start, delta)
    return start + (progress * delta)
end
]],

straightTranslate = [[
function(progress, startX, startY, deltaX, deltaY)
    return startX + (progress * deltaX), startY + (progress * deltaY)
end
]],

straightScale = [[
function(progress, startX, startY, scaleX, scaleY)
    return startX + (progress * (scaleX - startX)), startY + (progress * (scaleY - startY))
end
]],

straightColor = [[
function(progress, r1, g1, b1, a1, r2, g2, b2, a2)
    return r1 + (progress * (r2 - r1)), g1 + (progress * (g2 - g1)), b1 + (progress * (b2 - b1)), a1 + (progress * (a2 - a1))
end
]],

straightHSV = [[
function(progress, r1, g1, b1, a1, r2, g2, b2, a2)
    return WeakAuras.GetHSVTransition(progress, r1, g1, b1, a1, r2, g2, b2, a2)
end
]],

circle = [[
function(progress, startX, startY, deltaX, deltaY)
    local angle = progress * 2 * math.pi
    return startX + (deltaX * math.cos(angle)), startY + (deltaY * math.sin(angle))
end
]],

circle2 = [[
function(progress, startX, startY, deltaX, deltaY)
    local angle = progress * 2 * math.pi
    return startX + (deltaX * math.sin(angle)), startY + (deltaY * math.cos(angle))
end
]],

spiral = [[
function(progress, startX, startY, deltaX, deltaY)
    local angle = progress * 2 * math.pi
    return startX + (progress * deltaX * math.cos(angle)), startY + (progress * deltaY * math.sin(angle))
end
]],

spiralandpulse = [[
function(progress, startX, startY, deltaX, deltaY)
    local angle = (progress + 0.25) * 2 * math.pi
    return startX + (math.cos(angle) * deltaX * math.cos(angle*2)), startY + (math.abs(math.cos(angle)) * deltaY * math.sin(angle*2))
end
]],

shake = [[
function(progress, startX, startY, deltaX, deltaY)
    local prog
    if(progress < 0.25) then
        prog = progress * 4
    elseif(progress < .75) then
        prog = 2 - (progress * 4)
    else
        prog = (progress - 1) * 4
    end
    return startX + (prog * deltaX), startY + (prog * deltaY)
end
]],

starShakeDecay = [[
function(progress, startX, startY, deltaX, deltaY)
    local spokes = 10
    local fullCircles = 4

    local r = min(abs(deltaX), abs(deltaY))
    local xScale = deltaX / r
    local yScale = deltaY / r

    local deltaAngle = fullCircles *2 / spokes * math.pi
    local p = progress * spokes
    local i1 = floor(p)
    p = p - i1

    local angle1 = i1 * deltaAngle
    local angle2 = angle1 + deltaAngle

    local x1 = r * math.cos(angle1)
    local y1 = r * math.sin(angle1)

    local x2 = r * math.cos(angle2)
    local y2 = r * math.sin(angle2)

    local x, y = p * x2 + (1-p) * x1, p * y2 + (1-p) * y1
    local ease = math.sin(progress * math.pi / 2)
    return ease * x * xScale, ease * y * yScale
end
]],

bounceDecay = [[
function(progress, startX, startY, deltaX, deltaY)
    local prog = (progress * 3.5) % 1
    local bounce = math.ceil(progress * 3.5)
    local bounceDistance = math.sin(prog * math.pi) * (bounce / 4)
    return startX + (bounceDistance * deltaX), startY + (bounceDistance * deltaY)
end
]],

bounce = [[
function(progress, startX, startY, deltaX, deltaY)
    local bounceDistance = math.sin(progress * math.pi)
    return startX + (bounceDistance * deltaX), startY + (bounceDistance * deltaY)
end
]],

flash = [[
function(progress, start, delta)
    local prog
    if(progress < 0.5) then
        prog = progress * 2
    else
        prog = (progress - 1) * 2
    end
    return start + (prog * delta)
end
]],

pulse = [[
function(progress, startX, startY, scaleX, scaleY)
    local angle = (progress * 2 * math.pi) - (math.pi / 2)
    return startX + (((math.sin(angle) + 1)/2) * (scaleX - 1)), startY + (((math.sin(angle) + 1)/2) * (scaleY - 1))
end
]],

alphaPulse = [[
function(progress, start, delta)
    local angle = (progress * 2 * math.pi) - (math.pi / 2)
    return start + (((math.sin(angle) + 1)/2) * delta)
end
]],

pulseColor = [[
function(progress, r1, g1, b1, a1, r2, g2, b2, a2)
    local angle = (progress * 2 * math.pi) - (math.pi / 2)
    local newProgress = ((math.sin(angle) + 1)/2);
    return r1 + (newProgress * (r2 - r1)),
         g1 + (newProgress * (g2 - g1)),
         b1 + (newProgress * (b2 - b1)),
         a1 + (newProgress * (a2 - a1))
end
]],

pulseHSV = [[
function(progress, r1, g1, b1, a1, r2, g2, b2, a2)
    local angle = (progress * 2 * math.pi) - (math.pi / 2)
    local newProgress = ((math.sin(angle) + 1)/2);
    return WeakAuras.GetHSVTransition(newProgress, r1, g1, b1, a1, r2, g2, b2, a2)
end
]],

fauxspin = [[
function(progress, startX, startY, scaleX, scaleY)
    local angle = progress * 2 * math.pi
    return math.cos(angle) * scaleX, startY + (progress * (scaleY - startY))
end
]],

fauxflip = [[
function(progress, startX, startY, scaleX, scaleY)
    local angle = progress * 2 * math.pi
    return startX + (progress * (scaleX - startX)), math.cos(angle) * scaleY
end
]],

backandforth = [[
function(progress, start, delta)
    local prog
    if(progress < 0.25) then
        prog = progress * 4
    elseif(progress < .75) then
        prog = 2 - (progress * 4)
    else
        prog = (progress - 1) * 4
    end
    return start + (prog * delta)
end
]],

wobble = [[
function(progress, start, delta)
    local angle = progress * 2 * math.pi
    return start + math.sin(angle) * delta
end
]],

hide = [[
function()
    return 0
end
]]
};

Private.anim_presets = {
  -- Start and Finish
  slidetop = {
    type = "custom",
    duration = 0.25,
    use_translate = true,
    x = 0, y = 50,
    use_alpha = true,
    alpha = 0
  },
  slideleft = {
    type = "custom",
    duration = 0.25,
    use_translate = true,
    x = -50,
    y = 0,
    use_alpha = true,
    alpha = 0
  },
  slideright = {
    type = "custom",
    duration = 0.25,
    use_translate = true,
    x = 50,
    y = 0,
    use_alpha = true,
    alpha = 0
  },
  slidebottom = {
    type = "custom",
    duration = 0.25,
    use_translate = true,
    x = 0,
    y = -50,
    use_alpha = true,
    alpha = 0
  },
  fade = {
    type = "custom",
    duration = 0.25,
    use_alpha = true,
    alpha = 0
  },
  grow = {
    type = "custom",
    duration = 0.25,
    use_scale = true,
    scalex = 2,
    scaley = 2,
    use_alpha = true,
    alpha = 0
  },
  shrink = {
    type = "custom",
    duration = 0.25,
    use_scale = true,
    scalex = 0,
    scaley = 0,
    use_alpha = true,
    alpha = 0
  },
  spiral = {
    type = "custom",
    duration = 0.5,
    use_translate = true,
    x = 100,
    y = 100,
    translateType = "spiral",
    use_alpha = true,
    alpha = 0
  },
  bounceDecay = {
    type = "custom",
    duration = 1.5,
    use_translate = true,
    x = 50,
    y = 50,
    translateType = "bounceDecay",
    use_alpha = true,
    alpha = 0
  },
  starShakeDecay = {
    type = "custom",
    duration = 1,
    use_translate = true,
    x = 50,
    y = 50,
    translateType = "starShakeDecay",
    use_alpha = true,
    alpha = 0
  },
  -- Main
  shake = {
    type = "custom",
    duration = 0.5,
    use_translate = true,
    x = 10,
    y = 0,
    translateType = "circle2"
  },
  spin = {
    type = "custom",
    duration = 1,
    use_scale = true,
    scalex = 1,
    scaley = 1,
    scaleType = "fauxspin"
  },
  flip = {
    type = "custom",
    duration = 1,
    use_scale = true,
    scalex = 1,
    scaley = 1,
    scaleType = "fauxflip"
  },
  wobble = {
    type = "custom",
    duration = 0.5,
    use_rotate = true,
    rotate = 3,
    rotateType = "wobble"
  },
  pulse = {
    type = "custom",
    duration = 0.75,
    use_scale = true,
    scalex = 1.05,
    scaley = 1.05,
    scaleType = "pulse"
  },
  alphaPulse = {
    type = "custom",
    duration = 0.5,
    use_alpha = true,
    alpha = 0.5,
    alphaType = "alphaPulse"
  },
  rotateClockwise = {
    type = "custom",
    duration = 4,
    use_rotate = true,
    rotate = -360
  },
  rotateCounterClockwise = {
    type = "custom",
    duration = 4,
    use_rotate = true,
    rotate = 360
  },
  spiralandpulse = {
    type = "custom",
    duration = 6,
    use_translate = true,
    x = 100,
    y = 100,
    translateType = "spiralandpulse"
  },
  circle = {
    type = "custom",
    duration = 4,
    use_translate = true,
    x = 100,
    y = 100,
    translateType = "circle"
  },
  orbit = {
    type = "custom",
    duration = 4,
    use_translate = true,
    x = 100,
    y = 100,
    translateType = "circle",
    use_rotate = true,
    rotate = 360
  },
  bounce = {
    type = "custom",
    duration = 0.6,
    use_translate = true,
    x = 0,
    y = 25,
    translateType = "bounce"
  }
};

function WeakAuras.CheckTalentByIndex(index, extraOption)
  local tab = ceil(index / MAX_NUM_TALENTS)
  local num_talent = (index - 1) % MAX_NUM_TALENTS + 1
  local name, _, _, _, rank  = GetTalentInfo(tab, num_talent)
  if name == nil then
    if GetTalentInfo(1, 1) == nil then
      -- No talents at all, likely to early to grab
      return nil
    else
      -- Talent doesn't exist; ignore it
      -- Should be cleared if missing, but struc doesn't exist yet
      return true
    end
  end
  local result = rank and rank > 0
  if extraOption == 4 then
    return result
  elseif extraOption == 5 then
    return not result
  end
  return result;
end

function WeakAuras.CheckNumericIds(loadids, currentId)
  if (not loadids or not currentId) then
    return false;
  end

  local searchFrom = 0;
  local startI, endI = string.find(loadids, currentId, searchFrom);
  while (startI) do
    searchFrom = endI + 1; -- start next search from end
    local isNeg = (startI > 1 and string.sub(loadids, startI - 1, startI - 1) == "-")
    if (isNeg or startI == 1 or tonumber(string.sub(loadids, startI - 1, startI - 1)) == nil) then
      -- Either right at start, or character before is not a number
      if (endI == string.len(loadids) or tonumber(string.sub(loadids, endI + 1, endI + 1)) == nil) then
        return not isNeg
      end
    end
    startI, endI = string.find(loadids, currentId, searchFrom);
  end
  return false;
end

function WeakAuras.ValidateNumeric(info, val)
  if val ~= nil and val ~= "" and (not tonumber(val) or tonumber(val) >= 2^31) then
    return false;
  end
  return true
end

function WeakAuras.ValidateTime(info, val)
  if val ~= nil and val ~= "" then
    if not tonumber(val) then
      if val:sub(1,1) == "-" then
        val = val:sub(2, #val)
      end
      return (val:match("^%d+:%d+:[%d%.]+$") or val:match("^%d+:[%d+%.]+$")) and true or false
    elseif tonumber(val) >= 2^31 then
      return false
    end
  end
  return true
end

function WeakAuras.TimeToSeconds(val)
  if tonumber(val) then
    return tonumber(val)
  else
    local sign = 1
    if val:sub(1,1) == "-" then
      sign = -1
      val = val:sub(2, #val)
    end
    local h, m, s = val:match("^(%d+):(%d+):([%d%.]+)$")
    if h and m and s then
      return (h*3600 + m*60 + s) * sign
    else
      local m, s = val:match("^(%d+):([%d%.]+)$")
      if m and s then
        return (m*60 + s) * sign
      end
    end
  end
end

Private.tinySecondFormat = function(value)
  if type(value) == "string" then value = tonumber(value) end
  if type(value) == "number" then
     local negative = value < 0
     value = math.abs(value)
     local fraction = value - math.floor(value)
     local ret
     if value > 3600 then
        ret = ("%i:%02i:%02i"):format(math.floor(value / 3600), math.floor((value % 3600) / 60), value % 60)
     elseif value > 60 then
        ret = ("%i:%02i"):format(math.floor(value / 60), value % 60)
     else
        ret = ("%i"):format(value)
     end
     local negSign = negative and "-" or ""
     if fraction > 0 then
        return negSign .. ret .. tostring(Round(fraction * 100) / 100):sub(2)
     else
        return negSign .. ret
     end
  end
end

function Private.ExecEnv.GetSpecIcon(specID)
  return specID and Private.specid_to_icon[specID] or ""
end

function Private.ExecEnv.GetSpecName(specID)
  return specID and Private.specid_to_name[specID] or ""
end

function Private.ExecEnv.GetSpecID(specName)
  return specName and Private.specname_to_id[specName] or 0
end

function Private.ExecEnv.GetUnitTalentSpec(unit)
  local spec = WeakAuras.LGT:GetUnitTalentSpec(unit)
  -- LibGroupTalents misses Guardian for tanks, so we fix it here
  if spec == L["Feral Combat"] then
    return (WeakAuras.LGT:GetUnitRole(unit) == "tank" and L["Guardian"]) or spec
  end
  return spec
end

function WeakAuras.CheckClassSpec(specID)
  specID = tonumber(specID)
  if not specID then return end
  local class = select(2, UnitClass("player")) or ""
  local currentSpec = Private.ExecEnv.GetUnitTalentSpec("player") or ""
  return Private.ExecEnv.GetSpecName(specID) == class .. currentSpec
end

function WeakAuras.SpecForUnit(unit)
  if not unit then return 0 end
  local spec = Private.ExecEnv.GetUnitTalentSpec(unit)
  local class = select(2, UnitClass(unit))
  return (spec and class) and Private.ExecEnv.GetSpecID(class .. spec) or 0
end

function Private.ExecEnv.ParseStringCheck(input)
  if not input then return end
  local matcher = {
    entries = {},
    negativeEntries = {},
    Check = function(self, e)
      return false
    end,
    CheckBoth = function(self, e)
      return self.entries[e] and not self.negativeEntries[e]
    end,
    CheckPositive = function(self, e)
      return self.entries[e]
    end,
    CheckNegative = function(self, e)
      return not self.negativeEntries[e]
    end,
    Add = function(self, e, negate)
      if negate then
        self.negativeEntries[e] = true
      else
        self.entries[e] = true
      end
    end
  }

  local start = 1
  local escaped = false
  local partial = ""
  local negate = false
  for i = 1, #input do
    local c = input:sub(i, i)
    if escaped then
      escaped = false
    elseif c == '\\' then
      partial = partial .. input:sub(start, i - 1)
      start = i + 1
      escaped = true
    elseif c == "," then
      matcher:Add(partial .. input:sub(start, i - 1):trim(), negate)
      start = i + 1
      partial = ""
      negate = false
    elseif c == "-" and partial:trim() == "" and input:sub(start, i - 1):trim() == "" then
      start = i + 1
      negate = true
    end
  end
  matcher:Add(partial .. input:sub(start, #input):trim(), negate)

  -- Update check function
  if next(matcher.entries) and next(matcher.negativeEntries) then
    matcher.Check = matcher.CheckBoth
  elseif next(matcher.entries) then
    matcher.Check = matcher.CheckPositive
  elseif next(matcher.negativeEntries) then
    matcher.Check = matcher.CheckNegative
  end

  return matcher
end

function WeakAuras.ValidateNumericOrPercent(info, val)
  if val ~= nil and val ~= "" then
    local index = val:find("%% *$")
    local number = index and tonumber(val:sub(1, index-1)) or tonumber(val)
    if(not number or number >= 2^31) then
      return false;
    end
  end
  return true
end

function Private.ExecEnv.CheckGroupMemberType(loadSetting, currentFlags)
  if loadSetting == "LEADER" then
    return bit.band(currentFlags, 1) == 1
  elseif loadSetting == "ASSIST" then
    return bit.band(currentFlags, 2) == 2
  else
    return currentFlags == 0
  end
end

function Private.ExecEnv.CheckChargesDirection(direction, triggerDirection)
  return triggerDirection == "CHANGED"
    or (triggerDirection == "GAINED" and direction > 0)
    or (triggerDirection == "LOST" and direction < 0)
end

function Private.ExecEnv.CheckCombatLogFlags(flags, flagToCheck)
  if type(flags) ~= "number" then return end
  if(flagToCheck == "Mine") then
    return bit.band(flags, COMBATLOG_OBJECT_AFFILIATION_MINE) > 0
  elseif (flagToCheck == "InGroup") then
    return bit.band(flags, COMBATLOG_OBJECT_AFFILIATION_OUTSIDER) == 0
  elseif (flagToCheck == "InParty") then
    return bit.band(flags, COMBATLOG_OBJECT_AFFILIATION_PARTY) > 0
  elseif (flagToCheck == "NotInGroup") then
    return bit.band(flags, COMBATLOG_OBJECT_AFFILIATION_OUTSIDER) > 0
  end
end

function Private.ExecEnv.CheckCombatLogFlagsReaction(flags, flagToCheck)
  if type(flags) ~= "number" then return end
  if (flagToCheck == "Hostile") then
    return bit.band(flags, 64) ~= 0;
  elseif (flagToCheck == "Neutral") then
    return bit.band(flags, 32) ~= 0;
  elseif (flagToCheck == "Friendly") then
    return bit.band(flags, 16) ~= 0;
  end
end

local objectTypeToBit = {
  Object = 16384,
  Guardian = 8192,
  Pet = 4096,
  NPC = 2048,
  Player = 1024,
}

function Private.ExecEnv.CheckCombatLogFlagsObjectType(flags, flagToCheck)
  if type(flags) ~= "number" then return end
  local bitToCheck = objectTypeToBit[flagToCheck]
  if not bitToCheck then return end
  return bit.band(flags, bitToCheck) ~= 0;
end

function WeakAuras.IsSpellKnownForLoad(spell, exact)
  local result = WeakAuras.IsSpellKnown(spell)
  if exact or result then
    return result
  end
  -- Dance through the spellname to the current spell id
  spell = GetSpellInfo(spell)
  if spell then
    return WeakAuras.IsSpellKnown(spell)
  end
end

function WeakAuras.IsSpellKnown(spell, pet)
  local id = tonumber(spell)
  if id then
    if id > 0 and id < 2^31 then
      return IsSpellKnown(id, pet)
    end
    return false
  end
  return GetSpellInfo(spell) and true or false
end

function WeakAuras.IsSpellKnownIncludingPet(spell)
  if (not spell) then
    return false;
  end
  return WeakAuras.IsSpellKnown(spell, false) or WeakAuras.IsSpellKnown(spell, true)
end

function WeakAuras.IsGlyphActive(glyphID)
  for slot = 1, GetNumGlyphSockets() or 6 do
    local enabled, _, glyphId = GetGlyphSocketInfo(slot)
    if glyphID == glyphId then
      return enabled == 1
    end
  end
  return false
end

function WeakAuras.GetEffectiveAttackPower()
  local base, pos, neg = UnitAttackPower("player")
  return base + pos + neg
end

function WeakAuras.GetEffectiveSpellPower()
  -- Straight from the PaperDoll
  local spellPower = 0
  for i = 2, MAX_SPELL_SCHOOLS or 7 do
    spellPower = max(spellPower, GetSpellBonusDamage(i))
  end
  return spellPower
end

function WeakAuras.GetEffectiveRangedAttackPower()
  local base, pos, neg = UnitRangedAttackPower("player")
  return base + pos + neg
end

function WeakAuras.IsTalentKnownForLoad(spell, exact)
  local result = WeakAuras.IsTalentKnown(spell)
  if exact or result then
    return result
  end
  return false
end

function WeakAuras.IsTalentKnown(spell)
  if (spell) then
    if tonumber(spell) then
      return C_CharacterAdvancement.IsKnownSpellID(spell) and true or false;
    else
      -- name of talent zzz
      if (GetSpellInfo(spell)) then -- maybe talent ability?
        return true
      end
    end
  end
  return false
end

function WeakAuras.IsSpecActive(specID)
  specID = specID and tonumber(specID)
  if not specID then return false end
  return SpecializationUtil.GetActiveSpecialization() == specID
end

function WeakAuras.IsMysticEnchantApplied(spellID)
  spellID = spellID and tonumber(spellID)
  if not spellID then return false end
  return MysticEnchantUtil.IsEnchantApplied("player", spellID)
end


---helper to check if a condition is checked and have a single value, and return it
function Private.checkForSingleLoadCondition(trigger, name, validateFn)
  local use_name = "use_"..name
  local trigger_use_name = trigger[use_name]
  local trigger_name = trigger[name]
  if trigger_use_name == true
  and trigger_name
  and trigger_name.single ~= nil
  and (validateFn == nil or validateFn(trigger_name.single))
  then
    return trigger_name.single
  end
  if trigger_use_name == false and trigger_name and trigger_name.multi ~= nil then
    local count = 0
    local key
    for k, v in pairs(trigger_name.multi) do
      if v ~= nil
      and (validateFn == nil or validateFn(k))
      then
        count = count + 1
        key = k
      end
    end
    if count == 1 then
      return key
    end
  end
end

Private.load_prototype = {
  -- Each entry
  --   name: name of argument for load function/option for options/setting in saved data
  --   Options data
  --     display: name to be displayed in the options
  --     type: type to be used for the options
  --     width: width in the options
  --     hidden: whether the option is shown in the options, defaults to false
  --   Load Function Data
  --     enable: whether the test should be tested or not, defaults to true
  --     test: overrides the default test
  --     init: whether the argument should be a function parameter or not. "arg" for yes. Defaults to no argument
  --     events: the events on which the test must be reevaluated
  --     optional: whether the test is relevant for the options classification between loaded and unloaded, defaults to false
  args = {
    {
      name ="generalTitle",
      display = L["General"],
      type = "description",
    },
    {
      name = "combat",
      display = L["In Combat"],
      type = "tristate",
      width = WeakAuras.normalWidth,
      init = "arg",
      optional = true,
      events = {"PLAYER_REGEN_DISABLED", "PLAYER_REGEN_ENABLED"}
    },
    {
      name = "never",
      display = L["Never"],
      type = "toggle",
      width = WeakAuras.normalWidth,
      test = "false",
    },
    {
      name = "alive",
      display = L["Alive"],
      type = "tristate",
      init = "arg",
      width = WeakAuras.normalWidth,
      optional = true,
      events = {"PLAYER_DEAD", "PLAYER_ALIVE", "PLAYER_UNGHOST"}
    },
    {
      name = "encounter",
      display = WeakAuras.newFeatureString .. L["In Encounter"],
      desc = constants.encounterDBMDesc,
      type = "tristate",
      width = WeakAuras.normalWidth,
      init = "arg",
      optional = true,
      events = {"ENCOUNTER_START", "ENCOUNTER_END"}
    },
    {
      name = "pvpmode",
      display = L["PvP Mode Active"],
      type = "tristate",
      init = "arg",
      width = WeakAuras.normalWidth,
      optional = true,
      events = {"PLAYER_FLAGS_CHANGED", "UNIT_FACTION", "ZONE_CHANGED"}
    },
    {
      name = "manastorm",
      display = L["In Manastorm"],
      type = "tristate",
      init = "arg",
      width = WeakAuras.normalWidth,
      events = {"ACTIVE_MANASTORM_UPDATED"}
    },
    {
      name = "vehicle",
      display = L["In Vehicle"],
      type = "tristate",
      init = "arg",
      width = WeakAuras.normalWidth,
      optional = true,
      events = {"VEHICLE_UPDATE", "UNIT_ENTERED_VEHICLE", "UNIT_EXITED_VEHICLE", "UNIT_FLAGS"}
    },
    {
      name = "vehicleUi",
      display = L["Has Vehicle UI"],
      type = "tristate",
      init = "arg",
      width = WeakAuras.normalWidth,
      optional = true,
      events = {"VEHICLE_UPDATE", "UNIT_ENTERED_VEHICLE", "UNIT_EXITED_VEHICLE"}
    },
    {
      name = "mounted",
      display = L["Mounted"],
      type = "tristate",
      init = "arg",
      width = WeakAuras.normalWidth,
      optional = true,
      events = {"MOUNTED_UPDATE"}
    },
    {
      name ="playerTitle",
      display = L["Player"],
      type = "description",
    },
    {
      name = "class",
      display = L["Player Class"],
      type = "multiselect",
      values = "class_types",
      init = "arg"
    },
    {
      name = "specialization",
      display = L["Talent Specialization"],
      type = "multiselect",
      values = "specialization_types",
      test = "WeakAuras.IsSpecActive(%s)",
      preamble = "wipe(WeakAuras.specialization_types) for i = 1, SpecializationUtil.GetNumSpecializations() do WeakAuras.specialization_types[i] = i .. \". \" .. SpecializationUtil.GetSpecializationInfo(i) end",
      events = {"ASCENSION_CA_SPECIALIZATION_ACTIVE_ID_CHANGED"},
      init = "arg"
    },
    {
      name = "knowntalent",
      display = L["Talent"],
      type = "talent",
      test = "WeakAuras.IsTalentKnownForLoad(%s, %s)",
      events = {"ASCENSION_KNOWN_ENTRIES_UPDATED"},
      orConjunctionGroup = "knowntalent",
      showExactOption = true,
    },
    {
      name = "mysticenchantactive",
      display = L["Mystic Enchant"],
      type = "mysticenchant",
      test = "WeakAuras.IsMysticEnchantApplied(%s)",
      events = {"MYSTIC_ENCHANT_PRESET_SET_ACTIVE_RESULT", "MYSTIC_ENCHANT_SLOT_UPDATE", "PLAYER_EQUIPMENT_CHANGED", "SPELLS_CHANGED"},
      showExactOption = true,
    },
    {
      name = "not_mysticenchantactive",
      display = WeakAuras.newFeatureString .. L["|cFFFF0000Not|r Mystic Enchant"],
      type = "mysticenchant",
      test = "not WeakAuras.IsMysticEnchantApplied(%s)",
      events = {"MYSTIC_ENCHANT_PRESET_SET_ACTIVE_RESULT", "MYSTIC_ENCHANT_SLOT_UPDATE", "PLAYER_EQUIPMENT_CHANGED", "SPELLS_CHANGED"},
      showExactOption = true,
    },
    {
      name = "spellknown",
      display = L["Spell Known"],
      type = "spell",
      test = "WeakAuras.IsSpellKnownForLoad(%s, %s)",
      events = {"SPELLS_CHANGED", "UNIT_PET"},
      showExactOption = true
    },
    {
      name = "not_spellknown",
      display = WeakAuras.newFeatureString .. L["|cFFFF0000Not|r Spell Known"],
      type = "spell",
      test = "not WeakAuras.IsSpellKnownForLoad(%s, %s)",
      events = {"SPELLS_CHANGED", "UNIT_PET", "ASCENSION_KNOWN_ENTRIES_UPDATED"},
      showExactOption = true
    },
    {
      name = "player",
      init = "arg",
      enable = false,
      hidden = true
    },
    {
      name = "realm",
      init = "arg",
      enable = false,
      hidden = true
    },
    {
      name = "guild",
      init = "arg",
      enable = false,
      hidden = true
    },
    {
      name = "namerealm",
      display = L["Player Name/Realm"],
      type = "string",
      multiline = true,
      test = "nameRealmChecker:Check(player, realm)",
      preamble = "local nameRealmChecker = Private.ExecEnv.ParseNameCheck(%q)",
      desc = constants.nameRealmFilterDesc,
    },
    {
      name = "ignoreNameRealm",
      display = L["|cFFFF0000Not|r Player Name/Realm"],
      type = "string",
      multiline = true,
      test = "not nameRealmIgnoreChecker:Check(player, realm)",
      preamble = "local nameRealmIgnoreChecker = Private.ExecEnv.ParseNameCheck(%q)",
      desc = constants.nameRealmFilterDesc,
    },
    {
      name = "guildcheck",
      display = L["Guild"],
      type = "string",
      multiline = true,
      test = "guildChecker:Check(guild)",
      preamble = "local guildChecker = Private.ExecEnv.ParseStringCheck(%q)",
      desc = constants.guildFilterDesc,
      events = {"PLAYER_GUILD_UPDATE"}
    },
    {
      name = "race",
      display = L["Player Race"],
      type = "multiselect",
      values = "race_types",
      init = "arg"
    },
    {
      name = "faction",
      display = L["Player Faction"],
      type = "multiselect",
      values = "faction_group",
      init = "arg"
    },
    {
      name = "level",
      display = L["Player Level"],
      type = "number",
      init = "arg",
      events = {"PLAYER_LEVEL_UP"},
      multiEntry = {
        operator = "and",
        limit = 2
      },
    },
    {
      name = "role",
      display = L["Spec Role"],
      type = "multiselect",
      values = "role_types",
      init = "arg",
      events = {"PLAYER_ROLES_ASSIGNED", "PLAYER_TALENT_UPDATE", "WA_DELAYED_PLAYER_ENTERING_WORLD"}
    },
    {
      name = "spec_position",
      display = WeakAuras.newFeatureString .. L["Spec Position"],
      type = "multiselect",
      values = "spec_position_types",
      init = "arg",
      events = {"PLAYER_ROLES_ASSIGNED", "PLAYER_TALENT_UPDATE", "WA_DELAYED_PLAYER_ENTERING_WORLD"}
    },
    {
      name = "raid_role",
      display = L["Raid Role"],
      type = "multiselect",
      values = "raid_role_types",
      init = "arg",
      events = {"PLAYER_ROLES_ASSIGNED"}
    },
    {
      name = "ingroup",
      display = L["Group Type"],
      type = "multiselect",
      width = WeakAuras.normalWidth,
      init = "arg",
      values = "group_types",
      events = {"PARTY_MEMBERS_CHANGED", "RAID_ROSTER_UPDATE"},
      optional = true,
    },
    {
      name = "groupSize",
      display = L["Group Size"],
      type = "number",
      init = "arg",
      events = {"PARTY_MEMBERS_CHANGED", "RAID_ROSTER_UPDATE"},
      multiEntry = {
        operator = "and",
        limit = 2
      },
      optional = true,
    },
    {
      name = "group_leader",
      display = WeakAuras.newFeatureString .. L["Group Leader/Assist"],
      type = "multiselect",
      init = "arg",
      events = {"PARTY_LEADER_CHANGED", "PLAYER_FLAGS_CHANGED", "RAID_ROSTER_UPDATE"},
      values = "group_member_types",
      test = "Private.ExecEnv.CheckGroupMemberType(%s, group_leader)",
      optional = true,
    },
    {
      name = "ruleset",
      display = "PvP Ruleset",
      type = "multiselect",
      width = WeakAuras.normalWidth,
      init = "arg",
      values = "ruleset_types",
      events = {"ZONE_CHANGED_NEW_AREA", "PLAYER_FLAGS_CHANGED"}
    },
    {
      name ="locationTitle",
      display = L["Location"],
      type = "description",
    },
    {
      name = "zone",
      display = L["Zone Name"],
      type = "string",
      multiline = true,
      init = "arg",
      preamble = "local checker = Private.ExecEnv.ParseStringCheck(%q)",
      test = "checker:Check(zone)",
      events = {"ZONE_CHANGED", "ZONE_CHANGED_INDOORS", "ZONE_CHANGED_NEW_AREA", "VEHICLE_UPDATE", "WA_DELAYED_PLAYER_ENTERING_WORLD" },
      desc = function()
        return ("\n|cffffd200%s|r%s\n\n%s"):format(L["Current Zone\n"], GetRealZoneText(), L["Supports multiple entries, separated by commas. Prefix with '-' for negation."])
      end,
      optional = true,
    },
    {
      name = "zoneId",
      display = L["Player Location ID(s)"],
      type = "string",
      multiline = true,
      init = "arg",
      test = "WeakAuras.CheckNumericIds(%q, zoneId)",
      events = {"ZONE_CHANGED", "ZONE_CHANGED_INDOORS", "ZONE_CHANGED_NEW_AREA", "VEHICLE_UPDATE", "WA_DELAYED_PLAYER_ENTERING_WORLD" },
      desc = function()
	    return ("\n|cffffd200%s|r%s: %d\n\n%s"):format(L["Current Zone\n"], GetRealZoneText(), GetCurrentMapAreaID(), L["Supports multiple entries, separated by commas. Prefix with '-' for negation."])
	    end,
      optional = true,
    },
    {
      name = "subzone",
      display = L["Subzone Name"],
      type = "string",
      multiline = true,
      init = "arg",
      preamble = "local subzoneChecker = Private.ExecEnv.ParseStringCheck(%q)",
      test = "subzoneChecker:Check(subzone)",
      events = { "ZONE_CHANGED", "ZONE_CHANGED_INDOORS", "ZONE_CHANGED_NEW_AREA", "VEHICLE_UPDATE", "WA_DELAYED_PLAYER_ENTERING_WORLD" },
      desc = function()
        return ("\n|cffffd200%s|r%s\n\n%s"):format(L["Current Zone\n"], GetMinimapZoneText(), L["Supports multiple entries, separated by commas. Prefix with '-' for negation."])
      end,
      optional = true,
    },
    {
      name = "encounterid",
      display = WeakAuras.newFeatureString .. L["Encounter ID(s)"],
      type = "string",
      init = "arg",
      multiline = true,
      desc = Private.get_encounters_list,
      test = "WeakAuras.CheckNumericIds(%q, encounterid)",
      events = {"ENCOUNTER_START", "ENCOUNTER_END"},
      optional = true,
    },
    {
      name = "size",
      display = L["Instance Size Type"],
      type = "multiselect",
      values = "instance_types",
      sorted = true,
      init = "arg",
      events = {"ZONE_CHANGED", "ZONE_CHANGED_INDOORS", "ZONE_CHANGED_NEW_AREA", "WA_DELAYED_PLAYER_ENTERING_WORLD"},
      optional = true,
    },
    {
      name = "difficulty",
      display = L["Instance Difficulty"],
      type = "multiselect",
      values = "difficulty_types",
      init = "arg",
      events = {"PLAYER_DIFFICULTY_CHANGED", "ZONE_CHANGED", "ZONE_CHANGED_INDOORS", "ZONE_CHANGED_NEW_AREA", "WA_DELAYED_PLAYER_ENTERING_WORLD"},
      optional = true,
    },
    {
      name ="equipmentTitle",
      display = L["Equipment"],
      type = "description",
    },
    {
      name = "itemequiped",
      display = L["Item Equipped"],
      type = "item",
      multiEntry = {
        operator = "or"
      },
      test = "IsEquippedItem(%s or '')",
      events = { "UNIT_INVENTORY_CHANGED", "PLAYER_EQUIPMENT_CHANGED"},
      only_exact = true,
    },
    {
      name = "not_itemequiped",
      display = WeakAuras.newFeatureString .. L["|cFFFF0000Not|r Item Equipped"],
      type = "item",
      multiEntry = {
        operator = "or"
      },
      test = "not IsEquippedItem(%s or '')",
      events = { "UNIT_INVENTORY_CHANGED", "PLAYER_EQUIPMENT_CHANGED"},
      only_exact = true,
    },
    {
      name = "itemtypeequipped",
      display = L["Item Type Equipped"],
      type = "multiselect",
      test = "Private.ExecEnv.IsEquippedItemType(%s or '')",
      events = { "UNIT_INVENTORY_CHANGED", "PLAYER_EQUIPMENT_CHANGED"},
      values = "item_weapon_types"
    },
  }
};

local function AddUnitChangeInternalEvents(triggerUnit, t, includePets, unitisunit)
  if (triggerUnit == nil) then
    return
  end
  if (triggerUnit == "multi") then
    -- Handled by normal events"
  elseif triggerUnit == "pet" then
    tinsert(t, "PET_UPDATE")
  else
    if Private.multiUnitUnits[triggerUnit] then
      local isPet
      for unit in pairs(Private.multiUnitUnits[triggerUnit]) do
        isPet = WeakAuras.UnitIsPet(unit)
        if (includePets ~= nil and isPet) or (includePets ~= "PetsOnly" and not isPet) then
          tinsert(t, "UNIT_CHANGED_" .. string.lower(unit))
          if unitisunit then
            tinsert(t, "UNIT_IS_UNIT_CHANGED_" .. string.lower(unit) .. "_" .. string.lower(unitisunit))
          end
        end
      end
    else
      tinsert(t, "UNIT_CHANGED_" .. string.lower(triggerUnit))
      if unitisunit then
        tinsert(t, "UNIT_IS_UNIT_CHANGED_" .. string.lower(triggerUnit) .. "_" .. string.lower(unitisunit))
      end
    end
  end
end

local function AddWatchedUnits(triggerUnit, includePets, unitisunit)
  if (triggerUnit == nil) then
    return
  end
  if (triggerUnit == "multi") then
    -- Handled by normal events"
  elseif triggerUnit == "pet" then
    WeakAuras.WatchForPetDeath();
  else
    if Private.multiUnitUnits[triggerUnit] then
      local isPet
      for unit in pairs(Private.multiUnitUnits[triggerUnit]) do
        isPet = WeakAuras.UnitIsPet(unit)
        if (includePets ~= nil and isPet) or (includePets ~= "PetsOnly" and not isPet) then
          if unitisunit then
            WeakAuras.WatchUnitChange(unitisunit)
          end
          WeakAuras.WatchUnitChange(unit)
        end
      end
    else
      if unitisunit then
        WeakAuras.WatchUnitChange(unitisunit)
      end
      WeakAuras.WatchUnitChange(triggerUnit)
    end
  end
end

local function AddUnitSpecChangeInternalEvents(triggerUnit, t)
  if (triggerUnit == nil) then
    return
  end

  if Private.multiUnitUnits[triggerUnit] then
    for unit in pairs(Private.multiUnitUnits[triggerUnit]) do
      local isPet = WeakAuras.UnitIsPet(unit)
      if (not isPet) then
        tinsert(t, "UNIT_SPEC_CHANGED_" .. string.lower(unit))
      end
    end
  else
    tinsert(t, "UNIT_SPEC_CHANGED_" .. string.lower(triggerUnit))
  end
end

local function AddUnitRoleChangeInternalEvents(triggerUnit, t)
  if (triggerUnit == nil) then
    return
  end

  if Private.multiUnitUnits[triggerUnit] then
    for unit in pairs(Private.multiUnitUnits[triggerUnit]) do
      if not WeakAuras.UnitIsPet(unit) then
        tinsert(t, "UNIT_ROLE_CHANGED_" .. string.lower(unit))
      end
    end
  else
    if not WeakAuras.UnitIsPet(triggerUnit) then
      tinsert(t, "UNIT_ROLE_CHANGED_" .. string.lower(triggerUnit))
    end
  end
end

local function AddRemainingCastInternalEvents(triggerUnit, t)
  if (triggerUnit == nil) then
    return
  end

  if Private.multiUnitUnits[triggerUnit] then
    for unit in pairs(Private.multiUnitUnits[triggerUnit]) do
      tinsert(t, "CAST_REMAINING_CHECK_" .. string.lower(unit))
    end
  else
    tinsert(t, "CAST_REMAINING_CHECK_" .. string.lower(triggerUnit))
  end
end

local function AddUnitEventForEvents(result, unit, event)
  if unit then
    if not result.unit_events then
      result.unit_events = {}
    end
    if not result.unit_events[unit] then
      result.unit_events[unit] = {}
    end
    tinsert(result.unit_events[unit], event)
  else
    if not result.events then
      result.events = {}
    end
    tinsert(result.events, event)
  end
end

local powerEvents = {
  [0] = { "UNIT_MANA", "UNIT_MAXMANA" },
  [1] = { "UNIT_RAGE", "UNIT_MAXRAGE" },
  [2] = { "UNIT_FOCUS", "UNIT_MAXFOCUS" },
  [3] = { "UNIT_ENERGY", "UNIT_MAXENERGY" },
  [27] = { "UNIT_HAPPINESS", "UNIT_MAXHAPPINESS" },
  [6] = { "UNIT_RUNIC_POWER", "UNIT_MAXRUNIC_POWER" }
}

local function AddUnitEventForPowerEvents(result, unit, powerType)
  if powerType then
    if powerType == 4 then return end
    for _, event in ipairs(powerEvents[powerType]) do
      AddUnitEventForEvents(result, unit, event)
    end
  else
    for _, eventsList in pairs(powerEvents) do
      for _, event in ipairs(eventsList) do
        AddUnitEventForEvents(result, unit, event)
      end
    end
  end
  return result
end

local function AddTargetConditionEvents(result, useFocus)
  if useFocus then
    tinsert(result, "PLAYER_FOCUS_CHANGED")
  end
  tinsert(result, "PLAYER_TARGET_CHANGED")
  return result
end

Private.AddTargetConditionEvents = AddTargetConditionEvents

local unitHelperFunctions = {
  UnitChangedForceEventsWithPets = function(trigger)
    local events = {}
    local includePets = trigger.use_includePets == true and trigger.includePets or nil
    if Private.multiUnitUnits[trigger.unit] then
      local isPet
      for unit in pairs(Private.multiUnitUnits[trigger.unit]) do
        isPet = WeakAuras.UnitIsPet(unit)
        if (includePets ~= nil and isPet) or (includePets ~= "PetsOnly" and not isPet) then
          tinsert(events, {"UNIT_CHANGED_" .. unit, unit})
        end
      end
    else
      if trigger.unit then
        tinsert(events, {"UNIT_CHANGED_" .. trigger.unit, trigger.unit})
      end
    end
    return events
  end,

  UnitChangedForceEvents = function(trigger)
    local events = {}
    if Private.multiUnitUnits[trigger.unit] then
      for unit in pairs(Private.multiUnitUnits[trigger.unit]) do
        if not WeakAuras.UnitIsPet(unit) then
          tinsert(events, {"UNIT_CHANGED_" .. unit, unit})
        end
      end
    else
      if trigger.unit then
        tinsert(events, {"UNIT_CHANGED_" .. trigger.unit, trigger.unit})
      end
    end
    return events
  end,

  SpecificUnitCheck = function(trigger)
    if not trigger.use_specific_unit then
      return "local specificUnitCheck = true\n"
    end

    if trigger.unit == nil then
      return "local specificUnitCheck = false\n"
    end

    return string.format([=[
      local specificUnitCheck = UnitIsUnit(%q, unit)
    ]=], trigger.unit or "")
  end
}

Private.event_categories = {
  spell = {
    name = L["Spell"],
    default = "Cooldown Progress (Spell)"
  },
  item = {
    name = L["Item"],
    default = "Cooldown Progress (Item)"
  },
  unit = {
    name = L["Player/Unit Info"],
    default = "Health"
  },
  addons = {
    name = L["Other Addons"],
    default = "GTFO"
  },
  combatlog = {
    name = L["Combat Log"],
    default = "Combat Log",
  },
  event = {
    name = L["Other Events"],
    default = "Chat Message"
  },
  custom = {
    name = L["Custom"],
  }
}

local GetNameAndIconForSpellName = function(trigger)
  if type(trigger.spellName) == "table" then return end
  local name, _, icon = GetSpellInfo(trigger.spellName or 0)
  return name, icon
end

Private.event_prototypes = {
  ["Unit Characteristics"] = {
    type = "unit",
    events = function(trigger)
      local unit = trigger.unit
      local result = {}
      AddUnitEventForEvents(result, unit, "UNIT_LEVEL")
      AddUnitEventForEvents(result, unit, "UNIT_FACTION")
      AddUnitEventForEvents(result, unit, "UNIT_NAME_UPDATE")
      AddUnitEventForEvents(result, unit, "UNIT_FLAGS")
      AddUnitEventForEvents(result, unit, "PLAYER_FLAGS_CHANGED")
      if trigger.use_inRange then
        AddUnitEventForEvents(result, unit, "UNIT_IN_RANGE_UPDATE")
      end
      return result;
    end,
    internal_events = function(trigger)
      local unit = trigger.unit
      local result = {}
      AddUnitChangeInternalEvents(unit, result, nil, trigger.use_unitisunit and trigger.unitisunit or nil)
      AddUnitRoleChangeInternalEvents(unit, result)
      AddUnitSpecChangeInternalEvents(unit, result)
      return result
    end,
    loadFunc = function(trigger)
      AddWatchedUnits(trigger.unit, nil, trigger.use_unitisunit and trigger.unitisunit or nil)
      if trigger.use_inRange then
        WeakAuras.WatchForPlayerInRange()
      end
    end,
    force_events = unitHelperFunctions.UnitChangedForceEvents,
    name = L["Unit Characteristics"],
    init = function(trigger)
      trigger.unit = trigger.unit or "target";
      local ret = [=[
        unit = string.lower(unit)
        local smart = %s
        local extraUnit = %q;
        local name, realm = WeakAuras.UnitNameWithRealm(unit)
      ]=];

      ret = ret .. unitHelperFunctions.SpecificUnitCheck(trigger)

      return ret:format(trigger.unit == "group" and "true" or "false", trigger.unitisunit or "");
    end,
    statesParameter = "unit",
    args = {
      {
        name = "unit",
        required = true,
        display = L["Unit"],
        type = "unit",
        init = "arg",
        values = "actual_unit_types_cast",
        desc = Private.actual_unit_types_cast_tooltip,
        test = "true",
        store = true,
        reloadOptions = true,
      },
      {
        name = "unitisunit",
        display = L["Unit is Unit"],
        type = "unit",
        init = "UnitIsUnit(unit, extraUnit)",
        values = function(trigger)
          if Private.multiUnitUnits[trigger.unit] then
            return Private.actual_unit_types
          else
            return Private.actual_unit_types_with_specific
          end
        end,
        test = "unitisunit",
        desc = function() return L["Can be used for e.g. checking if \"boss1target\" is the same as \"player\"."] end,
      },
      {
        name = "name",
        display = L["Name"],
        type = "string",
        store = true,
        hidden = true,
        test = "true"
      },
      {
        name = "realm",
        display = L["Realm"],
        type = "string",
        store = true,
        hidden = true,
        test = "true"
      },
      {
        name = "namerealm",
        display = L["Unit Name/Realm"],
        desc = constants.nameRealmFilterDesc,
        type = "string",
        multiline = true,
        preamble = "local nameRealmChecker = Private.ExecEnv.ParseNameCheck(%q)",
        test = "nameRealmChecker:Check(name, realm)",
        conditionType = "string",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseNameCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.name, state.realm)
        end,
        operator_types = "none",
      },
      {
        name = "class",
        display = L["Class"],
        type = "select",
        init = "select(2, UnitClass(unit))",
        values = "class_types",
        store = true,
        conditionType = "select"
      },
      {
        name = "specId",
        display = L["Specialization"],
        type = "multiselect",
        init = "WeakAuras.SpecForUnit(unit)",
        values = "spec_types_all",
        store = true,
        conditionType = "select",
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party" or trigger.unit == "player"
        end,
        desc = L["Requires syncing the specialization via LibGroupTalents."],
      },
      {
        name = "classification",
        display = L["Classification"],
        type = "multiselect",
        init = "UnitClassification(unit)",
        values = "classification_types",
        store = true,
        conditionType = "select"
      },
      {
        name = "creatureTypeIndex",
        display = L["Creature Type"],
        type = "multiselect",
        init = "Private.ExecEnv.creature_type_name_to_id[UnitCreatureType(unit or '') or 0]",
        values = "creature_type_types",
        store = true,
        sorted = true,
        conditionType = "select",
      },
      {
        name = "creatureType",
        display = L["Creature Type Name"],
        init = "UnitCreatureType(unit)",
        store = true,
        test = "true",
        hidden = true,
      },
      {
        name = "creatureFamilyIndex",
        display = L["Creature Family"],
        type = "multiselect",
        init = "Private.ExecEnv.creature_family_name_to_id[UnitCreatureFamily(unit or '') or 0]",
        values = "creature_family_types",
        store = true,
        sorted = true,
        conditionType = "select",
      },
      {
        name = "creatureFamily",
        display = L["Creature Family Name"],
        init = "UnitCreatureFamily(unit)",
        store = true,
        test = "true",
        hidden = true,
      },
      {
        name = "role",
        display = L["Spec Role"],
        type = "select",
        init = "WeakAuras.LGT:GetUnitRole(unit)",
        values = "role_types",
        store = true,
        conditionType = "select",
      },
      {
        name = "raid_role",
        display = L["Raid Role"],
        type = "select",
        init = "WeakAuras.UnitRaidRole(unit)",
        values = "raid_role_types",
        store = true,
        conditionType = "select",
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end
      },
      {
        name = "raidMarkIndex",
        display = L["Raid Mark"],
        type = "multiselect",
        values = "raid_mark_check_type",
        store = true,
        conditionType = "select",
        init = "GetRaidTargetIndex(unit) or 0"
      },
      {
        name = "raidMark",
        display = L["Raid Mark Icon"],
        store = true,
        hidden = true,
        test = "true",
        init = "raidMarkIndex > 0 and '{rt'..raidMarkIndex..'}' or ''"
      },
      {
        name = "dead",
        display = L["Dead"],
        type = "tristate",
        width = WeakAuras.doubleWidth,
        init = "UnitIsDeadOrGhost(unit)",
        store = true,
        conditionType = "bool",
      },
      {
        name = "ignoreSelf",
        display = L["Ignore Self"],
        type = "toggle",
        width = WeakAuras.doubleWidth,
        enable = function(trigger)
          return trigger.unit == "nameplate" or trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
        init = "not UnitIsUnit(\"player\", unit)"
      },
      {
        name = "ignoreDisconnected",
        display = L["Ignore Disconnected"],
        type = "toggle",
        width = WeakAuras.doubleWidth,
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
        init = "UnitIsConnected(unit)"
      },
      {
        name = "inRange",
        display = L["In Range"],
        desc = L["Uses UnitInRange() to check if in range. Matches default raid frames out of range behavior, which is between 25 to 40 yards depending on your class and spec."],
        type = "toggle",
        width = WeakAuras.doubleWidth,
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
        init = "Private.ExecEnv.UnitInRangeFixed(unit)"
      },
      {
        name = "hostility",
        display = L["Hostility"],
        type = "select",
        init = "WeakAuras.GetPlayerReaction(unit)",
        values = "hostility_types",
        store = true,
        conditionType = "select",
      },
      {
        name = "character",
        display = L["Character Type"],
        type = "select",
        init = "UnitIsPlayer(unit) and 'player' or 'npc'",
        values = "character_types",
        store = true,
        conditionType = "select"
      },
      {
        name = "level",
        display = L["Level"],
        type = "number",
        init = "UnitLevel(unit)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "npcId",
        display = L["Npc ID"],
        type = "string",
        multiline = true,
        store = true,
        init = "tostring(tonumber(string.sub(UnitGUID(unit) or '', 8, 12), 16) or '')",
        conditionType = "string",
        preamble = "local npcIdChecker = Private.ExecEnv.ParseStringCheck(%q)",
        test = "npcIdChecker:Check(npcId)",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseStringCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.npcId)
        end,
        operator_types = "none",
        desc = L["Supports multiple entries, separated by commas. Prefix with '-' for negation."]
      },
      {
        name = "attackable",
        display = L["Attackable"],
        type = "tristate",
        init = "UnitCanAttack('player', unit)",
        store = true,
        conditionType = "bool"
      },
      {
        name = "inCombat",
        display = L["In Combat"],
        type = "tristate",
        init = "UnitAffectingCombat(unit)",
        store = true,
        conditionType = "bool"
      },
      {
        name = "afk",
        display = L["Afk"],
        type = "tristate",
        init = "UnitIsAFK(unit)",
        store = true,
        conditionType = "bool"
      },
      {
        name = "dnd",
        display = L["Do Not Disturb"],
        type = "tristate",
        init = "UnitIsDND(unit)",
        store = true,
        conditionType = "bool"
      },
      {
        hidden = true,
        test = "WeakAuras.UnitExistsFixed(unit, smart) and specificUnitCheck"
      }
    },
    automaticrequired = true,
    progressType = "none"
  },
  ["Faction Reputation"] = {
    type = "unit",
    progressType = "static",
    events = {
      ["events"] = {
        "UPDATE_FACTION",
      }
    },
    internal_events = {"WA_DELAYED_PLAYER_ENTERING_WORLD"},
    force_events = "UPDATE_FACTION",
    name = L["Faction Reputation"],
    statesParameter = "one",
    automaticrequired = true,
    init = function(trigger)
      local ret = [=[
        local useWatched = %s
        local factionID = useWatched and Private.ExecEnv.GetWatchedFactionId() or %q
        local minValue, maxValue, currentValue
        local factionData = Private.ExecEnv.GetFactionDataByID(factionID)
        if not factionData then return end;

        local name, description = factionData.name, factionData.description
        local standingID = factionData.reaction
        local hasRep = factionData.isHeaderWithRep
        local barMin, barMax, barValue = factionData.currentReactionThreshold, factionData.nextReactionThreshold, factionData.currentStanding
        local atWarWith, canToggleAtWar, isHeader, isCollapsed, isWatched, isChild = factionData.atWarWith, factionData. canToggleAtWar, factionData.isHeader, factionData.isCollapsed, factionData.isWatched, factionData.isChild
        minValue, maxValue, currentValue = barMin, barMax, barValue
        local standing
        if tonumber(standingID) then
           standing = GetText("FACTION_STANDING_LABEL"..standingID, UnitSex("player"))
        end
        local isCapped = standingID == 8 and currentValue >= 42999
      ]=]
      return ret:format(trigger.use_watched and "true" or "false", trigger.factionID or 0)
    end,
    args = {
      {
        name = "progressType",
        hidden = true,
        init = "'static'",
        store = true,
        test = "true"
      },
      {
        name = "watched",
        display = L["Use Watched Faction"],
        type = "toggle",
        test = "true",
        reloadOptions = true,
      },
      {
        name = "factionID",
        display = L["Faction"],
        required = true,
        type = "select",
        itemControl = "Dropdown-Currency",
        values = Private.GetReputations,
        headers = Private.GetReputationsHeaders,
        sorted = true,
        sortOrder = function()
          local sorted = Private.GetReputationsSorted()
          local sortOrder = {}
          for key, value in pairs(Private.GetReputations()) do
            tinsert(sortOrder, key)
          end
          table.sort(sortOrder, function(aKey, bKey)
            local aValue = sorted[aKey]
            local bValue = sorted[bKey]
            return aValue < bValue
          end)
          return sortOrder
        end,
        conditionType = "select",
        enable = function(trigger)
          return not trigger.use_watched
        end,
        reloadOptions = true,
        test = "true",
      },
      {
        name = "name",
        display = L["Faction Name"],
        type = "string",
        store = true,
        hidden = "true",
        init = "name",
        test = "true"
      },
      {
        name = "value",
        display = L["Reputation"],
        type = "number",
        store = true,
        init = [[currentValue - minValue]],
        conditionType = "number",
        progressTotal = "total",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "total",
        display = L["Total Reputation"],
        type = "number",
        store = true,
        init = [[maxValue - minValue]],
        conditionType = "number",
        noProgressSource = true,
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "percentRep",
        display = L["Reputation (%)"],
        type = "number",
        init = "total ~= 0 and (value / total) * 100 or nil",
        store = true,
        conditionType = "number",
        noProgressSource = true,
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "standing",
        display = L["Standing"],
        type = "string",
        init = "standing",
        store = true,
        hidden = "true",
        test = "true"
      },
      {
        name = "standingId",
        display = L["Standing"],
        type = "select",
        values = function()
          local ret = {}
          for i = 1, 8 do
            ret[i] = GetText("FACTION_STANDING_LABEL"..i, UnitSex("player"))
          end
          return ret
        end,
        init = "standingID",
        store = true,
        conditionType = "select",
      },
      {
        name = "capped",
        display = L["Capped"],
        type = "tristate",
        init = "isCapped",
        conditionType = "bool",
        store = true,
      },
      {
        name = "atWar",
        display = L["At War"],
        type = "tristate",
        init = "atWarWith",
        conditionType = "bool",
        store = true,
      },
    }
  },
  ["Experience"] = {
    type = "unit",
    progressType = "static",
    events = {
      ["events"] = {
        "PLAYER_XP_UPDATE",
      }
    },
    internal_events = {"WA_DELAYED_PLAYER_ENTERING_WORLD"},
    force_events = "PLAYER_XP_UPDATE",
    name = L["Player Experience"],
    init = function(trigger)
      return ""
    end,
    statesParameter = "one",
    args = {
      {
        name = "level",
        display = L["Level"],
        required = false,
        type = "number",
        store = true,
        init = [[UnitLevel("player")]],
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "currentXP",
        display = L["Current Experience"],
        type = "number",
        store = true,
        init = [[UnitXP("player")]],
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        progressTotal = "totalXP"
      },
      {
        name = "totalXP",
        display = L["Total Experience"],
        type = "number",
        store = true,
        init = [[UnitXPMax("player")]],
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "value",
        type = "number",
        store = true,
        init = "currentXP",
        hidden = true,
        test = "true",
      },
      {
        name = "total",
        type = "number",
        store = true,
        init = "totalXP",
        hidden = true,
        test = "true",
      },
      {
        name = "progressType",
        hidden = true,
        init = "'static'",
        store = true,
        test = "true"
      },
      {
        name = "percentXP",
        display = L["Experience (%)"],
        type = "number",
        init = "total ~= 0 and (value / total) * 100 or nil",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        type = "header",
        name = "restedExperienceHeader",
        display = L["Rested Experience"],
      },
      {
        name = "showRested",
        display = L["Show Rested Overlay"],
        type = "toggle",
        test = "true",
        reloadOptions = true,
      },
      {
        name = "restedXP",
        display = L["Rested Experience"],
        init = [[GetXPExhaustion() or 0]],
        type = "number",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "percentrested",
        display = L["Rested Experience (%)"],
        init = "total ~= 0 and (restedXP / total) * 100 or nil",
        type = "number",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
    },
    overlayFuncs = {
      {
        name = L["Rested"],
        func = function(trigger, state)
          return "forward", state.restedXP
        end,
        enable = function(trigger)
          return trigger.use_showRested
        end
      },
    },
    automaticrequired = true
  },
  ["Health"] = {
    type = "unit",
    includePets = "true",
    progressType = "static",
    events = function(trigger)
      local unit = trigger.unit
      local result = {}
      AddUnitEventForEvents(result, unit, "UNIT_HEALTH")
      AddUnitEventForEvents(result, unit, "UNIT_MAXHEALTH")
      AddUnitEventForEvents(result, unit, "UNIT_NAME_UPDATE")
      if trigger.use_ignoreDead or trigger.use_ignoreDisconnected then
        AddUnitEventForEvents(result, unit, "UNIT_FLAGS")
      end
      if trigger.use_showAbsorb then
        AddUnitEventForEvents(result, unit, "UNIT_ABSORB_AMOUNT_CHANGED")
      end
      if trigger.use_showHealAbsorb then
        AddUnitEventForEvents(result, unit, "UNIT_HEAL_ABSORB_AMOUNT_CHANGED")
      end
      if trigger.use_inRange then
        AddUnitEventForEvents(result, unit, "UNIT_IN_RANGE_UPDATE")
      end
      return result
    end,
    internal_events = function(trigger)
      local unit = trigger.unit
      local result = {}
      local includePets = trigger.use_includePets == true and trigger.includePets or nil
      AddUnitChangeInternalEvents(unit, result, includePets)
      if includePets ~= "PetsOnly" then
        AddUnitRoleChangeInternalEvents(unit, result)
      end
      AddUnitSpecChangeInternalEvents(unit, result)
      return result
    end,
    loadFunc = function(trigger)
      local includePets = trigger.use_includePets == true and trigger.includePets or nil
      AddWatchedUnits(trigger.unit, includePets)
      if trigger.use_inRange then
        WeakAuras.WatchForPlayerInRange()
      end
    end,
    force_events = unitHelperFunctions.UnitChangedForceEventsWithPets,
    name = L["Health"],
    init = function(trigger)
      trigger.unit = trigger.unit or "player";
      local ret = [=[
        unit = string.lower(unit)
        local name, realm = WeakAuras.UnitNameWithRealm(unit)
        local smart = %s
      ]=];

      ret = ret .. unitHelperFunctions.SpecificUnitCheck(trigger)

      return ret:format(trigger.unit == "group" and "true" or "false");
    end,
    statesParameter = "unit",
    args = {
      {
        name = "unit",
        required = true,
        display = L["Unit"],
        type = "unit",
        init = "arg",
        values = "actual_unit_types_cast",
        desc = Private.actual_unit_types_cast_tooltip,
        test = "true",
        store = true
      },
      {
        name = "health",
        display = L["Health"],
        type = "number",
        init = "UnitHealth(unit)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        progressTotal = "maxhealth",
        formatter = "BigNumber",
      },
      {
        name = "value",
        hidden = true,
        init = "health",
        store = true,
        test = "true"
      },
      {
        name = "total",
        hidden = true,
        init = "UnitHealthMax(unit)",
        store = true,
        test = "true"
      },
      {
        name = "progressType",
        hidden = true,
        init = "'static'",
        store = true,
        test = "true"
      },
      {
        name = "percenthealth",
        display = L["Health (%)"],
        type = "number",
        init = "total ~= 0 and (value / total) * 100 or nil",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "deficit",
        display = L["Health Deficit"],
        type = "number",
        init = "total - value",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        progressTotal = "total",
        formatter = "BigNumber",
      },
      {
        name = "maxhealth",
        display = WeakAuras.newFeatureString .. L["Max Health"],
        type = "number",
        init = "total",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "BigNumber",
      },
      {
        name = "showAbsorb",
        display = L["Show Absorb"],
        type = "toggle",
        test = "true",
        reloadOptions = true
      },
      {
        name = "absorbMode",
        display = L["Absorb Display"],
        type = "select",
        test = "true",
        values = "absorb_modes",
        required = true,
        enable = function(trigger) return trigger.use_showAbsorb end
      },
      {
        name = "absorb",
        type = "number",
        display = L["Absorb"],
        init = "UnitGetTotalAbsorbs(unit)",
        store = true,
        conditionType = "number",
        enable = function(trigger) return trigger.use_showAbsorb end
      },
      {
        type = "header",
        name = "unitCharacteristicsHeader",
        display = L["Unit Characteristics"],
      },
      {
        name = "name",
        display = L["Unit Name"],
        type = "string",
        store = true,
        hidden = true,
        test = "true"
      },
      {
        name = "realm",
        display = L["Realm"],
        type = "string",
        store = true,
        hidden = true,
        test = "true"
      },
      {
        name = "namerealm",
        display = L["Unit Name/Realm"],
        type = "string",
        multiline = true,
        preamble = "local nameRealmChecker = Private.ExecEnv.ParseNameCheck(%q)",
        test = "nameRealmChecker:Check(name, realm)",
        conditionType = "string",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseNameCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.name, state.realm)
        end,
        operator_types = "none",
        desc = constants.nameRealmFilterDesc,
      },
      {
        name = "npcId",
        display = L["Npc ID"],
        type = "string",
        multiline = true,
        store = true,
        init = "tostring(tonumber(string.sub(UnitGUID(unit) or '', 8, 12), 16) or '')",
        conditionType = "string",
        preamble = "local npcIdChecker = Private.ExecEnv.ParseStringCheck(%q)",
        test = "npcIdChecker:Check(npcId)",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseStringCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.npcId)
        end,
        operator_types = "none",
        desc = L["Supports multiple entries, separated by commas. Prefix with '-' for negation."]
      },
      {
        name = "class",
        display = L["Class"],
        type = "select",
        init = "select(2, UnitClass(unit))",
        values = "class_types",
        store = true,
        conditionType = "select"
      },
      {
        name = "specId",
        display = L["Specialization"],
        type = "multiselect",
        init = "WeakAuras.SpecForUnit(unit)",
        values = "spec_types_all",
        store = true,
        conditionType = "select",
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party" or trigger.unit == "player"
        end,
        desc = L["Requires syncing the specialization via LibGroupTalents."],
      },
      {
        name = "role",
        display = L["Spec Role"],
        type = "select",
        init = "WeakAuras.LGT:GetUnitRole(unit)",
        values = "role_types",
        store = true,
        conditionType = "select",
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party" or trigger.unit == "player"
        end
      },
      {
        name = "raid_role",
        display = L["Raid Role"],
        type = "select",
        init = "WeakAuras.UnitRaidRole(unit)",
        values = "raid_role_types",
        store = true,
        conditionType = "select",
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end
      },
      {
        name = "raidMarkIndex",
        display = L["Raid Mark"],
        type = "multiselect",
        values = "raid_mark_check_type",
        store = true,
        conditionType = "select",
        init = "GetRaidTargetIndex(unit) or 0"
      },
      {
        name = "raidMark",
        display = L["Raid Mark Icon"],
        store = true,
        hidden = true,
        test = "true",
        init = "raidMarkIndex > 0 and '{rt'..raidMarkIndex..'}' or ''"
      },
      {
        type = "header",
        name = "miscellaneousHeader",
        display = L["Miscellaneous"],
        enable = function(trigger)
          return trigger.unit == "nameplate" or trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
      },
      {
        name = "includePets",
        display = L["Include Pets"],
        type = "select",
        values = "include_pets_types",
        width = WeakAuras.normalWidth,
        test = "true",
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end
      },
      {
        name = "ignoreSelf",
        display = L["Ignore Self"],
        type = "toggle",
        width = WeakAuras.doubleWidth,
        enable = function(trigger)
          return trigger.unit == "nameplate" or trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
        init = "not UnitIsUnit(\"player\", unit)"
      },
      {
        name = "ignoreDead",
        display = L["Ignore Dead"],
        type = "toggle",
        width = WeakAuras.doubleWidth,
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
        init = "not UnitIsDeadOrGhost(unit)"
      },
      {
        name = "ignoreDisconnected",
        display = L["Ignore Disconnected"],
        type = "toggle",
        width = WeakAuras.doubleWidth,
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
        init = "UnitIsConnected(unit)"
      },
      {
        name = "inRange",
        display = L["In Range"],
        desc = L["Uses UnitInRange() to check if in range. Matches default raid frames out of range behavior, which is between 25 to 40 yards depending on your class and spec."],
        type = "toggle",
        width = WeakAuras.doubleWidth,
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
        init = "Private.ExecEnv.UnitInRangeFixed(unit)"
      },
      {
        name = "nameplateType",
        display = L["Hostility"],
        type = "select",
        init = "WeakAuras.GetPlayerReaction(unit)",
        values = "hostility_types",
        conditionType = "select",
        store = true,
      },
      {
        name = "name",
        hidden = true,
        init = "UnitName(unit)",
        test = "true"
      },
      {
        hidden = true,
        test = "WeakAuras.UnitExistsFixed(unit, smart) and specificUnitCheck"
      }
    },
    overlayFuncs = {
      {
        name = L["Absorb"],
        func = function(trigger, state)
          local absorb = state.absorb
          if (trigger.absorbMode == "OVERLAY_FROM_START") then
            return 0, absorb;
          else
            return "forward", absorb;
          end
        end,
        enable = function(trigger)
          return trigger.use_showAbsorb;
        end
      },
      {
        name = L["Heal Absorb"],
        func = function(trigger, state)
          local healabsorb = state.healabsorb
          if (trigger.absorbHealMode == "OVERLAY_FROM_START") then
            return 0, healabsorb;
          else
            return "forward", healabsorb;
          end
        end,
        enable = function(trigger)
          return trigger.use_showHealAbsorb;
        end
      },
    },
    automaticrequired = true
  },
  ["Power"] = {
    type = "unit",
    progressType = "static",
    events = function(trigger)
      local unit = trigger.unit
      local result = {}
      local powerType = trigger.use_powertype and trigger.powertype

      if trigger.powertype == 4 then
        local _, class = UnitClass("player")
        AddUnitEventForEvents(result, unit, "UNIT_COMBO_POINTS")
        AddUnitEventForEvents(result, unit, "UNIT_TARGET")
        if class == "DRUID" then
          AddUnitEventForEvents(result, unit, "UNIT_MODEL_CHANGED")
        end
      -- For units not in multiUnitUnits, add FRAME_UPDATE to ensure smooth power updates
      elseif unit and not Private.multiUnitUnits[unit] then
        if not result.events then
          result.events = {}
        end
        tinsert(result.events, "FRAME_UPDATE")
      else
        AddUnitEventForPowerEvents(result, unit, powerType)
      end

      -- The api for spell power costs is not meant to be for other units
      if trigger.use_showCost and unit == "player" then
        AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_START")
        AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_STOP")
        AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_FAILED")
        AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_SUCCEEDED")
      end

      -- Register shared events unless we're in the FRAME_UPDATE case
      if (not (unit and not Private.multiUnitUnits[unit])) or (trigger.powertype == 4) then
        AddUnitEventForEvents(result, unit, "UNIT_DISPLAYPOWER")
        AddUnitEventForEvents(result, unit, "UNIT_NAME_UPDATE")
        if trigger.use_ignoreDead or trigger.use_ignoreDisconnected then
          AddUnitEventForEvents(result, unit, "UNIT_FLAGS")
        end
        if trigger.use_inRange then
          AddUnitEventForEvents(result, unit, "UNIT_IN_RANGE_UPDATE")
        end
      end
      return result;
    end,
    internal_events = function(trigger)
      local unit = trigger.unit
      local result = {}
      local includePets = trigger.use_includePets == true and trigger.includePets or nil
      AddUnitChangeInternalEvents(unit, result, includePets)
      if includePets ~= "PetsOnly" then
        AddUnitRoleChangeInternalEvents(unit, result)
      end
      AddUnitSpecChangeInternalEvents(unit, result)
      if trigger.use_showCost and trigger.unit == "player" then
        tinsert(result, "WA_UNIT_QUEUED_SPELL_CHANGED");
      end
      return result
    end,
    loadFunc = function(trigger)
      if trigger.use_showCost and trigger.unit == "player" then
        WeakAuras.WatchForQueuedSpell()
      end
      local includePets = trigger.use_includePets == true and trigger.includePets or nil
      AddWatchedUnits(trigger.unit, includePets)
      if trigger.use_inRange then
        WeakAuras.WatchForPlayerInRange()
      end
    end,
    force_events = unitHelperFunctions.UnitChangedForceEventsWithPets,
    name = L["Power"],
    init = function(trigger)
      trigger.unit = trigger.unit or "player";
      local ret = {}
      table.insert(ret, ([=[
        unit = string.lower(unit)
        local name, realm = WeakAuras.UnitNameWithRealm(unit)
        local smart = %s
        local powerType = %s
        local unitPowerType = UnitPowerType(unit);
        local powerTypeToCheck = powerType or unitPowerType;
      ]=]):format(trigger.unit == "group" and "true" or "false", trigger.use_powertype and trigger.powertype or "nil"))
      local powerType = trigger.use_powertype and trigger.powertype or nil
      -- Combo Points
      if powerType == 4 then
        local _, class = UnitClass("player")
        table.insert(ret, [[
          local comboUnit = UnitInVehicle(unit) and 'vehicle' or unit
          local power = GetComboPoints(comboUnit, 'target')
          local total = MAX_COMBO_POINTS
        ]])
        if class == "ROGUE" then
          table.insert(ret, [[
            unitPowerType = 4
          ]])
        elseif class == "DRUID" then
          local formName = GetSpellInfo(768) or ""
          table.insert(ret, ([[
            local index = GetShapeshiftForm() or 0
            local form = index > 0 and select(2, GetShapeshiftFormInfo(index)) == %q
            if form or comboUnit == 'vehicle' then
              unitPowerType = 4
            end
          ]]):format(formName))
        end
      -- Happiness
      elseif powerType == 27 then
        table.insert(ret, [[
          local power = UnitPower(unit, 4)
          local total = math.max(1, UnitPowerMax(unit, 4))
        ]])
      else
        table.insert(ret, [[
          local power = UnitPower(unit, powerType)
          local total = math.max(1, UnitPowerMax(unit, powerType))
        ]])
      end

      table.insert(ret, unitHelperFunctions.SpecificUnitCheck(trigger))

      local canEnableShowCost = (not trigger.use_powertype) and trigger.unit == "player";
      if (canEnableShowCost and trigger.use_showCost) then
        table.insert(ret, [[
          if (event == "UNIT_DISPLAYPOWER") then
            local cost = WeakAuras.GetSpellCost(powerTypeToCheck)
            if state.cost ~= cost then
              state.cost = cost
              state.changed = true
            end
          elseif ( (event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_FAILED" or event == "UNIT_SPELLCAST_SUCCEEDED") and unit == "player") or event == "WA_UNIT_QUEUED_SPELL_CHANGED" then
            local cost = WeakAuras.GetSpellCost(powerTypeToCheck)
            if state.cost ~= cost then
              state.cost = cost
              state.changed = true
            end
          end
        ]])
      end

      return table.concat(ret)
    end,
    statesParameter = "unit",
    args = {
      {
        name = "unit",
        required = true,
        display = L["Unit"],
        type = "unit",
        init = "arg",
        values = "actual_unit_types_cast",
        desc = Private.actual_unit_types_cast_tooltip,
        test = "true",
        store = true
      },
      {
        name = "powertype",
        display = L["Power Type"],
        type = "select",
        values = function(trigger)
          if trigger and trigger.unit ~= "player" then
            return Private.power_types
          else
            return Private.power_types_player
          end
        end,
        init = "powerTypeToCheck",
        test = "true",
        store = true,
        conditionType = "select",
        reloadOptions = true
      },
      {
        name = "requirePowerType",
        display = L["Only if Primary"],
        type = "toggle",
        test = "unitPowerType == powerType",
        enable = function(trigger)
          return trigger.use_powertype
        end,
      },
      {
        name = "showCost",
        display = L["Overlay Cost of Casts"],
        type = "toggle",
        test = "true",
        enable = function(trigger)
          return (not trigger.use_powertype) and trigger.unit == "player";
        end,
        reloadOptions = true
      },
      {
        name = "power",
        display = L["Power"],
        type = "number",
        init = "power",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        progressTotal = "total"
      },
      {
        name = "value",
        hidden = true,
        init = "power",
        store = true,
        test = "true"
      },
      {
        name = "total",
        hidden = true,
        init = "total",
        store = true,
        test = "true"
      },
      {
        name = "stacks",
        hidden = true,
        init = "power",
        store = true,
        test = "true"
      },
      {
        name = "progressType",
        hidden = true,
        init = "'static'",
        store = true,
        test = "true"
      },
      {
        name = "percentpower",
        display = L["Power (%)"],
        type = "number",
        init = "total ~= 0 and (value / total) * 100 or nil",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "deficit",
        display = L["Power Deficit"],
        type = "number",
        init = "total - value",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        progressTotal = "total"
      },
      {
        name = "maxpower",
        display = WeakAuras.newFeatureString .. L["Max Power"],
        type = "number",
        init = "total",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "name",
        display = L["Unit Name"],
        type = "string",
        store = true,
        hidden = true,
        test = "true"
      },
      {
        name = "realm",
        display = L["Realm"],
        type = "string",
        store = true,
        hidden = true,
        test = "true"
      },
      {
        type = "header",
        name = "unitCharacteristicsHeader",
        display = L["Unit Characteristics"],
      },
      {
        name = "namerealm",
        display = L["Unit Name/Realm"],
        type = "string",
        multiline = true,
        preamble = "local nameRealmChecker = Private.ExecEnv.ParseNameCheck(%q)",
        test = "nameRealmChecker:Check(name, realm)",
        conditionType = "string",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseNameCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.name, state.realm)
        end,
        operator_types = "none",
        desc = constants.nameRealmFilterDesc,
      },
      {
        name = "npcId",
        display = L["Npc ID"],
        type = "string",
        multiline = true,
        store = true,
        init = "tostring(tonumber(string.sub(UnitGUID(unit) or '', 8, 12), 16) or '')",
        conditionType = "string",
        preamble = "local npcIdChecker = Private.ExecEnv.ParseStringCheck(%q)",
        test = "npcIdChecker:Check(npcId)",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseStringCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.npcId)
        end,
        operator_types = "none",
        desc = L["Supports multiple entries, separated by commas. Prefix with '-' for negation."],
        enable = function(trigger)
          return trigger.powertype ~= 4
        end
      },
      {
        name = "class",
        display = L["Class"],
        type = "select",
        init = "select(2, UnitClass(unit))",
        values = "class_types",
        store = true,
        conditionType = "select"
      },
      {
        name = "specId",
        display = L["Specialization"],
        type = "multiselect",
        init = "WeakAuras.SpecForUnit(unit)",
        values = "spec_types_all",
        store = true,
        conditionType = "select",
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party" or trigger.unit == "player"
        end,
        desc = L["Requires syncing the specialization via LibGroupTalents."],
      },
      {
        name = "role",
        display = L["Spec Role"],
        type = "select",
        init = "WeakAuras.LGT:GetUnitRole(unit)",
        values = "role_types",
        store = true,
        conditionType = "select",
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party" or trigger.unit == "player"
        end
      },
      {
        name = "raid_role",
        display = L["Raid Role"],
        type = "select",
        init = "WeakAuras.UnitRaidRole(unit)",
        values = "raid_role_types",
        store = true,
        conditionType = "select",
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end
      },
      {
        name = "raidMarkIndex",
        display = L["Raid Mark"],
        type = "multiselect",
        values = "raid_mark_check_type",
        store = true,
        conditionType = "select",
        init = "GetRaidTargetIndex(unit) or 0"
      },
      {
        name = "raidMark",
        display = L["Raid Mark Icon"],
        store = true,
        hidden = true,
        test = "true",
        init = "raidMarkIndex > 0 and '{rt'..raidMarkIndex..'}' or ''"
      },
      {
        type = "header",
        name = "miscellaneousHeader",
        display = L["Miscellaneous"],
        enable = function(trigger)
          return trigger.unit == "nameplate" or trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
      },
      {
        name = "includePets",
        display = L["Include Pets"],
        type = "select",
        values = "include_pets_types",
        width = WeakAuras.normalWidth,
        test = "true",
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end
      },
      {
        name = "ignoreSelf",
        display = L["Ignore Self"],
        type = "toggle",
        width = WeakAuras.doubleWidth,
        enable = function(trigger)
          return trigger.unit == "nameplate" or trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
        init = "not UnitIsUnit(\"player\", unit)"
      },
      {
        name = "ignoreDead",
        display = L["Ignore Dead"],
        type = "toggle",
        width = WeakAuras.doubleWidth,
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
        init = "not UnitIsDeadOrGhost(unit)"
      },
      {
        name = "ignoreDisconnected",
        display = L["Ignore Disconnected"],
        type = "toggle",
        width = WeakAuras.doubleWidth,
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
        init = "UnitIsConnected(unit)"
      },
      {
        name = "inRange",
        display = L["In Range"],
        desc = L["Uses UnitInRange() to check if in range. Matches default raid frames out of range behavior, which is between 25 to 40 yards depending on your class and spec."],
        type = "toggle",
        width = WeakAuras.doubleWidth,
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
        init = "Private.ExecEnv.UnitInRangeFixed(unit)"
      },
      {
        name = "nameplateType",
        display = L["Hostility"],
        type = "select",
        init = "WeakAuras.GetPlayerReaction(unit)",
        values = "hostility_types",
        store = true,
        conditionType = "select",
        enable = function(trigger)
          return trigger.powertype ~= 4
        end
      },
      {
        hidden = true,
        test = "WeakAuras.UnitExistsFixed(unit, smart) and specificUnitCheck"
      }
    },
    overlayFuncs = {
      {
        name = L["Spell Cost"],
        func = function(trigger, state)
          return "back", type(state.cost) == "number" and state.cost;
        end,
        enable = function(trigger)
          return trigger.use_showCost and (not trigger.use_powertype) and trigger.unit == "player";
        end
      },
    },
    automaticrequired = true
  },
  ["Combat Log"] = {
    type = "combatlog",
    events = {
      ["events"] = {"COMBAT_LOG_EVENT_UNFILTERED"}
    },
    init = function(trigger)
      local ret = [[
        local use_cloneId = %s;
      ]];
      return ret:format(trigger.use_cloneId and "true" or "false");
    end,
    name = L["Combat Log"],
    statesParameter = "all",
    args = {
      {}, -- timestamp ignored with _ argument
      {}, -- messageType ignored with _ argument (it is checked before the dynamic function)
      -- {}, -- we don't have hideCaster
      {
        type = "header",
        name = "sourceHeader",
        display = L["Source Info"],
      },
      {
        name = "sourceGUID",
        init = "arg",
        hidden = "true",
        test = "true",
        store = true,
        display = L["Source GUID"],
        formatter = "guid",
        formatterArgs = { color = "class" }
      },
      {
        name = "sourceUnit",
        display = L["Source Unit"],
        type = "unit",
        test = "(sourceGUID or '') == (UnitGUID(%q) or '') and sourceGUID",
        values = "actual_unit_types_with_specific",
        enable = function(trigger)
          return not (trigger.subeventPrefix == "ENVIRONMENTAL")
        end,
        store = true,
        conditionType = "select",
        conditionTest = function(state, needle, op)
          return state and state.show and ((state.sourceGUID or '') == (UnitGUID(needle) or '')) == (op == "==")
        end
      },
      {
        name = "sourceName",
        display = L["Source Name"],
        type = "string",
        multiline = true,
        init = "arg",
        store = true,
        conditionType = "string",
        preamble = "local sourceNameChecker = Private.ExecEnv.ParseStringCheck(%q)",
        test = "sourceNameChecker:Check(sourceName)",
        desc = L["Supports multiple entries, separated by commas. Prefix with '-' for negation."],
      },
      {
        name = "sourceNpcId",
        display = L["Source NPC Id"],
        type = "string",
        multiline = true,
        init = "tostring(tonumber(string.sub(sourceGUID or '', 8, 12), 16) or '')",
        store = true,
        conditionType = "string",
        preamble = "local sourceNpcIdChecker = Private.ExecEnv.ParseStringCheck(%q)",
        test = "sourceNpcIdChecker:Check(sourceNpcId)",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseStringCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.sourceNpcId)
        end,
        operator_types = "none",
        desc = L["Supports multiple entries, separated by commas. Prefix with '-' for negation."],
        enable = function(trigger)
          return not (trigger.subeventPrefix == "ENVIRONMENTAL")
        end,
      },
      {
        name = "sourceFlags",
        display = L["Source Affiliation"],
        type = "select",
        values = "combatlog_flags_check_type",
        init = "arg",
        store = true,
        test = "Private.ExecEnv.CheckCombatLogFlags(sourceFlags, %q)",
        conditionType = "select",
        conditionTest = function(state, needle, op)
          if state and state.show then
            return Private.ExecEnv.CheckCombatLogFlags(state.sourceFlags, needle)  == (op == "==")
          end
        end
      },
      {
        name = "sourceFlags2",
        display = L["Source Reaction"],
        type = "select",
        values = "combatlog_flags_check_reaction",
        test = "Private.ExecEnv.CheckCombatLogFlagsReaction(sourceFlags, %q)",
        conditionType = "select",
        conditionTest = function(state, needle, op)
          if state and state.show then
            return Private.ExecEnv.CheckCombatLogFlagsReaction(state.sourceFlags, needle)  == (op == "==")
          end
        end
      },
      {
        name = "sourceFlags3",
        display = L["Source Object Type"],
        type = "select",
        values = "combatlog_flags_check_object_type",
        test = "Private.ExecEnv.CheckCombatLogFlagsObjectType(sourceFlags, %q)",
        conditionType = "select",
        conditionTest = function(state, needle, op)
          if state and state.show then
            return Private.ExecEnv.CheckCombatLogFlagsObjectType(state.sourceFlags, needle) == (op == "==")
          end
        end
      },
      -- we don't have sourceRaidFlags
      {
        type = "header",
        name = "destHeader",
        display = L["Destination Info"],
        enable = function(trigger)
          return not (trigger.subeventPrefix == "SPELL" and trigger.subeventSuffix == "_CAST_START");
        end,
      },
      {
        name = "destGUID",
        init = "arg",
        hidden = "true",
        test = "true",
        store = true,
        display = L["Destination GUID"],
        formatter = "guid",
        formatterArgs = { color = "class" }
      },
      {
        name = "destUnit",
        display = L["Destination Unit"],
        type = "unit",
        test = "(destGUID or '') == (UnitGUID(%q) or '') and destGUID",
        values = "actual_unit_types_with_specific",
        enable = function(trigger)
          return not (trigger.subeventPrefix == "SPELL" and trigger.subeventSuffix == "_CAST_START");
        end,
        store = true,
        conditionType = "select",
        conditionTest = function(state, needle, op)
          return state and state.show and ((state.destGUID or '') == (UnitGUID(needle) or '')) == (op == "==")
        end
      },
      {
        name = "destName",
        display = L["Destination Name"],
        type = "string",
        multiline = true,
        init = "arg",
        store = true,
        conditionType = "string",
        preamble = "local destNameChecker = Private.ExecEnv.ParseStringCheck(%q)",
        test = "destNameChecker:Check(destName)",
        desc = L["Supports multiple entries, separated by commas. Prefix with '-' for negation."],
        enable = function(trigger)
          return not (trigger.subeventPrefix == "SPELL" and trigger.subeventSuffix == "_CAST_START");
        end,
      },
      {
        name = "destNpcId",
        display = L["Destination NPC Id"],
        type = "string",
        multiline = true,
        init = "tostring(tonumber(string.sub(destGUID or '', 8, 12), 16) or '')",
        store = true,
        conditionType = "string",
        preamble = "local destNpcIdChecker = Private.ExecEnv.ParseStringCheck(%q)",
        test = "destNpcIdChecker:Check(destNpcId)",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseStringCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.destNpcId)
        end,
        operator_types = "none",
        desc = L["Supports multiple entries, separated by commas. Prefix with '-' for negation."],
        enable = function(trigger)
          return not (trigger.subeventPrefix == "SPELL" and trigger.subeventSuffix == "_CAST_START");
        end,
      },
      { -- destName ignore for SPELL_CAST_START
        enable = function(trigger)
          return (trigger.subeventPrefix == "SPELL" and trigger.subeventSuffix == "_CAST_START");
        end
      },
      {
        name = "destFlags",
        display = L["Destination Affiliation"],
        type = "select",
        values = "combatlog_flags_check_type",
        init = "arg",
        store = true,
        test = "Private.ExecEnv.CheckCombatLogFlags(destFlags, %q)",
        conditionType = "select",
        conditionTest = function(state, needle, op)
          if state and state.show then
            return Private.ExecEnv.CheckCombatLogFlags(state.destFlags, needle) == (op == "==")
          end
        end,
        enable = function(trigger)
          return not (trigger.subeventPrefix == "SPELL" and trigger.subeventSuffix == "_CAST_START");
        end,
      },
      {
        name = "destFlags2",
        display = L["Destination Reaction"],
        type = "select",
        values = "combatlog_flags_check_reaction",
        test = "Private.ExecEnv.CheckCombatLogFlagsReaction(destFlags, %q)",
        conditionType = "select",
        conditionTest = function(state, needle, op)
          if state and state.show then
            return Private.ExecEnv.CheckCombatLogFlagsReaction(state.destFlags, needle) == (op == "==")
          end
        end,
        enable = function(trigger)
          return not (trigger.subeventPrefix == "SPELL" and trigger.subeventSuffix == "_CAST_START");
        end,
      },
      {
        name = "destFlags3",
        display = L["Destination Object Type"],
        type = "select",
        values = "combatlog_flags_check_object_type",
        test = "Private.ExecEnv.CheckCombatLogFlagsObjectType(destFlags, %q)",
        conditionType = "select",
        conditionTest = function(state, needle, op)
          if state and state.show then
            return Private.ExecEnv.CheckCombatLogFlagsObjectType(state.destFlags, needle) == (op == "==")
          end
        end,
        enable = function(trigger)
          return not (trigger.subeventPrefix == "SPELL" and trigger.subeventSuffix == "_CAST_START");
        end,
      },
      {-- destFlags ignore for SPELL_CAST_START
        enable = function(trigger)
          return (trigger.subeventPrefix == "SPELL" and trigger.subeventSuffix == "_CAST_START");
        end,
      },
      -- we don't have destRaidFlags
      {
        type = "header",
        name = "subeventHeader",
        display = L["Subevent Info"],
        enable = function(trigger)
          return trigger.subeventPrefix and (
            trigger.subeventPrefix == "RANGE"
            or trigger.subeventPrefix == "ENVIRONMENTAL"
            or trigger.subeventPrefix:find("DAMAGE")
            or trigger.subeventPrefix:find("SPELL"))

            or trigger.subeventSuffix and (
            -- trigger.subeventSuffix == "_ABSORBED" -- we don't have that
            trigger.subeventSuffix == "_INTERRUPT"
              or trigger.subeventSuffix == "_DISPEL"
              or trigger.subeventSuffix == "_DISPEL_FAILED"
              or trigger.subeventSuffix == "_STOLEN"
              or trigger.subeventSuffix == "_AURA_BROKEN_SPELL"
              or trigger.subeventSuffix == "_DAMAGE"
              or trigger.subeventSuffix == "_HEAL"
              or trigger.subeventSuffix == "_ENERGIZE"
              or trigger.subeventSuffix == "_DRAIN"
              or trigger.subeventSuffix == "_LEECH"
              or trigger.subeventSuffix == "_DAMAGE"
              or trigger.subeventSuffix == "_MISSED"
              or trigger.subeventSuffix == "_EXTRA_ATTACKS"
              or trigger.subeventSuffix == "_CAST_FAILED"
              or trigger.subeventSuffix:find("DOSE")
              or trigger.subeventSuffix:find("AURA"))
        end,
      },
      {
        name = "spellId",
        display = L["Spell Id"],
        init = "arg",
        enable = function(trigger)
          return trigger.subeventPrefix and (trigger.subeventPrefix:find("SPELL") or trigger.subeventPrefix == "RANGE" or trigger.subeventPrefix:find("DAMAGE"))
        end,
        store = true,
        preambleGroup = "spell",
        preamble = "local spellChecker = Private.ExecEnv.CreateSpellChecker()",
        multiEntry = {
          operator = "preamble",
          preambleAdd = "spellChecker:AddExact(%q)"
        },
        test = "spellChecker:Check(spellId)",
        conditionType = "number",
        type = "spell",
        showExactOption = false,
        noProgressSource = true
      },
      {
        name = "spellName",
        display = L["Spell Name"],
        type = "spell",
        noValidation = true,
        init = "arg",
        enable = function(trigger)
          return trigger.subeventPrefix and (trigger.subeventPrefix:find("SPELL") or trigger.subeventPrefix == "RANGE" or trigger.subeventPrefix:find("DAMAGE"))
        end,
        store = true,
        preambleGroup = "spell",
        preamble = "local spellChecker = Private.ExecEnv.CreateSpellChecker()",
        multiEntry = {
          operator = "preamble",
          preambleAdd = "spellChecker:AddName(%q)"
        },
        test = "spellChecker:Check(spellId)",
        conditionType = "string"
      },
      {
        name = "spellSchool",
        display = WeakAuras.newFeatureString .. L["Spell School"],
        type = "select",
        values = "combatlog_spell_school_types_for_ui",
        sorted = true,
        test = "spellSchool == %d",
        init = "arg",
        conditionType = "select",
        store = true,
        enable = function(trigger)
          return trigger.subeventPrefix and (trigger.subeventPrefix:find("SPELL") or trigger.subeventPrefix == "RANGE" or trigger.subeventPrefix:find("DAMAGE"))
        end
      },
      {
        name = "environmentalType",
        display = L["Environment Type"],
        type = "select",
        init = "arg",
        values = "environmental_types",
        enable = function(trigger)
          return trigger.subeventPrefix == "ENVIRONMENTAL"
        end,
        store = true,
        conditionType = "select"
      },
      {
        name = "missType",
        display = L["Miss Type"],
        type = "select",
        init = "arg",
        values = "miss_types",
        enable = function(trigger)
          return trigger.subeventSuffix and trigger.subeventPrefix and (trigger.subeventSuffix == "_MISSED" or trigger.subeventPrefix == "DAMAGE_SHIELD_MISSED")
        end,
        conditionType = "select",
        store = true
      },
      {
        name = "extraSpellId",
        display = WeakAuras.newFeatureString .. L["Extra Spell Id"],
        init = "arg",
        enable = function(trigger)
          return trigger.subeventSuffix and (trigger.subeventSuffix == "_INTERRUPT" or trigger.subeventSuffix == "_DISPEL" or trigger.subeventSuffix == "_DISPEL_FAILED" or trigger.subeventSuffix == "_STOLEN" or trigger.subeventSuffix == "_AURA_BROKEN_SPELL")
        end,
        test = "GetSpellInfo(%q or '') == extraSpellName",
        type = "spell",
        showExactOption = false,
        store = true,
        conditionType = "number",
        noProgressSource = true
      },
      {
        name = "extraSpellName",
        display = L["Extra Spell Name"],
        type = "spell",
        noValidation = true,
        init = "arg",
        enable = function(trigger)
          return trigger.subeventSuffix and (trigger.subeventSuffix == "_INTERRUPT" or trigger.subeventSuffix == "_DISPEL" or trigger.subeventSuffix == "_DISPEL_FAILED" or trigger.subeventSuffix == "_STOLEN" or trigger.subeventSuffix == "_AURA_BROKEN_SPELL")
        end,
        store = true,
        conditionType = "string"
      },
      {
        enable = function(trigger)
          return trigger.subeventSuffix and (trigger.subeventSuffix == "_INTERRUPT" or trigger.subeventSuffix == "_DISPEL" or trigger.subeventSuffix == "_DISPEL_FAILED" or trigger.subeventSuffix == "_STOLEN" or trigger.subeventSuffix == "_AURA_BROKEN_SPELL")
        end
      }, -- extraSchool ignored with _ argument
      {
        name = "auraType",
        display = L["Aura Type"],
        type = "select",
        init = "arg",
        values = "aura_types",
        store = true,
        enable = function(trigger)
          return trigger.subeventSuffix and (trigger.subeventSuffix:find("AURA") or trigger.subeventSuffix == "_DISPEL" or trigger.subeventSuffix == "_STOLEN")
        end,
        conditionType = "select"
      },
      {
        name = "amount",
        display = L["Amount"],
        type = "number",
        init = "arg",
        enable = function(trigger)
          return trigger.subeventSuffix and trigger.subeventPrefix and (trigger.subeventSuffix == "_DAMAGE" or trigger.subeventSuffix == "_HEAL" or trigger.subeventSuffix == "_ENERGIZE" or trigger.subeventSuffix == "_DRAIN" or trigger.subeventSuffix == "_LEECH" or trigger.subeventPrefix:find("DAMAGE"))
        end,
        store = true,
        conditionType = "number"
      },
      {
        name = "overkill",
        display = L["Overkill"],
        type = "number",
        init = "arg",
        enable = function(trigger)
          return trigger.subeventSuffix and trigger.subeventPrefix and (trigger.subeventSuffix == "_DAMAGE" or trigger.subeventPrefix == "DAMAGE_SHIELD" or trigger.subeventPrefix == "DAMAGE_SPLIT")
        end,
        store = true,
        conditionType = "number"
      },
      {
        name = "overhealing",
        display = L["Overhealing"],
        type = "number",
        init = "arg",
        enable = function(trigger)
          return trigger.subeventSuffix and trigger.subeventSuffix == "_HEAL"
        end,
        store = true,
        conditionType = "number"
      },
      {
        enable = function(trigger)
          return trigger.subeventSuffix and trigger.subeventPrefix and (trigger.subeventSuffix == "_DAMAGE" or trigger.subeventPrefix == "DAMAGE_SHIELD" or trigger.subeventPrefix == "DAMAGE_SPLIT")
        end
      }, -- damage school ignored with _ argument
      {
        name = "resisted",
        display = L["Resisted"],
        type = "number",
        init = "arg",
        enable = function(trigger)
          return trigger.subeventSuffix and trigger.subeventPrefix and (trigger.subeventSuffix == "_DAMAGE" or trigger.subeventPrefix == "DAMAGE_SHIELD" or trigger.subeventPrefix == "DAMAGE_SPLIT")
        end,
        store = true,
        conditionType = "number"
      },
      {
        name = "blocked",
        display = L["Blocked"],
        type = "number",
        init = "arg",
        enable = function(trigger)
          return trigger.subeventSuffix and trigger.subeventPrefix and (trigger.subeventSuffix == "_DAMAGE" or trigger.subeventPrefix == "DAMAGE_SHIELD" or trigger.subeventPrefix == "DAMAGE_SPLIT")
        end,
        store = true,
        conditionType = "number"
      },
      {
        name = "absorbed",
        display = L["Absorbed"],
        type = "number",
        init = "arg",
        enable = function(trigger)
          return trigger.subeventSuffix and trigger.subeventPrefix and (trigger.subeventSuffix == "_DAMAGE" or trigger.subeventPrefix == "DAMAGE_SHIELD" or trigger.subeventPrefix == "DAMAGE_SPLIT" or trigger.subeventSuffix == "_HEAL")
        end,
        store = true,
        conditionType = "number"
      },
      {
        name = "critical",
        display = L["Critical"],
        type = "tristate",
        init = "arg",
        enable = function(trigger)
          return trigger.subeventSuffix and trigger.subeventPrefix and (trigger.subeventSuffix == "_DAMAGE" or trigger.subeventPrefix == "DAMAGE_SHIELD" or trigger.subeventPrefix == "DAMAGE_SPLIT" or trigger.subeventSuffix == "_HEAL")
        end,
        store = true,
        conditionType = "bool"
      },
      {
        name = "glancing",
        display = L["Glancing"],
        type = "tristate",
        init = "arg",
        enable = function(trigger)
          return trigger.subeventSuffix and trigger.subeventPrefix and (trigger.subeventSuffix == "_DAMAGE" or trigger.subeventPrefix == "DAMAGE_SHIELD" or trigger.subeventPrefix == "DAMAGE_SPLIT")
        end,
        store = true,
        conditionType = "bool"
      },
      {
        name = "crushing",
        display = L["Crushing"],
        type = "tristate",
        init = "arg",
        enable = function(trigger)
          return trigger.subeventSuffix and trigger.subeventPrefix and (trigger.subeventSuffix == "_DAMAGE" or trigger.subeventPrefix == "DAMAGE_SHIELD" or trigger.subeventPrefix == "DAMAGE_SPLIT")
        end,
        store = true,
        conditionType = "bool"
      },
      -- we don't have isOffHand :(
      {
        name = "number",
        display = L["Number"],
        type = "number",
        init = "arg",
        enable = function(trigger)
          return trigger.subeventSuffix and (trigger.subeventSuffix == "_EXTRA_ATTACKS" or trigger.subeventSuffix:find("DOSE"))
        end,
        store = true,
        conditionType = "number"
      },
      {
        name = "overEnergize",
        display = L["Over Energize"],
        type = "number",
        init = "arg",
        store = true,
        conditionType = "number",
        enable = function(trigger)
          return trigger.subeventSuffix and (trigger.subeventSuffix == "_ENERGIZE")
        end
      },
      {
        name = "powerType",
        display = L["Power Type"],
        type = "select",
        init = "arg",
        values = "power_types",
        store = true,
        enable = function(trigger)
          return trigger.subeventSuffix and (trigger.subeventSuffix == "_ENERGIZE" or trigger.subeventSuffix == "_DRAIN" or trigger.subeventSuffix == "_LEECH")
        end,
        conditionType = "select"
      },
      {
        name = "extraAmount",
        display = L["Extra Amount"],
        type = "number",
        init = "arg",
        enable = function(trigger)
          return trigger.subeventSuffix and (trigger.subeventSuffix == "_DRAIN" or trigger.subeventSuffix == "_LEECH")
        end,
        store = true,
        conditionType = "number"
      },
      {
        enable = function(trigger)
          return trigger.subeventSuffix == "_CAST_FAILED"
        end
      }, -- failedType ignored with _ argument - theoretically this is not necessary because it is the last argument in the event, but it is added here for completeness
      {
        type = "header",
        name = "miscellaneousHeader",
        display = L["Miscellaneous"],
      },
      {
        name = "cloneId",
        display = L["Clone per Event"],
        type = "toggle",
        test = "true",
        init = "use_cloneId and WeakAuras.GetUniqueCloneId() or ''"
      },
      {
        hidden = true,
        name = "icon",
        init = "(spellId and select(3, GetSpellInfo(spellId))) or 'Interface\\\\Icons\\\\INV_Misc_QuestionMark'",
        store = true,
        test = "true"
      },
    },
    countEvents = true,
    delayEvents = true,
    timedrequired = true,
    progressType = "timed"
  },
  ["Cooldown Progress (Spell)"] = {
    type = "spell",
    events = {},
    loadInternalEventFunc = function(trigger, untrigger)
      local spellName = type(trigger.spellName) ~= "table" and trigger.spellName or 0;
      if not trigger.use_exact_spellName then
        spellName = type(spellName) == "number" and GetSpellInfo(spellName) or spellName;
      end
      if spellName == nil then return {} end
      local events = {
        "SPELL_COOLDOWN_CHANGED:" .. spellName,
        "COOLDOWN_REMAINING_CHECK:" .. spellName,
        "WA_DELAYED_PLAYER_ENTERING_WORLD"
      };
      if (trigger.use_showgcd) then
        tinsert(events, "GCD_START");
        tinsert(events, "GCD_CHANGE");
        tinsert(events, "GCD_END");
      end
      return events;
    end,
    force_events = "SPELL_COOLDOWN_FORCE",
    name = L["Cooldown/Charges/Count"],
    loadFunc = function(trigger)
      local spellName = type(trigger.spellName) ~= "table" and trigger.spellName or 0;
      if not trigger.use_exact_spellName then
        spellName = type(spellName) == "number" and GetSpellInfo(spellName) or spellName;
      end
      WeakAuras.WatchSpellCooldown(spellName, trigger.use_matchedRune)
      if (trigger.use_showgcd) then
        WeakAuras.WatchGCD();
      end
    end,
    init = function(trigger)
      local spellName = type(trigger.spellName) ~= "table" and trigger.spellName or 0
      if not trigger.use_exact_spellName then
        spellName = type(spellName) == "number" and GetSpellInfo(spellName) or spellName;
      end
      if (type(spellName) == "string") then
        spellName = "[[" .. spellName .. "]]";
      end
      local ret = {}

      local showOnCheck = "false";
      if (trigger.genericShowOn == "showOnReady") then
        showOnCheck = "startTime and startTime == 0 or gcdCooldown";
      elseif (trigger.genericShowOn == "showOnCooldown") then
        showOnCheck = "startTime and startTime > 0 and not gcdCooldown";
      elseif (trigger.genericShowOn == "showAlways") then
        showOnCheck = "startTime ~= nil";
      end

      local trackSpecificCharge = trigger.use_trackcharge and trigger.trackcharge and trigger.trackcharge ~= ""
      local track = trigger.track or "auto"
      if track == "auto" and trackSpecificCharge then
        track = "charges"
      end

      table.insert(ret, ([=[
        local spellname = %s
        local ignoreRuneCD = %s
        local showgcd = %s;
        local ignoreSpellKnown = %s;
        local track = %q
        local effectiveSpellId = spellname
        local name, _, icon = GetSpellInfo(effectiveSpellId)
        local startTime, duration, gcdCooldown, readyTime, paused = WeakAuras.GetSpellCooldown(effectiveSpellId, ignoreRuneCD, showgcd, ignoreSpellKnown, track)
        local charges, maxCharges, spellCount, chargeGainTime, chargeLostTime = WeakAuras.GetSpellCharges(effectiveSpellId, ignoreSpellKnown)
        local stacks = maxCharges and maxCharges > 1 and charges or (spellCount and spellCount > 0 and spellCount) or nil;
        if (charges == nil) then
          -- Use fake charges for spells that use GetSpellCooldown
          charges = (duration == 0 or gcdCooldown) and 1 or 0;
        end
        local genericShowOn = %s
        local expirationTime = startTime and duration and startTime + duration
        state.spellname = spellname;
      ]=]):format(
        spellName,
        (trigger.use_matchedRune and "true" or "false"),
        (trigger.use_showgcd and "true" or "false"),
        (trigger.use_ignoreSpellKnown and "true" or "false"),
        track,
        showOnCheck
      ))

      if (not trackSpecificCharge) then
        table.insert(ret, [=[
          if paused then
            if not state.paused then
              state.paused = true
              state.expirationTime = nil
              state.changed = true
            end
            if state.remaining ~= startTime then
              state.remaining = startTime
              state.changed = true
            end
          else
            if (state.expirationTime ~= expirationTime) then
              state.expirationTime = expirationTime;
              state.changed = true;
            end

            if state.paused then
              state.paused = false
              state.remaining = nil
              state.changed = true
            end
          end
          if (state.duration ~= duration) then
            state.duration = duration;
            state.changed = true;
          end
          state.progressType = 'timed';
        ]=])
      else -- Tracking charges
        local trackedCharge = tonumber(trigger.trackcharge) or 1;
        table.insert(ret, ([=[
          local trackedCharge = %s
          if (charges > trackedCharge) then
            if (state.expirationTime ~= 0) then
              state.expirationTime = 0;
              state.changed = true;
            end
            if (state.duration ~= 0) then
              state.duration = 0;
              state.changed = true;
            end
            state.value = nil;
            state.total = nil;
            state.progressType = 'timed';
          else
            if duration then
              expirationTime = expirationTime + (trackedCharge - charges) * duration
            end
            if (state.expirationTime ~= expirationTime) then
              state.expirationTime = expirationTime;
              state.changed = true;
            end
            if (state.duration ~= duration) then
              state.duration = duration;
              state.changed = true;
            end
            state.value = nil;
            state.total = nil;
            state.progressType = 'timed';
          end
        ]=]):format(trackedCharge - 1))
      end
      if(trigger.use_remaining and trigger.genericShowOn ~= "showOnReady") then
        table.insert(ret, ([[
          local remaining = 0;
          if (not paused and expirationTime and expirationTime > 0) then
            remaining = expirationTime - GetTime();
            local remainingCheck = %s;
            if(remaining >= remainingCheck and remaining > 0) then
              local event = "COOLDOWN_REMAINING_CHECK:" .. %s
              Private.ExecEnv.ScheduleScan(expirationTime - remainingCheck, event);
            end
          end
        ]]):format(tonumber(trigger.remaining or 0) or 0, spellName))
      end

      return table.concat(ret)
    end,
    GetNameAndIcon = GetNameAndIconForSpellName,
    statesParameter = "one",
    progressType = "timed",
    args = {
      {
      }, -- Ignore first argument (id)
      {
        name = "spellName",
        required = true,
        display = L["Spell"],
        type = "spell",
        test = "true",
        showExactOption = true,
      },
      {
        name = "extra Cooldown Progress (Spell)",
        display = function(trigger)
          return function()
            local text = "";
            if trigger.track == "charges" then
              text = L["Tracking Charge CDs"]
            elseif trigger.track == "cooldown" then
              text = L["Tracking Only Cooldown"]
            end

            if trigger.use_showgcd then
              if text ~= "" then text = text .. "; " end
              text = text .. L["Show GCD"]
            end

            if trigger.use_matchedRune then
              if text ~= "" then text = text .. "; " end
              text = text ..L["Ignore Rune CDs"]
            end

            if trigger.use_ignoreSpellKnown then
              if text ~= "" then text = text .. "; " end
              text = text .. L["Disabled Spell Known Check"]
            end

            if trigger.genericShowOn ~= "showOnReady" and trigger.track ~= "cooldown" then
              if trigger.use_trackcharge and trigger.trackcharge and trigger.trackcharge ~= "" then
                if text ~= "" then text = text .. "; " end
                text = text .. L["Tracking Charge %i"]:format(trigger.trackcharge)
              end
            end
            if text == "" then
              return L["|cFFffcc00Extra Options:|r None"]
            end
            return L["|cFFffcc00Extra Options:|r %s"]:format(text)
          end
        end,
        type = "collapse",
      },
      {
        name = "track",
        display = L["Track Cooldowns"],
        type = "select",
        values = "cooldown_types",
        collapse = "extra Cooldown Progress (Spell)",
        test = "true",
        required = true,
        default = "auto"
      },
      {
        name = "showgcd",
        display = L["Show Global Cooldown"],
        type = "toggle",
        test = "true",
        collapse = "extra Cooldown Progress (Spell)"
      },
      {
        name = "matchedRune",
        display = L["Ignore Rune CD"],
        type = "toggle",
        test = "true",
        collapse = "extra Cooldown Progress (Spell)"
      },
      {
        name = "ignoreSpellKnown",
        display = L["Disable Spell Known Check"],
        type = "toggle",
        test = "true",
        collapse = "extra Cooldown Progress (Spell)"
      },
      {
        name = "trackcharge",
        display = L["Show CD of Charge"],
        type = "number",
        enable = function(trigger)
          return (trigger.genericShowOn ~= "showOnReady") and trigger.track ~= "cooldown"
        end,
        test = "true",
        noOperator = true,
        collapse = "extra Cooldown Progress (Spell)"
      },
      {
        name = "remaining",
        display = L["Remaining Time"],
        type = "number",
        enable = function(trigger) return (trigger.genericShowOn ~= "showOnReady") end
      },
      {
        name = "charges",
        display = L["Charges"],
        type = "number",
        store = true,
        conditionType = "number",
        progressTotal = "maxCharges"
      },
      {
        name = "spellCount",
        display = L["Spell Count"],
        type = "number",
        store = true,
        conditionType = "number"
      },
      {
        name = "stacks",
        init = "stacks",
        hidden = true,
        test = "true",
        store = true
      },
      {
        hidden  = true,
        name = "maxCharges",
        store = true,
        display = L["Max Charges"],
        conditionType = "number",
        test = "true",
      },
      {
        hidden = true,
        name = "readyTime",
        display = L["Since Ready"],
        conditionType = "elapsedTimer",
        store = true,
        test = "true"
      },
      {
        hidden = true,
        name = "chargeGainTime",
        display = L["Since Charge Gain"],
        conditionType = "elapsedTimer",
        store = true,
        test = "true"
      },
      {
        hidden = true,
        name = "chargeLostTime",
        display = L["Since Charge Lost"],
        conditionType = "elapsedTimer",
        store = true,
        test = "true"
      },
      {
        hidden = true,
        name = "effectiveSpellId",
        display = L["Effective Spell Id"],
        conditionType = "number",
        store = true,
        test = "true",
        operator_types = "only_equal"
      },
      {
        name = "genericShowOn",
        display =  L["Show"],
        type = "select",
        values = "cooldown_progress_behavior_types",
        test = "true",
        required = true,
        default = "showOnCooldown"
      },
      {
        hidden = true,
        name = "onCooldown",
        test = "true",
        display = L["On Cooldown"],
        conditionType = "bool",
        conditionTest = function(state, needle)
          return state and state.show and (state.paused or (not state.gcdCooldown and state.expirationTime and state.expirationTime > GetTime())) == (needle == 1)
        end,
      },
      {
        hidden = true,
        name = "gcdCooldown",
        store = true,
        test = "true"
      },
      {
        name = "spellUsable",
        display = L["Spell Usable"],
        hidden = true,
        test = "true",
        conditionType = "bool",
        conditionTest = function(state, needle)
        return state and state.show and
          ((IsUsableSpell((type(state.spellname) == "number" and GetSpellInfo(state.spellname)) or state.spellname) == 1 and true or false) == (needle == 1))
        end,
        conditionEvents = AddTargetConditionEvents({
          "SPELL_UPDATE_USABLE",
          "UNIT_MANA:player", "UNIT_RAGE:player", "UNIT_FOCUS:player", "UNIT_ENERGY:player", "UNIT_RUNIC_POWER:player"
        }),
      },
      {
        name = "insufficientResources",
        display = L["Insufficient Resources"],
        hidden = true,
        test = "true",
        conditionType = "bool",
        conditionTest = function(state, needle)
        return state and state.show and
          ((select(2, IsUsableSpell((type(state.spellname) == "number" and GetSpellInfo(state.spellname)) or state.spellname)) == 1 and true or false) == (needle == 1))
        end,
        conditionEvents = AddTargetConditionEvents({
          "SPELL_UPDATE_USABLE",
          "UNIT_MANA:player", "UNIT_RAGE:player", "UNIT_FOCUS:player", "UNIT_ENERGY:player", "UNIT_RUNIC_POWER:player"
        }),
      },
      {
        name = "spellInRange",
        display = L["Spell in Range"],
        hidden = true,
        test = "true",
        conditionType = "bool",
        conditionTest = function(state, needle)
          return state and state.show and (UnitExists('target') and state.spellname and WeakAuras.IsSpellInRange(state.spellname, 'target') == needle)
        end,
        conditionEvents = AddTargetConditionEvents({
          "WA_SPELL_RANGECHECK",
        }),
      },
      {
        hidden = true,
        test = "genericShowOn"
      },
      {
        name = "name",
        display = L["Name"],
        hidden = true,
        init = "name",
        test = "true",
        store = true,
        conditionType = "string"
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
    },
    hasSpellID = true,
    automaticrequired = true,
  },
  ["Cooldown Ready (Spell)"] = {
    type = "spell",
    events = {},
    loadInternalEventFunc = function(trigger, untrigger)
      local spellName = type(trigger.spellName) ~= "table" and trigger.spellName or 0
      if not trigger.use_exact_spellName then
        spellName = type(spellName) == "number" and GetSpellInfo(spellName) or spellName;
      end
      if spellName == nil then return {} end
      return { "SPELL_COOLDOWN_READY:" .. spellName }
    end,
    name = L["Cooldown Ready Event"],
    loadFunc = function(trigger)
      local spellName = type(trigger.spellName) ~= "table" and trigger.spellName or 0
      if not trigger.use_exact_spellName then
        spellName = type(spellName) == "number" and GetSpellInfo(spellName) or spellName;
      end
      WeakAuras.WatchSpellCooldown(spellName, false)
    end,
    init = function(trigger)
      local spellName = type(trigger.spellName) ~= "table" and trigger.spellName or 0
      if not trigger.use_exact_spellName then
        spellName = type(spellName) == "number" and GetSpellInfo(spellName) or spellName;
      end
      if (type(spellName) == "string") then
        spellName = "[[" .. spellName .. "]]";
      end

      local ret = [=[
        local triggerSpellName = %s
        local name, _, icon = GetSpellInfo(triggerSpellName)
      ]=]
      return ret:format(spellName)
    end,
    GetNameAndIcon = GetNameAndIconForSpellName,
    statesParameter = "one",
    args = {
      {
        name = "spellName",
        required = true,
        display = L["Spell"],
        type = "spell",
        init = "arg",
        showExactOption = true,
        test = "true"
      },
      {
        name = "name",
        display = L["Name"],
        hidden = true,
        init = "name",
        test = "true",
        store = true,
        conditionType = "string"
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
    },
    hasSpellID = true,
    timedrequired = true,
    progressType = "timed"
  },
  ["Charges Changed"] = {
    type = "spell",
    events = {},
    loadInternalEventFunc = function(trigger, untrigger)
      local spellName = type(trigger.spellName) ~= "table" and trigger.spellName or 0
      if not trigger.use_exact_spellName then
        spellName = type(spellName) == "number" and GetSpellInfo(spellName) or spellName;
      end
      if spellName == nil then return {} end
      return { "SPELL_CHARGES_CHANGED:" .. spellName }
    end,
    name = L["Charges Changed Event"],
    loadFunc = function(trigger)
      local spellName = type(trigger.spellName) ~= "table" and trigger.spellName or 0
      if not trigger.use_exact_spellName then
        spellName = type(spellName) == "number" and GetSpellInfo(spellName) or spellName;
      end
      WeakAuras.WatchSpellCooldown(spellName);
    end,
    init = function(trigger)
      local spellName = type(trigger.spellName) ~= "table" and trigger.spellName or 0
      if not trigger.use_exact_spellName then
        spellName = type(spellName) == "number" and GetSpellInfo(spellName) or spellName;
      end
      if (type(spellName) == "string") then
        spellName = "[[" .. spellName .. "]]";
      end
      local ret = [=[
        local triggerSpellName = %s
        local name, _, icon = GetSpellInfo(triggerSpellName)
      ]=]
      return ret:format(spellName)
    end,
    statesParameter = "one",
    GetNameAndIcon = GetNameAndIconForSpellName,
    args = {
      {
        name = "spellName",
        required = true,
        display = L["Spell"],
        type = "spell",
        init = "arg",
        showExactOption = true,
        test = "true"
      },
      {
        name = "direction",
        required = true,
        display = L["Charge gained/lost"],
        type = "select",
        values = "charges_change_type",
        init = "arg",
        test = "Private.ExecEnv.CheckChargesDirection(direction, %q)",
        store = true,
        conditionType = "select",
        conditionValues = "charges_change_condition_type";
        conditionTest = function(state, needle)
          return state and state.show and state.direction and Private.ExecEnv.CheckChargesDirection(state.direction, needle)
        end,
      },
      {
        name = "charges",
        display = L["Charges"],
        type = "number",
        init = "arg",
        store = true,
        conditionType = "number"
      },
      {
        name = "name",
        display = L["Name"],
        hidden = true,
        init = "name",
        test = "true",
        store = true,
        conditionType = "string"
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
    },
    hasSpellID = true,
    timedrequired = true,
    progressType = "timed"
  },
  ["Cooldown Progress (Item)"] = {
    type = "item",
    events = {},
    internal_events = function(trigger, untrigger)
      local itemName = type(trigger.itemName) == "number" and trigger.itemName or string.format("%q", trigger.itemName or "0")
      local events = {
        "ITEM_COOLDOWN_READY:" .. itemName,
        "ITEM_COOLDOWN_CHANGED:" .. itemName,
        "ITEM_COOLDOWN_STARTED:" .. itemName,
        "COOLDOWN_REMAINING_CHECK:" .. itemName,
      }
      if (trigger.use_showgcd) then
        tinsert(events, "GCD_START");
        tinsert(events, "GCD_CHANGE");
        tinsert(events, "GCD_END");
      end
      return events
    end,
    force_events = "ITEM_COOLDOWN_FORCE",
    name = L["Cooldown Progress (Item)"],
    loadFunc = function(trigger)
      WeakAuras.WatchItemCooldown(trigger.itemName or 0)
      if (trigger.use_showgcd) then
        WeakAuras.WatchGCD();
      end
    end,
    init = function(trigger)
      local itemName = type(trigger.itemName) == "number" and trigger.itemName or string.format("%q", trigger.itemName or "0")
      local ret = [=[
        local itemname = %s;
        local name = GetItemInfo(itemname or 0) or "Invalid"
        local icon = GetItemIcon(itemname) or ""
        local showgcd = %s
        local startTime, duration, enabled, gcdCooldown = WeakAuras.GetItemCooldown(itemname, showgcd);
        local expirationTime = startTime + duration
        local genericShowOn = %s
        state.itemname = itemname;
      ]=];
      if(trigger.use_remaining and trigger.genericShowOn ~= "showOnReady") then
        local ret2 = [[
          local remaining = expirationTime > 0 and (expirationTime - GetTime()) or 0;
          local remainingCheck = %s;
          if(remaining >= remainingCheck and remaining > 0) then
            local event = "COOLDOWN_REMAINING_CHECK:" .. %s
            Private.ExecEnv.ScheduleScan(expirationTime - remainingCheck, event);
          end
        ]];
        ret = ret..ret2:format(tonumber(trigger.remaining or 0) or 0, itemName);
      end
      return ret:format(itemName,
                        trigger.use_showgcd and "true" or "false",
                        "[[" .. (trigger.genericShowOn or "") .. "]]");
    end,
    GetNameAndIcon = function(trigger)
      local name = GetItemInfo(trigger.itemName or 0)
      local icon = GetItemIcon(trigger.itemName or 0)
      return name, icon
    end,
    statesParameter = "one",
    args = {
      {
        name = "itemName",
        required = true,
        display = L["Item"],
        type = "item",
        test = "true"
      },
      --[[{ maybe some day
        name = "itemId",
        display = WeakAuras.newFeatureString .. L["ItemId"],
        hidden = true,
        init = "itemId",
        test = "true",
        store = true,
        conditionType = "number",
        operator_types = "only_equal",
      },]]
      {
        name = "remaining",
        display = L["Remaining Time"],
        type = "number",
        enable = function(trigger) return (trigger.genericShowOn ~= "showOnReady") end,
        init = "remaining"
      },
      {
        name = "extra Cooldown Progress (Item)",
        display = function(trigger)
          return function()
            local text = "";
            if trigger.use_showgcd then
              if text ~= "" then text = text .. "; " end
              text = text .. L["Show GCD"]
            end
            if text == "" then
              return L["|cFFffcc00Extra Options:|r None"]
            end
            return L["|cFFffcc00Extra Options:|r %s"]:format(text)
          end
        end,
        type = "collapse",
      },
      {
        name = "showgcd",
        display = L["Show Global Cooldown"],
        type = "toggle",
        test = "true",
        collapse = "extra Cooldown Progress (Item)"
      },
      {
        name = "genericShowOn",
        display =  L["Show"],
        type = "select",
        values = "cooldown_progress_behavior_types",
        test = "true",
        required = true,
        default = "showOnCooldown"
      },
      {
        hidden = true,
        name = "enabled",
        store = true,
        test = "true",
      },
      {
        hidden = true,
        name = "onCooldown",
        test = "true",
        display = L["On Cooldown"],
        conditionType = "bool",
        conditionTest = function(state, needle)
          return state and state.show and (not state.gcdCooldown and state.expirationTime and state.expirationTime > GetTime() or state.enabled == 0) == (needle == 1)
        end,
      },
      {
        hidden = true,
        name = "gcdCooldown",
        store = true,
        test = "true"
      },
      {
        name = "name",
        display = L["Name"],
        hidden = true,
        init = "name",
        test = "true",
        store = true,
        conditionType = "string"
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
      {
        name = "progressType",
        hidden = true,
        init = "'timed'",
        store = true,
        test = "true"
      },
      {
        name = "duration",
        hidden = true,
        init = "duration",
        test = "true",
        store = true
      },
      {
        name = "expirationTime",
        init = "expirationTime",
        hidden = true,
        test = "true",
        store = true
      },
      {
        hidden = true,
        test = "(genericShowOn == \"showOnReady\" and (startTime == 0 and enabled == 1 or gcdCooldown))" ..
        "or (genericShowOn == \"showOnCooldown\" and (startTime > 0 or enabled == 0) and not gcdCooldown) " ..
        "or (genericShowOn == \"showAlways\")"
      }
    },
    hasItemID = true,
    automaticrequired = true,
    progressType = "timed"
  },
  ["Cooldown Progress (Equipment Slot)"] = {
    type = "item",
    events = {
      ["unit_events"] = {
        ["player"] = {"UNIT_INVENTORY_CHANGED"}
      }
    },
    internal_events = function(trigger, untrigger)
      local slot = trigger.itemSlot or 0
      local events = {
        "ITEM_SLOT_COOLDOWN_STARTED:" .. slot,
        "ITEM_SLOT_COOLDOWN_CHANGED:" .. slot,
        "COOLDOWN_REMAINING_CHECK:" .. slot,
        "ITEM_SLOT_COOLDOWN_ITEM_CHANGED:" .. slot,
        "ITEM_SLOT_COOLDOWN_READY:" .. slot,
        "WA_DELAYED_PLAYER_ENTERING_WORLD"
      }

      if (trigger.use_showgcd) then
        tinsert(events, "GCD_START");
        tinsert(events, "GCD_CHANGE");
        tinsert(events, "GCD_END");
      end

      return events
    end,
    force_events = "ITEM_COOLDOWN_FORCE",
    name = L["Cooldown Progress (Slot)"],
    loadFunc = function(trigger)
      WeakAuras.WatchItemSlotCooldown(trigger.itemSlot);
      if (trigger.use_showgcd) then
        WeakAuras.WatchGCD();
      end
    end,
    init = function(trigger)
      local ret = [[
        local showgcd = %s
        local itemSlot = %s
        local startTime, duration, enable, gcdCooldown = WeakAuras.GetItemSlotCooldown(itemSlot, showgcd)
        local expirationTime = startTime + duration
        local genericShowOn = %s
        local remaining = startTime + duration - GetTime();

        local name = ""
        local item = GetInventoryItemID("player", itemSlot or 0)
        if item then
          name = GetItemInfo(item)
        end
        local icon = GetInventoryItemTexture("player", itemSlot or 0)
        local stacks = GetInventoryItemCount("player", itemSlot or 0)
        if ((stacks == 1) and (not GetInventoryItemTexture("player", itemSlot or 0))) then
          stacks = 0
        end
      ]];
      if(trigger.use_remaining and trigger.genericShowOn ~= "showOnReady") then
        local ret2 = [[
          local remaining = expirationTime > 0 and (expirationTime - GetTime()) or 0;
          local remainingCheck = %s;
          if(remaining >= remainingCheck and remaining > 0) then
            local event = "COOLDOWN_REMAINING_CHECK:" .. %s
            Private.ExecEnv.ScheduleScan(expirationTime - remainingCheck, event);
          end
        ]];
        ret = ret..ret2:format(tonumber(trigger.remaining or 0) or 0, trigger.itemSlot or 0);
      end
      return ret:format(trigger.use_showgcd and "true" or "false",
                        trigger.itemSlot or "0",
                        "[[" .. (trigger.genericShowOn or "") .. "]]");
    end,
    GetNameAndIcon = function(trigger)
      local item = GetInventoryItemID("player", trigger.itemSlot or 0);
      local name
      if (item) then
        name = GetItemInfo(item)
      end
      local icon = GetInventoryItemTexture("player", trigger.itemSlot or 0)
      return name, icon
    end,
    statesParameter = "one",
    args = {
      {
        name = "itemId",
        display = WeakAuras.newFeatureString .. L["ItemId"],
        hidden = true,
        init = "item",
        test = "true",
        store = true,
        conditionType = "number",
        operator_types = "only_equal",
      },
      {
        name = "itemSlot",
        required = true,
        display = L["Equipment Slot"],
        type = "select",
        values = "item_slot_types",
        test = "true"
      },
      {
        name = "extra Cooldown Progress (Equipment Slot)",
        display = function(trigger)
          return function()
            local text = "";
            if trigger.use_showgcd then
              if text ~= "" then text = text .. "; " end
              text = text .. L["Show GCD"]
            end
            if text == "" then
              return L["|cFFffcc00Extra Options:|r None"]
            end
            return L["|cFFffcc00Extra Options:|r %s"]:format(text)
          end
        end,
        type = "collapse",
      },
      {
        name = "showgcd",
        display = L["Show Global Cooldown"],
        type = "toggle",
        test = "true",
        collapse = "extra Cooldown Progress (Equipment Slot)"
      },
      {
        name = "remaining",
        display = L["Remaining Time"],
        type = "number",
        enable = function(trigger) return (trigger.genericShowOn ~= "showOnReady") end,
        init = "remaining"
      },
      {
        name = "testForCooldown",
        display = L["is useable"],
        type = "toggle",
        test = "enable == 1"
      },
      {
        name = "genericShowOn",
        display =  L["Show"],
        type = "select",
        values = "cooldown_progress_behavior_types",
        test = "true",
        required = true,
        default = "showOnCooldown"
      },
      {
        name = "progressType",
        hidden = true,
        init = "'timed'",
        store = true,
        test = "true"
      },
      {
        name = "duration",
        hidden = true,
        init = "duration",
        test = "true",
        store = true
      },
      {
        name = "expirationTime",
        init = "expirationTime",
        hidden = true,
        test = "true",
        store = true
      },
      {
        name = "name",
        display = L["Name"],
        hidden = true,
        init = "name",
        test = "true",
        store = true,
        conditionType = "string"
      },
      --[[{ maybe some day
        name = "itemId",
        display = L["ItemId"],
        hidden = true,
        init = "item",
        test = "true",
        store = true,
        conditionType = "number",
        operator_types = "only_equal",
      },]]
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
      {
        name = "stacks",
        display = L["Stacks"],
        init = "stacks",
        hidden = true,
        test = "true",
        store = true,
        conditionType = "number"
      },
      {
        hidden = true,
        name = "onCooldown",
        test = "true",
        display = L["On Cooldown"],
        conditionType = "bool",
        conditionTest = function(state, needle)
          return state and state.show and (not state.gcdCooldown and state.expirationTime and state.expirationTime > GetTime()) == (needle == 1);
        end,
      },
      {
        hidden = true,
        name = "gcdCooldown",
        store = true,
        test = "true"
      },
      {
        hidden = true,
        test = "(genericShowOn == \"showOnReady\" and (startTime == 0 or gcdCooldown)) " ..
        "or (genericShowOn == \"showOnCooldown\" and startTime > 0 and not gcdCooldown) " ..
        "or (genericShowOn == \"showAlways\")"
      }
    },
    automaticrequired = true,
    hasItemID = true,
    progressType = "timed"
  },
  ["Cooldown Ready (Item)"] = {
    type = "item",
    events = {},
    internal_events = function(trigger)
      return { "ITEM_COOLDOWN_READY:" .. (trigger.itemName or 0) }
    end,
    name = L["Cooldown Ready Event (Item)"],
    loadFunc = function(trigger)
      WeakAuras.WatchItemCooldown(trigger.itemName or 0)
    end,
    init = function(trigger)
      local ret = [[
        local itemName = %s
        local name = GetItemInfo(itemName) or "Invalid"
        local icon = GetItemIcon(itemName) or ""
      ]]

      local itemName = type(trigger.itemName) == "number" and trigger.itemName or string.format("%q", trigger.itemName or "0")
      return ret:format(itemName)
    end,
    GetNameAndIcon = function(trigger)
      local name = GetItemInfo(trigger.itemName or 0)
      local icon = GetItemIcon(trigger.itemName or 0)
      return name, icon
    end,
    statesParameter = "one",
    args = {
      {
        name = "itemName",
        required = true,
        display = L["Item"],
        type = "item",
        init = "arg"
      },
      --[[{ maybe some day
        name = "itemId",
        display = WeakAuras.newFeatureString .. L["ItemId"],
        hidden = true,
        init = "itemId",
        test = "true",
        store = true,
        conditionType = "number",
        operator_types = "only_equal",
      },]]
      {
        name = "name",
        display = L["Name"],
        hidden = true,
        init = "name",
        test = "true",
        store = true,
        conditionType = "string"
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
    },
    hasItemID = true,
    timedrequired = true,
    progressType = "timed"
  },
  ["Cooldown Ready (Equipment Slot)"] = {
    type = "item",
    events = {},
    internal_events = function(trigger)
      return {
        "ITEM_SLOT_COOLDOWN_READY:" .. (trigger.itemSlot or 0)
      }
    end,
    name = L["Cooldown Ready Event (Slot)"],
    loadFunc  = function(trigger)
      WeakAuras.WatchItemSlotCooldown(trigger.itemSlot);
    end,
    init = function(trigger)
      local ret = [[
        local itemSlot = %s
        local item = GetInventoryItemID("player", itemSlot)
        local name = ""
        if (item) then
          name = GetItemInfo(item)
        end
        local icon = GetInventoryItemTexture("player", itemSlot)
      ]]

      return ret:format(trigger.itemSlot or 0)
    end,
    GetNameAndIcon = function(trigger)
      local item = GetInventoryItemID("player", trigger.itemSlot or 0)
      local name = item and GetItemInfo(item) or nil
      local icon = GetInventoryItemTexture("player", trigger.itemSlot or 0)
      return name, icon
    end,
    statesParameter = "one",
    args = {
      {
        name = "itemSlot",
        required = true,
        display = L["Equipment Slot"],
        type = "select",
        values = "item_slot_types",
        init = "arg"
      },
      {
        name = "itemId",
        display = WeakAuras.newFeatureString .. L["ItemId"],
        hidden = true,
        init = "item",
        test = "true",
        store = true,
        conditionType = "number",
        operator_types = "only_equal",
      },
      {
        name = "name",
        display = L["Name"],
        hidden = true,
        init = "name",
        test = "true",
        store = true,
        conditionType = "string"
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
    },
    hasItemID = true,
    timedrequired = true,
    progressType = "timed"
  },
  ["GTFO"] = {
    type = "addons",
    events = {
      ["events"] = {"GTFO_DISPLAY"}
    },
    name = L["GTFO Alert"],
    statesParameter = "one",
    args = {
      {
        name = "alertType",
        display = L["Alert Type"],
        type = "select",
        init = "arg",
        values = "gtfo_types",
        store = true,
        conditionType = "select"
      },
    },
    timedrequired = true,
    progressType = "timed"
  },
  ["Global Cooldown"] = {
    type = "spell",
    events = {},
    internal_events = {
      "GCD_START",
      "GCD_CHANGE",
      "GCD_END",
      "GCD_UPDATE",
      "WA_DELAYED_PLAYER_ENTERING_WORLD"
    },
    force_events = "GCD_UPDATE",
    name = L["Global Cooldown"],
    loadFunc = function(trigger)
      WeakAuras.WatchGCD();
    end,
    init = function(trigger)
      local ret = [[
        local inverse = %s;
        local duration, expirationTime, name, icon = WeakAuras.GetGCDInfo()
        local hasSpellName = WeakAuras.GcdSpellName();
      ]];
      return ret:format(trigger.use_inverse and "true" or "false");
    end,
    statesParameter = "one",
    args = {
      {
        name = "inverse",
        display = L["Inverse"],
        type = "toggle",
        test = "true"
      },
      {
        name = "name",
        display = L["Name"],
        hidden = true,
        init = "name",
        test = "true",
        store = true,
        conditionType = "string"
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
      {
        name = "duration",
        hidden = true,
        init = "duration",
        test = "true",
        store = true
      },
      {
        name = "expirationTime",
        init = "expirationTime",
        hidden = true,
        test = "true",
        store = true
      },
      {
        name = "progressType",
        hidden = true,
        init = "'timed'",
        test = "true",
        store = true
      },
      {
        hidden = true,
        test = "(inverse and duration == 0) or (not inverse and duration > 0 and hasSpellName)"
      }
    },
    hasSpellID = true,
    automaticrequired = true,
    progressType = "timed"
  },
  ["Swing Timer"] = {
    type = "unit",
    events = {},
    internal_events = {
      "SWING_TIMER_UPDATE"
    },
    force_events = "SWING_TIMER_UPDATE",
    name = L["Swing Timer"],
    loadFunc = function()
      WeakAuras.InitSwingTimer();
    end,
    init = function(trigger)
      local ret = [=[
        local inverse = %s;
        local hand = %q;
        local triggerRemaining = %s
        local duration, expirationTime, name, icon = WeakAuras.GetSwingTimerInfo(hand)
        local remaining = expirationTime and expirationTime - GetTime()
        local remainingCheck = not triggerRemaining or remaining and remaining %s triggerRemaining

        if triggerRemaining and remaining and remaining >= triggerRemaining and remaining > 0 then
          Private.ExecEnv.ScheduleScan(expirationTime - triggerRemaining, "SWING_TIMER_UPDATE")
        end
      ]=];
      return ret:format(
        (trigger.use_inverse and "true" or "false"),
        trigger.hand or "main",
        trigger.use_remaining and tonumber(trigger.remaining or 0) or "nil",
        trigger.remaining_operator or "<"
      );
    end,
    args = {
      {
        name = "note",
        type = "description",
        display = "",
        text = function()
          return L["Note: Due to how complicated the swing timer behavior is and the lack of APIs from Blizzard, results are inaccurate in edge cases."]
        end,

      },
      {
        name = "hand",
        required = true,
        display = L["Weapon"],
        type = "select",
        values = "swing_types",
        test = "true"
      },
      {
        name = "duration",
        hidden = true,
        init = "duration",
        test = "true",
        store = true
      },
      {
        name = "expirationTime",
        init = "expirationTime",
        hidden = true,
        test = "true",
        store = true
      },
      {
        name = "progressType",
        hidden = true,
        init = "'timed'",
        test = "true",
        store = true
      },
      {
        name = "name",
        hidden = true,
        init = "name",
        test = "true",
        store = true
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
      {
        name = "remaining",
        display = L["Remaining Time"],
        type = "number",
        enable = function(trigger) return not trigger.use_inverse end,
        test = "true"
      },
      {
        name = "inverse",
        display = L["Inverse"],
        type = "toggle",
        test = "true"
      },
      {
        hidden = true,
        test = "(inverse and duration == 0) or (not inverse and duration > 0)"
      },
      {
        hidden = true,
        test = "remainingCheck"
      }
    },
    automaticrequired = true,
    progressType = "timed",
    statesParameter = "one"
  },
  ["Action Usable"] = {
    type = "spell",
    events = function()
      local events = {
        "SPELL_UPDATE_USABLE",
        "ACTIONBAR_UPDATE_USABLE",
        "PLAYER_TARGET_CHANGED",
      }
      local unit_events
      if UnitPowerType("player") == 6 then
        tinsert(events, "RUNE_POWER_UPDATE")
        tinsert(events, "RUNE_TYPE_UPDATE")
        unit_events = {}
      end
      return {
        ["events"] = events,
        ["unit_events"] = unit_events or {
          ["player"] = {
          "UNIT_POWER",
          "UNIT_ENERGY",
          "UNIT_MANA",
          "UNIT_RAGE"
          }
        }
      }
    end,
    loadInternalEventFunc = function(trigger)
      local spellName = type(trigger.spellName) ~= "table" and trigger.spellName or 0
      if type(trigger.spellName) == "number" then
        spellName = GetSpellInfo(spellName)
      end
      if spellName == nil then return {} end
      return { "SPELL_COOLDOWN_CHANGED:" .. spellName }
    end,
    force_events = "SPELL_UPDATE_USABLE",
    name = L["Spell Usable"],
    statesParameter = "one",
    loadFunc = function(trigger)
      local spellName = type(trigger.spellName) ~= "table" and trigger.spellName or 0
      if type(trigger.spellName) == "number" then
        spellName = GetSpellInfo(spellName)
      end
      WeakAuras.WatchSpellCooldown(spellName, false);
    end,
    init = function(trigger)
      local spellName = type(trigger.spellName) ~= "table" and trigger.spellName or 0
      if type(trigger.spellName) == "number" then
        spellName = GetSpellInfo(spellName) or ""
      end
      local ret = [=[
        local spellName = %s
        local effectiveSpellId = spellName
        local name, _, icon = GetSpellInfo(effectiveSpellId)
      ]=]

      if trigger.use_ignoreSpellCooldown then
        ret = ret .. [=[local active = IsUsableSpell(spellName or "")]=]
      else
        ret = ret .. [=[
        local startTime, duration, gcdCooldown, readyTime, paused = WeakAuras.GetSpellCooldown(effectiveSpellId)
        local charges, maxCharges, spellCount, chargeGainTime, chargeLostTime = WeakAuras.GetSpellCharges(effectiveSpellId)
        local stacks = maxCharges and maxCharges > 1 and charges
                       or spellCount and spellCount > 0 and spellCount
                       or nil
        if (charges == nil) then
          charges = (duration == 0 or gcdCooldown) and 1 or 0;
        end
        local ready = (startTime == 0 and not paused) or charges > 0
        local active = IsUsableSpell(spellName or "") and ready
        ]=]
      end
      if(trigger.use_targetRequired) then
        ret = ret.."active = active and WeakAuras.IsSpellInRange(spellName or '', 'target')\n";
      end
      if(trigger.use_inverse) then
        ret = ret.."active = not active\n";
      end

      if (type(spellName) == "string") then
        spellName = string.format("%q", spellName)
      end

      return ret:format(spellName)
    end,
    GetNameAndIcon = GetNameAndIconForSpellName,
    args = {
      {
        name = "spellName",
        display = L["Spell"],
        required = true,
        type = "spell",
        test = "true",
        showExactOption = true,
        store = true
      },
      -- This parameter uses the IsSpellInRange API function, but it does not check spell range at all
      -- IsSpellInRange returns nil for invalid targets, 0 for out of range, 1 for in range (0 and 1 are both "positive" values)
      {
        name = "targetRequired",
        display = L["Require Valid Target"],
        type = "toggle",
        test = "true"
      },
      {
        name = "ignoreSpellCooldown",
        display = L["Ignore Spell Cooldown/Charges"],
        type = "toggle",
        test = "true"
      },
      {
        name = "charges",
        display = L["Charges"],
        type = "number",
        enable = function(trigger) return not trigger.use_inverse and not trigger.use_ignoreSpellCooldown end,
        store = true,
        conditionType = "number",
      },
      {
        name = "spellCount",
        display = L["Spell Count"],
        type = "number",
        enable = function(trigger) return not trigger.use_ignoreSpellCooldown end,
        store = true,
        conditionType = "number",
      },
      {
        hidden = true,
        name = "readyTime",
        display = L["Since Ready"],
        conditionType = "elapsedTimer",
        store = true,
        test = "true",
        enable = function(trigger) return not trigger.use_ignoreSpellCooldown end,
      },
      {
        hidden = true,
        name = "chargeGainTime",
        display = L["Since Charge Gain"],
        conditionType = "elapsedTimer",
        store = true,
        test = "true",
        enable = function(trigger) return not trigger.use_ignoreSpellCooldown end,
      },
      {
        hidden = true,
        name = "chargeLostTime",
        display = L["Since Charge Lost"],
        conditionType = "elapsedTimer",
        store = true,
        test = "true",
        enable = function(trigger) return not trigger.use_ignoreSpellCooldown end,
      },
      {
        name = "inverse",
        display = L["Inverse"],
        type = "toggle",
        test = "true",
        reloadOptions = true
      },
      {
        name = "spellInRange",
        display = L["Spell in Range"],
        hidden = true,
        test = "true",
        conditionType = "bool",
        conditionTest = function(state, needle)
          return state and state.show and (UnitExists('target') and state.spellName and WeakAuras.IsSpellInRange(state.spellName, 'target') == needle)
        end,
        conditionEvents = AddTargetConditionEvents({
          "WA_SPELL_RANGECHECK",
        })
      },
      {
        name = "name",
        display = L["Name"],
        hidden = true,
        init = "name",
        test = "true",
        store = true,
        conditionType = "string"
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
      {
        name = "stacks",
        display = L["Stacks"],
        hidden = true,
        init = "stacks",
        test = "true",
        store = true,
        conditionType = "number",
        enable = function(trigger) return not trigger.use_ignoreSpellCooldown end,
      },
      {
        hidden = true,
        test = "active"
      }
    },
    hasSpellID = true,
    automaticrequired = true,
    progressType = "none"
  },
  ["Talent Known"] = {
    type = "unit",
    events = {
      ["events"] = {"PLAYER_TALENT_UPDATE", "SPELL_UPDATE_USABLE"}
    },
    force_events = "PLAYER_TALENT_UPDATE",
    name = L["Talent Known"],
    init = function(trigger)
      local inverse = trigger.use_inverse
      local ret = {}
      if (trigger.use_talent) then
        -- Single selection
        local index = trigger.talent and trigger.talent.single;
        local tier = index and ceil(index / MAX_NUM_TALENTS)
        local column = index and ((index - 1) % MAX_NUM_TALENTS + 1)
        table.insert(ret, ([[
          local tier = %s;
          local column = %s;
          local active = false
          local name, icon, _, _, rank = GetTalentInfo(tier, column)
          if rank and rank > 0 then
            active = true;
            activeName = name;
            activeIcon = icon;
          end
        ]]):format(tier or 0, column or 0))
        if (inverse) then
          table.insert(ret, [[
            active = not (active);
          ]])
        end
      elseif (trigger.use_talent == false) then
        if (trigger.talent.multi) then
          table.insert(ret, [[
            local active = true
            local activeIcon, activeName, _
          ]])
          table.insert(ret, [[
            local tier
            local column
          ]])
          for index, value in pairs(trigger.talent.multi) do
            local tier = index and ceil(index / MAX_NUM_TALENTS)
            local column = index and ((index - 1) % MAX_NUM_TALENTS + 1)
            table.insert(ret, ([[
              if (not active) then
                tier = %s
                column = %s
                local name, icon, _, _, rank = GetTalentInfo(tier, column)
                if rank > 0 then
                  active = true;
                  activeName = name;
                  activeIcon = icon;
                end
              end
            ]]):format(tier, column))
          end
          if (inverse) then
            table.insert(ret, [[
              active = not (active);
            ]])
          end
        end
      end
      return table.concat(ret)
    end,
    args = {
      {
        name = "talent",
        display = L["Talent"],
        type = "multiselect",
        values = function(trigger)
          local class = select(2, UnitClass("player"));
          if Private.talent_types_specific and Private.talent_types_specific[class] then
            return Private.talent_types_specific[class];
          end
        end,
        test = "active",
        reloadOptions = true,
      },
      {
        name = "inverse",
        display = L["Inverse"],
        type = "toggle",
        test = "true",
      },
      {
        hidden = true,
        name = "icon",
        init = "activeIcon",
        store = "true",
        test = "true"
      },
      {
        hidden = true,
        name = "name",
        init = "activeName",
        store = "true",
        test = "true"
      },
    },
    automaticrequired = true,
    statesParameter = "one",
    progressType = "none"
  },
  ["Class/Spec"] = {
    type = "unit",
    events = {},
    internal_events = {"UNIT_SPEC_CHANGED_player", "WA_DELAYED_PLAYER_ENTERING_WORLD"},
    force_events = "UNIT_SPEC_CHANGED_player",
    name = L["Class and Specialization"],
    init = function(trigger)
      local class = select(2, UnitClass("player")) or "UNKNOWN"
      return ([[
        local specName = Private.ExecEnv.GetUnitTalentSpec("player") or "Unknown"
        local specId = Private.ExecEnv.GetSpecID("%s" .. specName)
        local specIcon = Private.ExecEnv.GetSpecIcon(specId)
      ]]):format(class)
    end,
    args = {
      {
        name = "specId",
        display = L["Class and Specialization"],
        type = "multiselect",
        values = "spec_types_all",
        store = "true",
        conditionType = "select",
      },
      {
        hidden = true,
        name = "icon",
        init = "specIcon",
        store = "true",
        test = "true"
      },
      {
        hidden = true,
        name = "name",
        init = "specName",
        store = "true",
        test = "true"
      },
    },
    automaticrequired = true,
    statesParameter = "one",
    progressType = "none"
  },
  ["Totem"] = {
    type = "spell",
    events = {
      ["events"] = {
        "PLAYER_TOTEM_UPDATE",
        "PLAYER_ENTERING_WORLD"
      }
    },
    internal_events = {
      "COOLDOWN_REMAINING_CHECK",
    },
    force_events = "PLAYER_ENTERING_WORLD",
    name = L["Totem"],
    statesParameter = "full",
    progressType = "timed",
    triggerFunction = function(trigger)
      local ret = [[return
      function (states)
        local totemType = %s;
        local triggerTotemName = %q
        local triggerTotemPattern = %q
        local triggerTotemPatternOperator = %q
        local triggerTotemIcon = %s
        local triggerTotemIconOperator = %q
        local clone = %s
        local inverse = %s
        local remainingCheck = %s

        local function checkActive(remaining)
          return remaining %s remainingCheck;
        end

        if (totemType) then -- Check a specific totem slot
          local _, totemName, startTime, duration, icon = GetTotemInfo(totemType);
          active = (startTime and startTime ~= 0);

          if not Private.ExecEnv.CheckTotemName(totemName, triggerTotemName, triggerTotemPattern, triggerTotemPatternOperator) then
            active = false;
          end

          if not Private.ExecEnv.CheckTotemIcon(icon, triggerTotemIcon, triggerTotemIconOperator) then
            active = false
          end

          if (inverse) then
            active = not active;
            if (triggerTotemName) then
              icon = select(3, GetSpellInfo(triggerTotemName));
            end
          elseif (active and remainingCheck) then
            local expirationTime = startTime and (startTime + duration) or 0;
            local remainingTime = expirationTime - GetTime()
            if (remainingTime >= remainingCheck) then
              Private.ExecEnv.ScheduleScan(expirationTime - remainingCheck);
            end
            active = checkActive(remainingTime);
          end
          states[""] = states[""] or {}
          local state = states[""];
          state.show = active;
          state.changed = true;
          if (active) then
            state.name = totemName;
            state.totemName = totemName;
            state.progressType = "timed";
            state.duration = duration;
            state.expirationTime = startTime and (startTime + duration);
            state.icon = icon;
          end
        elseif inverse then -- inverse without a specific slot
          local found = false;
          for i = 1, 4 do
            local _, totemName, startTime, duration, icon = GetTotemInfo(i);
            if ((startTime and startTime ~= 0)
              and Private.ExecEnv.CheckTotemName(totemName, triggerTotemName, triggerTotemPattern, triggerTotemPatternOperator)
              and Private.ExecEnv.CheckTotemIcon(icon, triggerTotemIcon, triggerTotemIconOperator)
            ) then
              found = true;
            end
          end
          local cloneId = "";
          states[cloneId] = states[cloneId] or {};
          local state = states[cloneId];
          state.show = not found;
          state.changed = true;
          state.name = triggerTotemName;
          state.totemName = triggerTotemName;
          if (triggerTotemName) then
            state.icon = select(3, GetSpellInfo(triggerTotemName));
          end
        else -- check all slots
          for i = 1, 4 do
            local _, totemName, startTime, duration, icon = GetTotemInfo(i);
            active = (startTime and startTime ~= 0);

            if not Private.ExecEnv.CheckTotemName(totemName, triggerTotemName, triggerTotemPattern, triggerTotemPatternOperator)
              or not Private.ExecEnv.CheckTotemIcon(icon, triggerTotemIcon, triggerTotemIconOperator)
            then
              active = false;
            end
            if (active and remainingCheck) then
              local expirationTime = startTime and (startTime + duration) or 0;
              local remainingTime = expirationTime - GetTime()
              if (remainingTime >= remainingCheck) then
                Private.ExecEnv.ScheduleScan(expirationTime - remainingCheck);
              end
              active = checkActive(remainingTime);
            end

            local cloneId = clone and tostring(i) or "";
            states[cloneId] = states[cloneId] or {};
            local state = states[cloneId];
            state.show = active;
            state.changed = true;
            if (active) then
              state.name = totemName;
              state.totemName = totemName;
              state.progressType = "timed";
              state.duration = duration;
              state.expirationTime = startTime and (startTime + duration);
              state.icon = icon;
            end
            if (active and not clone) then
              break;
            end
          end
        end
        return true;
      end
      ]];
      local totemName = tonumber(trigger.totemName) and GetSpellInfo(tonumber(trigger.totemName)) or trigger.totemName;
      ret = ret:format(trigger.use_totemType and tonumber(trigger.totemType) or "nil",
        trigger.use_totemName and totemName or "",
        trigger.use_totemNamePattern and trigger.totemNamePattern or "",
        trigger.use_totemNamePattern and trigger.totemNamePattern_operator or "",
        trigger.use_icon and trigger.icon or "nil",
        trigger.use_icon and trigger.icon_operator or "",
        trigger.use_clones and "true" or "false",
        trigger.use_inverse and "true" or "false",
        trigger.use_remaining and tonumber(trigger.remaining or 0) or "nil",
        trigger.use_remaining and trigger.remaining_operator or "<");
      return ret;
    end,
    args = {
      {
        name = "totemType",
        display = L["Totem Number"],
        type = "select",
        values = "totem_types"
      },
      {
        name = "totemName",
        display = L["Totem Name"],
        type = "string",
        conditionType = "string",
        store = true,
        desc = L["Enter a name or a spellId"]
      },
      {
        name = "totemNamePattern",
        display = L["Totem Name Pattern Match"],
        type = "longstring",
      },
      {
        name = "icon",
        display = L["Totem Icon"],
        type = "number",
        conditionType = "number",
        operator_types = "only_equal",
        store = true,
      },
      {
        name = "inverse",
        display = L["Inverse"],
        type = "toggle",
        test = "true",
      },
      {
        name = "clones",
        display = L["Clone per Match"],
        type = "toggle",
        test = "true",
        enable = function(trigger) return not (trigger.use_totemType or trigger.use_inverse) end,
      },
      {
        name = "remaining",
        display = L["Remaining Time"],
        type = "number",
        enable = function(trigger) return not(trigger.use_inverse) end
      },
    },
    automaticrequired = true
  },
  ["Item Count"] = {
    type = "item",
    events = {
      ["events"] = {
        "BAG_UPDATE",
        "BAG_UPDATE_COOLDOWN",
        "PLAYER_ENTERING_WORLD"
      }
    },
    internal_events = {
      "ITEM_COUNT_UPDATE",
    },
    force_events = "BAG_UPDATE",
    name = L["Item Count"],
    loadFunc = function(trigger)
      if(trigger.use_includeCharges) then
        WeakAuras.RegisterItemCountWatch();
      end
    end,
    init = function(trigger)
      local itemName = type(trigger.itemName) == "number" and trigger.itemName or string.format("%q", trigger.itemName or "0")
      local ret = [[
        local itemName = %s
        local exactSpellMatch = %s
        if not exactSpellMatch and tonumber(itemName) then
          itemName = GetItemInfo(itemName)
        end
        local count = GetItemCount(itemName or "", %s, %s);
      ]];
      return ret:format(
        itemName,
        trigger.use_exact_itemName and "true" or "nil",
        trigger.use_includeBank and "true" or "nil",
        trigger.use_includeCharges and "true" or "nil"
      )
    end,
    args = {
      {
        name = "itemName",
        required = true,
        display = L["Item"],
        type = "item",
        showExactOption = true,
        test = "true"
      },
      --[[{ maybe some day
        name = "itemId",
        display = WeakAuras.newFeatureString .. L["ItemId"],
        hidden = true,
        init = "itemId",
        test = "true",
        store = true,
        conditionType = "number",
        operator_types = "only_equal",
      },]]
      {
        name = "name",
        display = L["Name"],
        init = "itemName",
        type = "string",
        hidden = true,
        store = true,
        test = "true",
        conditionType = "string"
      },
      {
        name = "includeBank",
        display = L["Include Bank"],
        type = "toggle",
        test = "true"
      },
      {
        name = "includeCharges",
        display = L["Include Charges"],
        type = "toggle",
        test = "true"
      },
      {
        name = "count",
        display = L["Item Count"],
        type = "number"
      },
      {
        name = "stacks",
        display = L["Stacks"],
        init = "count",
        hidden = true,
        store = true,
        test = "true",
        conditionType = "number"
      },
      {
        name = "value",
        init = "count",
        hidden = true,
        store = true,
        test = "true",
        display = L["Progress Value"],
        conditionType = "number"
      },
      {
        name = "total",
        init = 0,
        hidden = true,
        store = true,
        test = "true",
        display = L["Progress Total"],
        conditionType = "number"
      },
      {
        name = "progressType",
        init = "'static'",
        hidden = true,
        store = true,
        test = "true",
      },
      {
        name = "icon",
        init = "GetItemIcon(itemName or '')",
        hidden = true,
        store = true,
        test = "true"
      },
      {
        name = "name",
        init = "itemName and itemName ~= '' and GetItemInfo(itemName) or itemName",
        hidden = true,
        store = true,
        test = "true"
      },
    },
    statesParameter = "one",
    hasItemID = true,
    automaticrequired = true,
    progressType = "static"
  },
  ["Stance/Form/Aura"] = {
    type = "unit",
    events = function()
      local events = {
        "UPDATE_SHAPESHIFT_FORM",
        "UPDATE_SHAPESHIFT_COOLDOWN"
      }
      if WeakAuras.IsClassicPlus() then -- Stances workaround for Epoch
        tinsert(events, "ACTIONBAR_SLOT_CHANGED")
      end
      return { ["events"] = events }
    end,
    internal_events = { "WA_DELAYED_PLAYER_ENTERING_WORLD" },
    force_events = "WA_DELAYED_PLAYER_ENTERING_WORLD",
    name = L["Stance/Form/Aura"],
    init = function(trigger)
      local inverse = trigger.use_inverse;
      local ret = {[[
        local form = GetShapeshiftForm()
        local active = false
      ]]}
      if trigger.use_form and trigger.form and trigger.form.single then
        -- Single selection
        table.insert(ret, ([[
          local trigger_form = %d
          active = form == trigger_form
        ]]):format(trigger.form.single))
        if inverse then
          table.insert(ret, [[
            active = not active
          ]])
        end
      elseif trigger.use_form == false and trigger.form and trigger.form.multi then
        for index in pairs(trigger.form.multi) do
          table.insert(ret, ([[
            if not active then
              local index = %d
              active = form == index
            end
          ]]):format(index))
        end
        if inverse then
          table.insert(ret, [[
            active = not active
          ]])
        end
      elseif trigger.use_form == nil then
        table.insert(ret, [[
          active = true
        ]])
      end
      return table.concat(ret)
    end,
    statesParameter = "one",
    args = {
      {
        name = "note",
        type = "description",
        display = "",
        text = function()
          return L["Note: This trigger internally stores the shapeshift position, and thus is incompatible with learning stances on the fly, like e.g. the Gladiator Rune."]
        end,
      },
      {
        name = "form",
        display = L["Form"],
        type = "multiselect",
        values = "form_types",
        test = "active",
        store = true,
        conditionType = "select"
      },
      {
        name = "inverse",
        display = L["Inverse"],
        type = "toggle",
        test = "true",
        enable = function(trigger) return type(trigger.use_form) == "boolean" end
      },
    },
    nameFunc = function(trigger)
      local _, class = UnitClass("player");
      local name
      if(class == trigger.class) then
        local form = GetShapeshiftForm();
        if form > 0 then
          local _, name = GetShapeshiftFormInfo(form);
        else
          name = "Humanoid";
        end
        return name;
      else
        local types = WeakAuras[class:lower().."_form_types"];
        if(types) then
          return types[GetShapeshiftForm()];
        end
      end
    end,
    iconFunc = function(trigger)
      local icon = "Interface\\Icons\\Spell_Nature_WispSplode"
      local form = GetShapeshiftForm()
      if form and form > 0 then
        icon = GetShapeshiftFormInfo(form);
      end
      return icon or "Interface\\Icons\\Spell_Nature_WispSplode"
    end,
    automaticrequired = true,
    progressType = "none"
  },
  ["Weapon Enchant"] = {
    type = "item",
    events = {},
    internal_events = {
      "TENCH_UPDATE",
    },
    force_events = "TENCH_UPDATE",
    name = L["Weapon Enchant / Fishing Lure"],
    init = function(trigger)
      WeakAuras.TenchInit();

      local ret = [[
        local triggerWeaponType = %q
        local triggerName = %q
        local triggerStack = %s
        local triggerRemaining = %s
        local triggerShowOn = %q
        local expirationTime, duration, name, icon, stacks

        if triggerWeaponType == "main" then
          expirationTime, duration, name, shortenedName, icon, stacks = WeakAuras.GetMHTenchInfo()
        elseif triggerWeaponType == "off" then
          expirationTime, duration, name, shortenedName, icon, stacks = WeakAuras.GetOHTenchInfo()
        elseif triggerWeaponType == "ranged" then
          expirationTime, duration, name, shortenedName, icon, stacks = WeakAuras.GetRangeTenchInfo()
        end

        local remaining = expirationTime and expirationTime - GetTime()

        local nameCheck = triggerName == "" or name and triggerName == name or shortenedName and triggerName == shortenedName
        local stackCheck = not triggerStack or stacks and stacks %s triggerStack
        local remainingCheck = not triggerRemaining or remaining and remaining %s triggerRemaining
        local found = expirationTime and nameCheck and stackCheck and remainingCheck

        if(triggerRemaining and remaining and remaining >= triggerRemaining and remaining > 0) then
          Private.ExecEnv.ScheduleScan(expirationTime - triggerRemaining, "TENCH_UPDATE");
        end

        if not found then
          expirationTime = nil
          duration = nil
          remaining = nil
        end
      ]];

      local showOnActive = trigger.showOn == 'showOnActive' or not trigger.showOn

      return ret:format(trigger.weapon or "main",
      trigger.use_enchant and trigger.enchant or "",
      showOnActive and trigger.use_stacks and tonumber(trigger.stacks or 0) or "nil",
      showOnActive and trigger.use_remaining and tonumber(trigger.remaining or 0) or "nil",
      trigger.showOn or "showOnActive",
      trigger.stacks_operator or "<",
      trigger.remaining_operator or "<")
    end,
    args = {
      {
        name = "weapon",
        display = L["Weapon"],
        type = "select",
        values = "weapon_types",
        test = "true",
        default = "main",
        required = true
      },
      {
        name = "enchant",
        display = L["Weapon Enchant"],
        desc = L["Enchant Name or ID"],
        type = "string",
        test = "true"
      },
      {
        name = "enchantID",
        hidden = true,
        test = "true",
        display = L["Enchant ID"],
        store = true,
        conditionType = "number",
        operator_types = "only_equal",
        noProgressSource = true
      },
      {
        name = "stacks",
        display = L["Stack Count"],
        type = "number",
        conditionType = "number",
        test = "true",
        store = true
      },
      {
        name = "duration",
        hidden = true,
        init = "duration",
        test = "true",
        store = true
      },
      {
        name = "expirationTime",
        init = "expirationTime",
        hidden = true,
        test = "true",
        store = true
      },
      {
        name = "progressType",
        hidden = true,
        init = "duration and 'timed'",
        test = "true",
        store = true
      },
      {
        name = "name",
        hidden = true,
        init = "name",
        test = "true",
        store = true
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
      {
        name = "enchanted",
        display = L["Enchanted"],
        hidden = true,
        init = "found == true",
        test = "true",
        store = true,
        conditionType = "bool",
        conditionTest = function(state, needle)
          return state and state.show and state.enchanted == (needle == 1)
        end,
      },
      {
        name = "remaining",
        display = L["Remaining Time"],
        type = "number",
        test = "true",
        enable = function(trigger)
          return not trigger.showOn or trigger.showOn == "showOnActive"
        end
      },
      {
        name = "showOn",
        display = L["Show On"],
        type = "select",
        values = "weapon_enchant_types",
        test = 'true',
        default = "showOnActive",
        required = true
      },
      {
        hidden = true,
        test = "(triggerShowOn == 'showOnActive' and found) " ..
        "or (triggerShowOn == 'showOnMissing' and not found) "  ..
        "or (triggerShowOn == 'showAlways')"
      }
    },
    automaticrequired = true,
    progressType = "timed",
    statesParameter = "one"
  },
  ["Chat Message"] = {
    type = "event",
    events = function(trigger)
      if trigger.use_messageType and trigger.messageType and Private.chat_message_types[trigger.messageType] then
        local events = {trigger.messageType}
        if Private.chat_message_leader_event[trigger.messageType] then
          table.insert(events, Private.chat_message_leader_event[trigger.messageType])
        end
        if trigger.messageType == "CHAT_MSG_EMOTE" then
          table.insert(events, "CHAT_MSG_TEXT_EMOTE")
        end
        return { events = events }
      end
      return {
      ["events"] = {
        "CHAT_MSG_BATTLEGROUND",
        "CHAT_MSG_BATTLEGROUND_LEADER",
        "CHAT_MSG_BG_SYSTEM_ALLIANCE",
        "CHAT_MSG_BG_SYSTEM_HORDE",
        "CHAT_MSG_BG_SYSTEM_NEUTRAL",
        "CHAT_MSG_BN_WHISPER",
        "CHAT_MSG_CHANNEL",
        "CHAT_MSG_EMOTE",
        "CHAT_MSG_GUILD",
        "CHAT_MSG_MONSTER_EMOTE",
        "CHAT_MSG_MONSTER_PARTY",
        "CHAT_MSG_MONSTER_SAY",
        "CHAT_MSG_MONSTER_WHISPER",
        "CHAT_MSG_MONSTER_YELL",
        "CHAT_MSG_OFFICER",
        "CHAT_MSG_PARTY",
        "CHAT_MSG_PARTY_LEADER",
        "CHAT_MSG_RAID",
        "CHAT_MSG_RAID_LEADER",
        "CHAT_MSG_RAID_BOSS_EMOTE",
        "CHAT_MSG_RAID_BOSS_WHISPER",
        "CHAT_MSG_RAID_WARNING",
        "CHAT_MSG_SAY",
        "CHAT_MSG_WHISPER",
        "CHAT_MSG_YELL",
        "CHAT_MSG_SYSTEM",
        "CHAT_MSG_TEXT_EMOTE",
        "CHAT_MSG_LOOT",
      }
    }
    end,
    name = L["Chat Message"],
    init = function(trigger)
      local ret = [[
        if (event:find('LEADER')) then
          event = event:sub(1, -8);
        end
        if (event == 'CHAT_MSG_TEXT_EMOTE') then
          event = 'CHAT_MSG_EMOTE';
        end
        local use_cloneId = %s;
      ]];
      return ret:format(trigger.use_cloneId and "true" or "false");
    end,
    statesParameter = "all",
    args = {
      {
        name = "messageType",
        display = L["Message Type"],
        type = "select",
        values = "chat_message_types",
        sorted = true,
        test = "event == %q",
      },
      {
        name = "message",
        display = L["Message"],
        init = "arg",
        type = "longstring",
        canBeCaseInsensitive = true,
        store = true,
        conditionType = "string",
      },
      {
        name = "sourceName",
        display = L["Source Name"],
        init = "arg",
        type = "string",
        store = true,
        conditionType = "string",
      },
      { -- language Name
      },
      { -- Channel Name
      },
      {
        name = "destName",
        display = L["Destination Name"],
        init = "arg",
        type = "string",
        store = true,
        conditionType = "string",
      },
      {
        -- flags
      },
      {
        -- zone Channel id
      },
      {
        -- channel index
      },
      {
        -- channel base name
      },
      {
        -- language id
      },
      {
        -- line id
      },
      {
        name = "sourceGUID",
        display = L["Source GUID"],
        init = "arg",
        store = true,
        hidden = true,
        test = "true",
        formatter = "guid",
        formatterArgs = { color = "class" }
      },
      {
        name = "cloneId",
        display = L["Clone per Event"],
        type = "toggle",
        test = "true",
        init = "use_cloneId and WeakAuras.GetUniqueCloneId() or ''",
        reloadOptions = true
      },
    },
    countEvents = true,
    delayEvents = true,
    timedrequired = true,
    progressType = "timed"
  },
  ["Spell Cast Succeeded"] = {
    type = "event",
    events = function(trigger)
      local result = {}
      local unit = trigger.unit
      AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_SUCCEEDED")
      return result
    end,
    name = L["Spell Cast Succeeded"],
    statesParameter = "unit",
    args = {
      {
        name = "unit",
        init = "arg",
        display = L["Caster Unit"],
        type = "unit",
        test = "true",
        values = "actual_unit_types_cast",
        store = true,
        conditionType = "select",
        conditionTest = function(state, needle, op)
          return state and state.show and (UnitIsUnit(needle, state.unit or '') == (op == "=="))
        end
      },
      { -- castGUID
      },
      {
        name = "spellNames",
        init = "arg",
        display = L["Name(s)"],
        type = "spell",
        multiEntry = {
          operator = "preamble",
          preambleAdd = "spellChecker:AddName(%q)"
        },
        preamble = "local spellChecker = Private.ExecEnv.CreateSpellChecker()",
        preambleGroup = "spell",
        test = "spellChecker:Check(spellNames)",
        noValidation = true,
      },
      {
        name = "spellId",
        display = L["Exact Spell ID(s)"],
        type = "spell",
        init = "spellNames",
        store = true,
        multiEntry = {
          operator = "preamble",
          preambleAdd = "spellChecker:AddName(%q)"
        },
        preamble = "local spellChecker = Private.ExecEnv.CreateSpellChecker()",
        preambleGroup = "spell",
        test = "spellChecker:CheckName(spellNames)",
        --conditionType = "number", -- Disabled because input would be string and would break imports to retail
        noProgressSource = true
      },
      {
        name = "icon",
        hidden = true,
        init = "(spellId and select(3, GetSpellInfo(spellId))) or 'Interface\\\\Icons\\\\INV_Misc_QuestionMark'",
        store = true,
        test = "true"
      },
      {
        name = "name",
        hidden = true,
        init = "GetSpellInfo(spellId or 0)",
        store = true,
        test = "true"
      },
    },
    countEvents = true,
    delayEvents = true,
    timedrequired = true,
    progressType = "timed"
  },
  ["Ready Check"] = {
    type = "event",
    events = {
      ["events"] = {"READY_CHECK"}
    },
    name = L["Ready Check"],
    args = {},
    statesParameter = "one",
    delayEvents = true,
    timedrequired = true,
    progressType = "timed"
  },
  ["Combat Events"] = {
    type = "event",
    events = {
      ["events"] = {
        "PLAYER_REGEN_ENABLED",
        "PLAYER_REGEN_DISABLED"
      }
    },
    name = L["Entering/Leaving Combat"],
    args = {
      {
        name = "eventtype",
        required = true,
        display = L["Type"],
        type = "select",
        values = "combat_event_type",
        test = "event == %q"
      }
    },
    statesParameter = "one",
    countEvents = true,
    delayEvents = true,
    timedrequired = true,
    progressType = "timed"
  },
  ["Encounter Events"] = {
    type = "event",
    events = {
      ["events"] = {
        "ENCOUNTER_START",
        "ENCOUNTER_END"
      }
    },
    name = WeakAuras.newFeatureString..L["Entering/Leaving Encounter"],
    args = {
      {
        name = "note",
        type = "description",
        display = "",
        text = constants.encounterDBMDesc
      },
      {
        name = "eventtype",
        required = true,
        display = L["Type"],
        type = "select",
        values = "encounter_event_type",
        test = "event == %q",
        reloadOptions = true
      },
      {
        name = "encounterId",
        display = L["Id"],
        type = "string",
        desc = Private.get_encounters_list,
        validate = WeakAuras.ValidateNumeric,
        conditionType = "number",
        store = true,
        init = "arg"
      },
      {
        name = "encounterName",
        display = L["Name"],
        type = "string",
        conditionType = "string",
        store = true,
        init = "arg"
      },
      {
        name = "difficulty",
        display = L["Difficulty"],
        type = "select",
        values = "difficulty_types",
        test = "%q == WeakAuras.InstanceDifficulty()",
        conditionType = "select",
        conditionTest = function(state, needle)
          return WeakAuras.InstanceDifficulty() == needle
        end,
        store = true,
        init = "arg"
      },
      {},
      {
        name = "success",
        display = L["Success"],
        type = "toggle",
        conditionType = "bool",
        enable = function(trigger)
          return trigger.eventtype == "ENCOUNTER_END"
        end,
        store = true,
        test = "success == 1",
        conditionTest = function(state, needle)
          return state and (state.success == needle)
        end,
        init = "arg"
      }
    },
    statesParameter = "one",
    countEvents = true,
    delayEvents = true,
    timedrequired = true,
    progressType = "timed"
  },
  ["Death Knight Rune"] = {
    type = "unit",
    events = {
      ["events"] = {
        "RUNE_POWER_UPDATE",
        "RUNE_TYPE_UPDATE"
      }
    },
    internal_events = {
      "RUNE_COOLDOWN_READY",
      "RUNE_COOLDOWN_CHANGED",
      "RUNE_COOLDOWN_STARTED",
      "COOLDOWN_REMAINING_CHECK",
      "WA_DELAYED_PLAYER_ENTERING_WORLD"
    },
    force_events = "RUNE_COOLDOWN_FORCE",
    name = L["Death Knight Rune"],
    loadFunc = function(trigger)
      trigger.rune = trigger.rune or 0;
      if (trigger.use_rune) then
        WeakAuras.WatchRuneCooldown(trigger.rune);
      else
        for i = 1, 6 do
          WeakAuras.WatchRuneCooldown(i);
        end
      end
    end,
    init = function(trigger)
      trigger.rune = trigger.rune or 0;
      WeakAuras.WatchRuneCooldown(trigger.rune);
      local ret = [[
        local rune = %s;
        local genericShowOn = %s
        local includeDeathRunes = %s;
        local startTime, duration = WeakAuras.GetRuneCooldown(rune);
        local numBloodRunes = 0;
        local numUnholyRunes = 0;
        local numFrostRunes = 0;
        local numDeathRunes = 0;
        local numRunes = 0;
        local isDeathRune = GetRuneType(rune) == 4
        for index = 1, 6 do
          local startTime = GetRuneCooldown(index);
          if startTime == 0 then
            numRunes = numRunes + 1;
            local runeType = GetRuneType(index)
            if runeType == 1 then
              numBloodRunes = numBloodRunes + 1;
            elseif runeType == 2 then
              numFrostRunes = numFrostRunes + 1;
            elseif runeType == 3 then
              numUnholyRunes = numUnholyRunes + 1;
            elseif runeType == 4 then
              numDeathRunes = numDeathRunes + 1;
            end
          end
        end
        if includeDeathRunes then
          numBloodRunes  = numBloodRunes  + numDeathRunes;
          numUnholyRunes = numUnholyRunes + numDeathRunes;
          numFrostRunes  = numFrostRunes  + numDeathRunes;
        end
      ]];
      if trigger.use_remaining then
        local ret2 = [[
          local expirationTime = startTime + duration
          local remaining = expirationTime - GetTime();
          local remainingCheck = %s;
          if(remaining >= remainingCheck and remaining > 0) then
            Private.ExecEnv.ScheduleScan(expirationTime - remainingCheck);
          end
        ]];
        ret = ret..ret2:format(tonumber(trigger.remaining or 0) or 0);
      end
      return ret:format(
        trigger.rune,
        "[[" .. (trigger.genericShowOn or "") .. "]]",
        (trigger.use_includeDeathRunes and "true" or "false")
      );
    end,
    statesParameter = "one",
    args = {
      {
        name = "rune",
        display = L["Rune"],
        type = "select",
        values = "rune_specific_types",
        test = "(genericShowOn == \"showOnReady\" and (startTime == 0)) " ..
        "or (genericShowOn == \"showOnCooldown\" and startTime > 0) " ..
        "or (genericShowOn == \"showAlways\")",
        reloadOptions = true
      },
      {
        name = "isDeathRune",
        display = L["Is Death Rune"],
        type = "tristate",
        init = "isDeathRune",
        store = true,
        conditionType = "bool",
        enable = function(trigger) return trigger.use_rune end,
      },
      {
        name = "remaining",
        display = L["Remaining Time"],
        type = "number",
        enable = function(trigger) return trigger.use_rune and not(trigger.genericShowOn == "showOnReady") end
      },
      {
        name = "genericShowOn",
        display =  L["Show"],
        type = "select",
        values = "cooldown_progress_behavior_types",
        test = "true",
        enable = function(trigger) return trigger.use_rune end,
        required = true
      },
      {
        name = "runesCount",
        display = L["Rune Count"],
        type = "number",
        init = "numRunes",
        enable = function(trigger) return not trigger.use_rune end
      },
      {
        hidden = true,
        name = "onCooldown",
        test = "true",
        display = L["On Cooldown"],
        conditionType = "bool",
        conditionTest = function(state, needle)
          return state and state.show and (state.expirationTime and state.expirationTime > GetTime()) == (needle == 1)
        end,
        enable = function(trigger) return trigger.use_rune end
      },
      {
        name = "bloodRunes",
        display = L["Rune Count - Blood"],
        type = "number",
        init = "numBloodRunes",
        store = true,
        conditionType = "number",
        enable = function(trigger) return not trigger.use_rune end,
      },
      {
        name = "frostRunes",
        display = L["Rune Count - Frost"],
        type = "number",
        init = "numFrostRunes",
        store = true,
        conditionType = "number",
        enable = function(trigger) return not trigger.use_rune end,
      },
      {
        name = "unholyRunes",
        display = L["Rune Count - Unholy"],
        type = "number",
        init = "numUnholyRunes",
        store = true,
        conditionType = "number",
        enable = function(trigger) return not trigger.use_rune end,
      },
      {
        name = "includeDeathRunes",
        display = L["Include Death Runes"],
        type = "toggle",
        test = "true",
        enable = function(trigger) return trigger.use_bloodRunes or trigger.use_unholyRunes or trigger.use_frostRunes end,
      },
    },
    durationFunc = function(trigger)
      if trigger.use_rune then
        local startTime, duration = WeakAuras.GetRuneCooldown(trigger.rune)
        return duration, startTime + duration
      else
        local numRunes = 0;
        for index = 1, 6 do
          if GetRuneCooldown(index) == 0 then
            numRunes = numRunes + 1;
          end
        end
        return numRunes, 6, true;
      end
    end,
    stacksFunc = function(trigger)
      local numRunes = 0;
      for index = 1, 6 do
        if GetRuneCooldown(index) == 0 then
          numRunes = numRunes  + 1;
        end
      end
      return numRunes;
    end,
    nameFunc = function(trigger)
      local runeNames = { L["Blood"], L["Frost"], L["Unholy"], L["Death"] }
      return runeNames[GetRuneType(trigger.rune)];
    end,
    iconFunc = function(trigger)
      if trigger.rune then
        local runeIcons = {
          "Interface\\PlayerFrame\\UI-PlayerFrame-Deathknight-Blood",
          "Interface\\PlayerFrame\\UI-PlayerFrame-Deathknight-Frost",
          "Interface\\PlayerFrame\\UI-PlayerFrame-Deathknight-Unholy",
          "Interface\\PlayerFrame\\UI-PlayerFrame-Deathknight-Death"
        };
        return runeIcons[GetRuneType(trigger.rune)];
      end
    end,
    automaticrequired = true,
    progressType = function(trigger)
      if trigger.use_rune then
        return "timed"
      else
        return "static"
      end
    end
  },
  ["Item Equipped"] = {
    type = "item",
    events = {
      ["events"] = {
        "PLAYER_EQUIPMENT_CHANGED",
      },
      ["unit_events"] = {
        ["player"] = {"UNIT_INVENTORY_CHANGED"}
      }
    },
    internal_events = { "WA_DELAYED_PLAYER_ENTERING_WORLD", },
    force_events = "UNIT_INVENTORY_CHANGED",
    name = L["Item Equipped"],
    init = function(trigger)
      local itemName = type(trigger.itemName) == "number" and trigger.itemName or string.format("%q", trigger.itemName or "0")

      local ret = [[
        local inverse = %s
        local triggerItemName = %s
        local icon = GetItemIcon(triggerItemName) or ""
        local itemSlot = %s
      ]]

      ret = ret ..[[
        local itemName = triggerItemName
        local equipped = WeakAuras.CheckForItemEquipped(triggerItemName, itemSlot)
      ]]

      return ret:format(trigger.use_inverse and "true" or "false", itemName, trigger.use_itemSlot and trigger.itemSlot or "nil");
    end,
    GetNameAndIcon = function(trigger)
      local name = GetItemInfo(trigger.itemName or 0)
      local icon = GetItemIcon(trigger.itemName or 0)
      return name, icon
    end,
    statesParameter = "one",
    args = {
      {
        name = "itemName",
        display = L["Item"],
        type = "item",
        required = true,
        test = "true",
        only_exact = true
      },
      --[[{ maybe some day
        name = "itemId",
        display = WeakAuras.newFeatureString .. L["ItemId"],
        hidden = true,
        init = "itemId",
        test = "true",
        store = true,
        conditionType = "number",
        operator_types = "only_equal",
      },]]
      {
        name = "itemSlot",
        display = WeakAuras.newFeatureString .. L["Item Slot"],
        type = "select",
        values = "item_slot_types",
        test = "true",
      },
      {
        name = "inverse",
        display = L["Inverse"],
        type = "toggle",
        test = "true"
      },
      {
        name = "name",
        display = L["Name"],
        hidden = true,
        init = "itemName",
        test = "true",
        store = true,
        conditionType = "string"
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
      {
        hidden = true,
        test = "(inverse and not equipped) or (equipped and not inverse)"
      }
    },
    hasItemID = true,
    automaticrequired = true,
    progressType = "none"
  },
  ["Item Type Equipped"] = {
    type = "item",
    events = {
      ["events"] = {
        "PLAYER_EQUIPMENT_CHANGED",
      },
      ["unit_events"] = {
        ["player"] = {"UNIT_INVENTORY_CHANGED"}
      }
    },
    internal_events = { "WA_DELAYED_PLAYER_ENTERING_WORLD", },
    force_events = "UNIT_INVENTORY_CHANGED",
    name = L["Item Type Equipped"],
    args = {
      {
        name = "itemTypeName",
        display = L["Item Type"],
        type = "multiselect",
        values = "item_weapon_types",
        required = true,
        test = "Private.ExecEnv.IsEquippedItemType(%s)",
        multiNoSingle = true
      },
    },
    automaticrequired = true,
    progressType = "none"
  },
  ["Equipment Set"] = {
    type = "item",
    events = {
      ["events"] = {
        "PLAYER_EQUIPMENT_CHANGED",
        "WEAR_EQUIPMENT_SET",
        "EQUIPMENT_SETS_CHANGED",
        "EQUIPMENT_SWAP_FINISHED",
      }
    },
    internal_events = {"WA_DELAYED_PLAYER_ENTERING_WORLD"},
    force_events = "PLAYER_EQUIPMENT_CHANGED",
    name = L["Equipment Set Equipped"],
    init = function(trigger)
      trigger.itemSetName = trigger.itemSetName or "";
      local itemSetName = type(trigger.itemSetName) == "string" and ("[=[" .. trigger.itemSetName .. "]=]") or "nil";

      local ret = [[
        local useItemSetName = %s;
        local triggerItemSetName = %s;
        local inverse = %s;
        local partial = %s;

        local itemSetName, icon, numEquipped, numItems = WeakAuras.GetEquipmentSetInfo(useItemSetName and triggerItemSetName or nil, partial);
      ]];

      return ret:format(trigger.use_itemSetName and "true" or "false", itemSetName, trigger.use_inverse and "true" or "false", trigger.use_partial and "true" or "false");
    end,
    GetNameAndIcon = function(trigger)
      local name, icon = WeakAuras.GetEquipmentSetInfo(trigger.use_itemSetName and trigger.itemSetName or nil, true)
      return name, icon
    end,
    statesParameter = "one",
    args = {
      {
        name = "itemSetName",
        display = L["Equipment Set"],
        type = "string",
        test = "true",
        store = true,
        conditionType = "string",
        init = "itemSetName"
      },
      {
        name = "partial",
        display = L["Allow partial matches"],
        type = "toggle",
        test = "true"
      },
      {
        name = "inverse",
        display = L["Inverse"],
        type = "toggle",
        test = "true"
      },
      {
        name = "name",
        display = L["Name"],
        hidden = true,
        init = "name",
        test = "true",
        store = true,
        conditionType = "string"
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
      {
        name = "value",
        init = "numEquipped",
        hidden = true,
        store = true,
        test = "true",
      },
      {
        name = "total",
        init = "numItems",
        hidden = true,
        store = true,
        test = "true",
      },
      {
        name = "progressType",
        init = "'static'",
        hidden = true,
        store = true,
        test = "true",
      },
      {
        hidden = true,
        test = "(inverse and itemSetName == nil) or (not inverse and itemSetName)"
      }
    },
    hasItemID = true,
    automaticrequired = true,
    progressType = "static"
  },
  ["Threat Situation"] = {
    type = "unit",
    events = function(trigger)
      local unit = trigger.unit
      local result = {}
      if unit and unit ~= "none" then
        AddUnitEventForEvents(result, unit, "UNIT_THREAT_LIST_UPDATE")
      else
        AddUnitEventForEvents(result, "player", "UNIT_THREAT_SITUATION_UPDATE")
      end
      return result
    end,
    internal_events = function(trigger)
      local unit = trigger.unit
      local result = {}
      if unit and unit ~= "none" then
        AddUnitChangeInternalEvents(unit, result)
      end
      return result
    end,
    loadFunc = function(trigger)
      local unit = trigger.unit
      if unit and unit ~= "none" then
        AddWatchedUnits(unit)
      end
    end,
    force_events = unitHelperFunctions.UnitChangedForceEvents,
    name = L["Threat Situation"],
    init = function(trigger)
      trigger.unit = trigger.unit or "target";
      local ret = [[
        unit = string.lower(unit)
        local name = UnitName(unit, false) or (unit == "none" and "Unknown")
        local ok = true
        local aggro, status, threatpct, rawthreatpct, threatvalue, threattotal
        if unit and unit ~= "none" then
          aggro, status, threatpct, rawthreatpct, threatvalue = WeakAuras.UnitDetailedThreatSituation('player', unit)
          threattotal = (threatvalue or 0) * 100 / (threatpct ~= 0 and threatpct or 1)
        else
          status = UnitThreatSituation('player')
          aggro = status == 2 or status == 3
          threatpct, rawthreatpct, threatvalue, threattotal = 100, 100, 0, 100
        end
      ]];
      return ret .. unitHelperFunctions.SpecificUnitCheck(trigger);
    end,
    progressType = "static",
    statesParameter = "unit",
    args = {
      {
        name = "unit",
        display = L["Unit"],
        required = true,
        type = "unit",
        init = "arg",
        values = "threat_unit_types",
        test = "true",
        store = true,
        default = "target"
      },
      {
        name = "status",
        display = L["Status"],
        type = "select",
        values = "unit_threat_situation_types",
        store = true,
        conditionType = "select"
      },
      {
        name = "aggro",
        display = L["Aggro"],
        type = "tristate",
        store = true,
        conditionType = "bool",
      },
      {
        name = "threatpct",
        display = L["Threat Percent"],
        desc = L["Your threat on the mob as a percentage of the amount required to pull aggro. Will pull aggro at 100."],
        type = "number",
        store = true,
        conditionType = "number",
        enable = function(trigger) return trigger.unit ~= "none" end,
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "rawthreatpct",
        display = L["Raw Threat Percent"],
        desc = L["Your threat as a percentage of the tank's current threat."],
        type = "number",
        store = true,
        conditionType = "number",
        enable = function(trigger) return trigger.unit ~= "none" end,
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "threatvalue",
        display = L["Threat Value"],
        desc = L["Your total threat on the mob."],
        type = "number",
        store = true,
        conditionType = "number",
        enable = function(trigger) return trigger.unit ~= "none" end,
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        type = "header",
        name = "unitCharacteristicsHeader",
        display = L["Unit Characteristics"],
      },
      {
        name = "name",
        display = L["Unit Name"],
        type = "string",
        store = true,
        multiline = true,
        preamble = "local nameChecker = Private.ExecEnv.ParseStringCheck(%q)",
        test = "nameChecker:Check(name)",
        conditionType = "string",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseStringCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.name)
        end,
        operator_types = "none",
        desc = L["Supports multiple entries, separated by commas. Prefix with '-' for negation."]
      },
      {
        name = "npcId",
        display = L["Npc ID"],
        type = "string",
        multiline = true,
        store = true,
        init = "tostring(tonumber(string.sub(UnitGUID(unit) or '', 8, 12), 16) or '')",
        conditionType = "string",
        preamble = "local npcIdChecker = Private.ExecEnv.ParseStringCheck(%q)",
        test = "npcIdChecker:Check(npcId)",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseStringCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.npcId)
        end,
        operator_types = "none",
        desc = L["Supports multiple entries, separated by commas. Prefix with '-' for negation."]
      },
      {
        name = "value",
        hidden = true,
        init = "threatvalue",
        store = true,
        test = "true"
      },
      {
        name = "total",
        hidden = true,
        init = "threattotal",
        store = true,
        test = "true"
      },
      {
        name = "progressType",
        hidden = true,
        init = "'static'",
        store = true,
        test = "true"
      },
      {
        hidden = true,
        test = "status ~= nil and ok"
      },
      {
        hidden = true,
        test = "((WeakAuras.UnitExistsFixed(unit, false) or unit == 'none') and specificUnitCheck)"
      }
    },
    automaticrequired = true
  },
  ["Crowd Controlled"] = {
    type = "unit",
    events = {
      ["unit_events"] = {
        ["player"] = {"UNIT_AURA"}
      }
    },
    force_events = "UNIT_AURA",
    name = L["Crowd Controlled"],
    args = {
      {
        name = "controlled",
        display = L["Crowd Controlled"],
        type = "tristate",
        init = "not HasFullControl()"
      }
    },
    automaticrequired = true,
  },
  ["Cast"] = {
    type = "unit",
    events = function(trigger)
      local result = {}
      local unit = trigger.unit
      AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_START")
      AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_DELAYED")
      AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_STOP")
      AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_CHANNEL_START")
      AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_CHANNEL_UPDATE")
      AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_CHANNEL_STOP")
      AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_INTERRUPTIBLE")
      AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_NOT_INTERRUPTIBLE")
      AddUnitEventForEvents(result, unit, "UNIT_SPELLCAST_INTERRUPTED")
      AddUnitEventForEvents(result, unit, "UNIT_NAME_UPDATE")
      AddUnitEventForEvents(result, unit, "UNIT_TARGET")
      return result
    end,
    internal_events = function(trigger)
      local unit = trigger.unit
      local result = {}
      if unit == "nameplate" and trigger.use_onUpdateUnitTarget then
        tinsert(result, "WA_UNIT_TARGET_NAME_PLATE")
      end
      AddRemainingCastInternalEvents(unit, result)
      local includePets = trigger.use_includePets == true and trigger.includePets or nil
      AddUnitChangeInternalEvents(unit, result, includePets)
      if includePets ~= "PetsOnly" then
        AddUnitRoleChangeInternalEvents(unit, result)
      end
      return result
    end,
    loadFunc = function(trigger)
      if trigger.use_showLatency and trigger.unit == "player" then
        WeakAuras.WatchForCastLatency()
      end
      if trigger.unit == "nameplate" and trigger.use_onUpdateUnitTarget then
        WeakAuras.WatchForNameplateTargetChange()
      end
      local includePets = trigger.use_includePets == true and trigger.includePets or nil
      AddWatchedUnits(trigger.unit, includePets)
    end,
    force_events = unitHelperFunctions.UnitChangedForceEventsWithPets,
    progressType = "timed",
    name = L["Cast"],
    init = function(trigger)
      trigger.unit = trigger.unit or "player";
      local ret = [=[
        unit = string.lower(unit)
        local destUnit = unit .. '-target'
        local sourceName, sourceRealm = WeakAuras.UnitNameWithRealm(unit)
        local destName, destRealm = WeakAuras.UnitNameWithRealm(destUnit)
        destName = destName or ""
        destRealm = destRealm or ""
        local smart = %s
        local remainingCheck = %s
        local inverseTrigger = %s

        local show, expirationTime, castType, spell, icon, startTime, endTime, interruptible, remaining, _

        spell, _, _, icon, startTime, endTime, _, _, interruptible = UnitCastingInfo(unit)
        if spell then
          castType = "cast"
        else
          spell, _, _, icon, startTime, endTime, _, interruptible = UnitChannelInfo(unit)
          if spell then
            castType = "channel"
          end
        end
        interruptible = not interruptible
        expirationTime = endTime and endTime > 0 and (endTime / 1000) or 0
        remaining = expirationTime - GetTime()

        if remainingCheck and remaining >= remainingCheck and remaining > 0 then
          Private.ExecEnv.ScheduleCastCheck(expirationTime - remainingCheck, unit)
        end
      ]=];
      ret = ret:format(trigger.unit == "group" and "true" or "false",
                        trigger.use_remaining and tonumber(trigger.remaining or 0) or "nil",
                        trigger.use_inverse and "true" or "false");

      ret = ret .. unitHelperFunctions.SpecificUnitCheck(trigger)

      return ret
    end,
    statesParameter = "unit",
    args = {
      {
        name = "unit",
        required = true,
        display = L["Unit"],
        type = "unit",
        init = "arg",
        values = function(trigger)
          if trigger.use_inverse then
            return Private.actual_unit_types_with_specific
          else
            return Private.actual_unit_types_cast
          end
        end,
        desc = Private.actual_unit_types_cast_tooltip,
        test = "true",
        store = true
      },
      {
        name = "spellNames",
        display = L["Name(s)"],
        type = "spell",
        enable = function(trigger) return not trigger.use_inverse end,
        preambleGroup = "spell",
        preamble = "local spellChecker = Private.ExecEnv.CreateSpellChecker()",
        multiEntry = {
          operator = "preamble",
          preambleAdd = "spellChecker:AddName(%q)"
        },
        test = "spellChecker:CheckName(spell)",
        noValidation = true,
      },
      {
        name = "spellIds",
        display = L["Exact Spell ID(s)"],
        type = "spell",
        enable = function(trigger) return not trigger.use_inverse end,
        preambleGroup = "spell",
        preamble = "local spellChecker = Private.ExecEnv.CreateSpellChecker()",
        multiEntry = {
          operator = "preamble",
          preambleAdd = "spellChecker:AddName(GetSpellInfo(%q))"
        },
        test = "spellChecker:CheckName(spell)",
      },
      {
        name = "spellId",
        display = L["Spell ID"],
        conditionType = "number",
        store = true,
        test = "true",
        hidden = true,
        noProgressSource = true
      },
      {
        name = "spell",
        display = L["Spellname"],
        type = "string",
        conditionType = "string",
        store = true,
        test = "true",
        hidden = true
      },
      {
        name = "castType",
        display = L["Cast Type"],
        type = "select",
        values = "cast_types",
        enable = function(trigger) return not trigger.use_inverse end,
        store = true,
        conditionType = "select"
      },
      {
        name = "interruptible",
        display = L["Interruptible"],
        type = "tristate",
        enable = function(trigger) return not trigger.use_inverse end,
        store = true,
        conditionType = "bool",
      },

      {
        name = "remaining",
        display = L["Remaining Time"],
        type = "number",
        enable = function(trigger) return not trigger.use_inverse end,
      },
      {
        name = "name",
        hidden = true,
        init = "spell",
        test = "true",
        store = true
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
      {
        name = "duration",
        hidden = true,
        init = "endTime and startTime and (endTime - startTime)/1000 or 0",
        test = "true",
        store = true
      },
      {
        name = "expirationTime",
        init = "expirationTime",
        hidden = true,
        test = "true",
        store = true
      },
      {
        name = "progressType",
        hidden = true,
        init = "'timed'",
        test = "true",
        store = true
      },
      {
        name = "inverse",
        hidden = true,
        init = "castType == 'cast'",
        test = "true",
        store = true
      },
      {
        name = "autoHide",
        hidden = true,
        init = "true",
        test = "true",
        store = true,
        enable = function(trigger)
          return not trigger.use_inverse
        end
      },
      {
        type = "header",
        name = "unitCharacteristicsHeader",
        display = L["Unit Characteristics"],
      },
      {
        name = "npcId",
        display = L["Npc ID"],
        type = "string",
        multiline = true,
        store = true,
        init = "tostring(tonumber(string.sub(UnitGUID(unit) or '', 8, 12), 16) or '')",
        conditionType = "string",
        preamble = "local npcIdChecker = Private.ExecEnv.ParseStringCheck(%q)",
        test = "npcIdChecker:Check(npcId)",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseStringCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.npcId)
        end,
        operator_types = "none",
        desc = L["Supports multiple entries, separated by commas. Prefix with '-' for negation."],
        enable = function(trigger)
          return not trigger.use_inverse
        end,
      },
      {
        name = "class",
        display = L["Class"],
        type = "select",
        init = "select(2, UnitClass(unit))",
        values = "class_types",
        store = true,
        conditionType = "select",
        enable = function(trigger)
          return not trigger.use_inverse
        end
      },
      {
        name = "role",
        display = L["Spec Role"],
        type = "select",
        init = "WeakAuras.LGT:GetUnitRole(unit)",
        values = "role_types",
        store = true,
        conditionType = "select",
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
                 or trigger.unit == "player" and not trigger.use_inverse
        end
      },
      {
        name = "raid_role",
        display = L["Raid Role"],
        type = "select",
        init = "WeakAuras.UnitRaidRole(unit)",
        values = "raid_role_types",
        store = true,
        conditionType = "select",
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party" and not trigger.use_inverse
        end
      },
      {
        name = "raidMarkIndex",
        display = L["Raid Mark"],
        type = "multiselect",
        values = "raid_mark_check_type",
        store = true,
        conditionType = "select",
        init = "GetRaidTargetIndex(unit) or 0"
      },
      {
        name = "raidMark",
        display = L["Raid Mark Icon"],
        store = true,
        hidden = true,
        test = "true",
        init = "raidMarkIndex > 0 and '{rt'..raidMarkIndex..'}' or ''"
      },
      {
        name = "nameplateType",
        display = L["Hostility"],
        type = "select",
        init = "WeakAuras.GetPlayerReaction(unit)",
        values = "hostility_types",
        store = true,
        conditionType = "select",
      },
      {
        name = "sourceUnit",
        init = "unit",
        display = L["Caster"],
        type = "unit",
        values = "actual_unit_types_with_specific",
        conditionType = "unit",
        conditionTest = function(state, unit, op)
          return state and state.show and state.unit and (UnitIsUnit(state.sourceUnit, unit) == (op == "=="))
        end,
        store = true,
        hidden = true,
        enable = function(trigger) return not trigger.use_inverse end,
        test = "true"
      },
      {
        name = "sourceName",
        display = L["Caster Name"],
        type = "string",
        store = true,
        hidden = true,
        test = "true",
        enable = function(trigger) return not trigger.use_inverse end,
      },
      {
        name = "sourceRealm",
        display = L["Caster Realm"],
        type = "string",
        store = true,
        hidden = true,
        test = "true",
        enable = function(trigger) return not trigger.use_inverse end,
      },
      {
        name = "sourceNameRealm",
        display = L["Source Unit Name/Realm"],
        type = "string",
        multiline = true,
        preamble = "local sourceNameRealmChecker = Private.ExecEnv.ParseNameCheck(%q)",
        test = "sourceNameRealmChecker:Check(sourceName, sourceRealm)",
        conditionType = "string",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseNameCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.sourceName, state.sourceRealm)
        end,
        operator_types = "none",
        enable = function(trigger) return not trigger.use_inverse end,
        desc = constants.nameRealmFilterDesc,
      },
      {
        name = "destUnit",
        display = L["Caster's Target"],
        type = "unit",
        values = "actual_unit_types_with_specific",
        conditionType = "unit",
        conditionTest = function(state, unit, op)
          return state and state.show and state.destUnit and (UnitIsUnit(state.destUnit, unit) == (op == "=="))
        end,
        store = true,
        enable = function(trigger) return not trigger.use_inverse end,
        test = "UnitIsUnit(destUnit, [[%s]])"
      },
      {
        name = "destName",
        display = L["Name of Caster's Target"],
        type = "string",
        store = true,
        hidden = true,
        test = "true",
        enable = function(trigger) return not trigger.use_inverse end,
      },
      {
        name = "destRealm",
        display = L["Realm of Caster's Target"],
        type = "string",
        store = true,
        hidden = true,
        test = "true",
        enable = function(trigger) return not trigger.use_inverse end,
      },
      {
        name = "destNameRealm",
        display = L["Name/Realm of Caster's Target"],
        type = "string",
        multiline = true,
        preamble = "local destNameRealmChecker = Private.ExecEnv.ParseNameCheck(%q)",
        test = "destNameRealmChecker:Check(destName, destRealm)",
        conditionType = "string",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseNameCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.destName, state.destRealm)
        end,
        operator_types = "none",
        enable = function(trigger) return not trigger.use_inverse end,
        desc = constants.nameRealmFilterDesc,
      },
      {
        type = "header",
        name = "miscellaneousHeader",
        display = L["Miscellaneous"],
      },
      {
        name = "showLatency",
        display = L["Overlay Latency"],
        type = "toggle",
        test = "true",
        enable = function(trigger)
          return trigger.unit == "player" and not trigger.use_inverse
        end,
        reloadOptions = true
      },
      {
        name = "includePets",
        display = L["Include Pets"],
        type = "select",
        values = "include_pets_types",
        width = WeakAuras.normalWidth,
        test = "true",
        enable = function(trigger)
          return trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end
      },
      {
        name = "ignoreSelf",
        display = L["Ignore Self"],
        type = "toggle",
        width = WeakAuras.doubleWidth,
        enable = function(trigger)
          return trigger.unit == "nameplate" or trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party"
        end,
        init = "not UnitIsUnit(\"player\", unit)"
      },
      {
        name = "onUpdateUnitTarget",
        display = WeakAuras.newFeatureString .. L["Advanced Caster's Target Check"],
        desc = L["Check nameplate's target every 0.2s"],
        type = "toggle",
        test = "true",
        enable = function(trigger)
          return trigger.unit == "nameplate"
        end
      },
      {
        name = "inverse",
        display = L["Inverse"],
        type = "toggle",
        test = "true",
        reloadOptions = true
      },
      {
        hidden = true,
        test = "WeakAuras.UnitExistsFixed(unit, smart) and ((not inverseTrigger and spell) or (inverseTrigger and not spell)) and specificUnitCheck"
      }
    },
    overlayFuncs = {
      {
        name = L["Latency"],
        func = function(trigger, state)
          local latency = WeakAuras.GetCastLatency()
          if not latency then return 0, 0 end
          return 0, latency
        end,
        enable = function(trigger)
          return trigger.use_showLatency and trigger.unit == "player"
        end
      },
    },
    GetNameAndIcon = function(trigger)
      local name, icon, spellId, _
      if trigger.use_spellNames and type(trigger.spellNames) == "table" then
        for _, spellName in ipairs(trigger.spellNames) do
          spellId = WeakAuras.SafeToNumber(spellName)
          if spellId then
            name, _, icon = GetSpellInfo(spellName)
            if name and icon then
              return name, icon
            end
          elseif not tonumber(spellName) then
            name, _, icon = GetSpellInfo(spellName)
            if name and icon then
              return name, icon
            end
          end
        end
      end
      if trigger.use_spellIds and type(trigger.spellIds) == "table" then
        for _, spellIdString in ipairs(trigger.spellIds) do
          spellId = WeakAuras.SafeToNumber(spellIdString)
          if spellId then
            name, _, icon = GetSpellInfo(spellIdString)
            if name and icon then
              return name, icon
            end
          end
        end
      end
    end,
    automaticrequired = true,
  },
  ["Character Stats"] = {
    type = "unit",
    name = L["Character Stats"],
    events = {
      ["events"] = {
        "COMBAT_RATING_UPDATE",
        "PLAYER_TARGET_CHANGED"
      },
      ["unit_events"] = {
        ["player"] = {"UNIT_STATS", "UNIT_ATTACK_POWER", "UNIT_AURA", "PLAYER_DAMAGE_DONE_MODS", "UNIT_RESISTANCES"}
      }
    },
    internal_events = function(trigger, untrigger)
      local events = { "WA_DELAYED_PLAYER_ENTERING_WORLD" }
      if trigger.use_moveSpeed then
        tinsert(events, "PLAYER_MOVE_SPEED_UPDATE")
      end
      return events
    end,
    loadFunc = function(trigger)
      if trigger.use_moveSpeed then
        WeakAuras.WatchForPlayerMoving()
      end
    end,
    force_events = "CONDITIONS_CHECK",
    statesParameter = "one",
    args = {
      {
        type = "header",
        name = "primaryStatsHeader",
        display = L["Primary Stats"],
      },
      {
        name = "strength",
        display = L["Strength"],
        type = "number",
        init = "UnitStat('player', 1)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "agility",
        display = L["Agility"],
        type = "number",
        init = "UnitStat('player', 2)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "stamina",
        display = L["Stamina"],
        type = "number",
        init = "UnitStat('player', 3)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "intellect",
        display = L["Intellect"],
        type = "number",
        init = "UnitStat('player', 4)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "spirit",
        display = L["Spirit"],
        type = "number",
        init = "UnitStat('player', 5)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        type = "header",
        name = "secondaryStatsHeader",
        display = L["Secondary Stats"],
      },
      {
        name = "criticalrating",
        display = L["Critical Rating"],
        type = "number",
        init = "max(GetCombatRating(CR_CRIT_MELEE), GetCombatRating(CR_CRIT_RANGED), GetCombatRating(CR_CRIT_SPELL))",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "criticalpercent",
        display = L["Critical (%)"],
        type = "number",
        init = "WeakAuras.GetCritChance()",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "hitrating",
        display = L["Hit Rating"],
        type = "number",
        init = "max(GetCombatRating(CR_HIT_MELEE), GetCombatRating(CR_HIT_RANGED), GetCombatRating(CR_HIT_SPELL))",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "hitpercent",
        display = L["Hit (%)"],
        type = "number",
        init = "WeakAuras.GetHitChance()",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "hasterating",
        display = L["Haste Rating"],
        type = "number",
        init = "max(GetCombatRating(CR_HASTE_SPELL), GetCombatRating(CR_HASTE_MELEE), GetCombatRating(CR_HASTE_RANGED))",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "hastepercent",
        display = L["Haste (%)"],
        type = "number",
        init = "max(GetCombatRatingBonus(CR_HASTE_SPELL), GetCombatRatingBonus(CR_HASTE_MELEE), GetCombatRatingBonus(CR_HASTE_RANGED))",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "expertiserating",
        display = L["Expertise Rating"],
        type = "number",
        init = "GetCombatRating(CR_EXPERTISE)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "expertisebonus",
        display = L["Expertise Bonus"],
        type = "number",
        init = "GetCombatRatingBonus(CR_EXPERTISE)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "armorpenrating",
        display = L["Armor Peneration Rating"],
        type = "number",
        init = "GetCombatRating(CR_ARMOR_PENETRATION)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "armorpenpercent",
        display = L["Armor Peneration (%)"],
        type = "number",
        init = "GetArmorPenetration()",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "spellpenpercent",
        display = L["Spell Peneration (%)"],
        type = "number",
        init = "GetSpellPenetration()",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "resiliencerating",
        display = L["Resilience Rating"],
        type = "number",
        init = "GetCombatRating(CR_CRIT_TAKEN_MELEE)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "resiliencepercent",
        display = L["Resilience (%)"],
        type = "number",
        init = "GetCombatRatingBonus(CR_CRIT_TAKEN_MELEE)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
            {
        name = "attackpower",
        display = L["Attack Power"],
        type = "number",
        init = "WeakAuras.GetEffectiveAttackPower()",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "spellpower",
        display = L["Spell Power"],
        type = "number",
        init = "WeakAuras.GetEffectiveSpellPower()",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "rangedattackpower",
        display = L["Ranged Attack Power"],
        type = "number",
        init = "WeakAuras.GetEffectiveRangedAttackPower()",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        type = "header",
        name = "resistanceHeader",
        display = L["Resistances"],
      },
      {
        name = "resistancefire",
        display = L["Fire Resistance"],
        type = "number",
        init = "select(2, UnitResistance('player', 2))",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "resistancenature",
        display = L["Nature Resistance"],
        type = "number",
        init = "select(2, UnitResistance('player', 3))",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "resistancefrost",
        display = L["Frost Resistance"],
        type = "number",
        init = "select(2, UnitResistance('player', 4))",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "resistanceshadow",
        display = L["Shadow Resistance"],
        type = "number",
        init = "select(2, UnitResistance('player', 5))",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "resistancearcane",
        display = L["Arcane Resistance"],
        type = "number",
        init = "select(2, UnitResistance('player', 6))",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        type = "header",
        name = "tertiaryStatsHeader",
        display = L["Tertiary Stats"],
      },
      {
        name = "moveSpeed",
        display = L["Continuously update Movement Speed"],
        type = "boolean",
        test = true,
        width = WeakAuras.doubleWidth
      },
      {
        name = "movespeedpercent",
        display = L["Current Movement Speed (%)"],
        type = "number",
        init = "GetUnitSpeed('player') / 7 * 100",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        type = "header",
        name = "defensiveStatsHeader",
        display = L["Defensive Stats"],
      },

      {
        name = "defense",
        display = L["Defense"],
        type = "number",
        init = "UnitDefense('player') + select(2, UnitDefense('player'))",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "defensevalue",
        display = L["Defense Value"],
        type = "number",
        init = "UnitDefense('player')",
        store = true,
        conditionType = "number"
      },
      {
        name = "defensepercent",
        display = L["Defense (%)"],
        type = "number",
        init = "0.04 * ( UnitDefense('player') + select(2,UnitDefense('player') ) - UnitLevel('player')*5 )",
        store = true,
        conditionType = "number"
      },
      {
        name = "dodgerating",
        display = L["Dodge Rating"],
        type = "number",
        init = "GetCombatRating(CR_DODGE)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "dodgepercent",
        display = L["Dodge (%)"],
        type = "number",
        init = "GetDodgeChance()",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "parryrating",
        display = L["Parry Rating"],
        type = "number",
        init = "GetCombatRating(CR_PARRY)",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "parrypercent",
        display = L["Parry (%)"],
        type = "number",
        init = "GetParryChance()",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "blockpercent",
        display = L["Block (%)"],
        type = "number",
        init = "GetBlockChance()",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "blockvalue",
        display = L["Block Value"],
        type = "number",
        init = "GetShieldBlock()",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "armorrating",
        display = L["Armor Rating"],
        type = "number",
        init = "select(2, UnitArmor('player'))",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
      },
      {
        name = "armorpercent",
        display = L["Armor (%)"],
        type = "number",
        init = "PaperDollFrame_GetArmorReduction(select(2, UnitArmor('player')), UnitLevel('player'))",
        store = true,
        conditionType = "number",
        multiEntry = {
          operator = "and",
          limit = 2
        },
        formatter = "Number",
      },
      {
        name = "avoidancetotalpercent",
        display = L["Total Avoidance (%)"],
        type = "number",
        init = "0.04 * ( UnitDefense('player') + select(2,UnitDefense('player') ) - UnitLevel('player')*5 ) + GetDodgeChance() + GetParryChance() + GetBlockChance()",
        store = true,
        conditionType = "number"
      },
    },
    automaticrequired = true,
    progressType = "none"
  },
  ["Conditions"] = {
    type = "unit",
    events = function(trigger, untrigger)
      local events = {}
      if trigger.use_incombat ~= nil then
        tinsert(events, "PLAYER_REGEN_ENABLED")
        tinsert(events, "PLAYER_REGEN_DISABLED")
        tinsert(events, "PLAYER_ENTERING_WORLD")
      end
      if trigger.use_pvpflagged ~= nil or trigger.use_afk ~= nil then
        tinsert(events, "PLAYER_FLAGS_CHANGED")
      end
      if trigger.use_alive ~= nil then
        tinsert(events, "PLAYER_DEAD")
        tinsert(events, "PLAYER_ALIVE")
        tinsert(events, "PLAYER_UNGHOST")
      end
      if trigger.use_resting ~= nil then
        tinsert(events, "PLAYER_UPDATE_RESTING")
        tinsert(events, "PLAYER_ENTERING_WORLD")
      end
      if trigger.use_mounted ~= nil then
        tinsert(events, "MOUNTED_UPDATE")
        tinsert(events, "PLAYER_ENTERING_WORLD")
      end
      local unit_events = {}
      local pet_unit_events = {}
      if trigger.use_vehicle ~= nil then
        tinsert(unit_events, "UNIT_ENTERED_VEHICLE")
        tinsert(unit_events, "UNIT_EXITED_VEHICLE")
        tinsert(events, "PLAYER_ENTERING_WORLD")
      end
      if trigger.use_HasPet ~= nil then
        tinsert(pet_unit_events, "UNIT_HEALTH")
      end
      if trigger.use_ingroup ~= nil then
        tinsert(events, "PARTY_MEMBERS_CHANGED")
        tinsert(events, "RAID_ROSTER_UPDATE")
      end
      if trigger.use_ruleset ~= nil then
        tinsert(events, "ZONE_CHANGED_NEW_AREA")
        tinsert(events, "PLAYER_FLAGS_CHANGED")
      end
      if trigger.use_instance_difficulty ~= nil
          or trigger.use_instance_size ~= nil
      then
        tinsert(events, "PLAYER_DIFFICULTY_CHANGED")
      end

      return {
        ["events"] = events,
        ["unit_events"] = {
          ["player"] = unit_events,
          ["pet"] = pet_unit_events
        }
      }
    end,
    internal_events = function(trigger, untrigger)
      local events = { "CONDITIONS_CHECK"};

      if trigger.use_ismoving ~= nil then
        tinsert(events, "PLAYER_MOVING_UPDATE");
      end

      if trigger.use_instance_difficulty ~= nil
          or trigger.use_instance_size ~= nil
          or trigger.use_pvpflagged ~= nil
      then
        tinsert(events, "WA_DELAYED_PLAYER_ENTERING_WORLD")
      end

      if (trigger.use_HasPet ~= nil) then
        AddUnitChangeInternalEvents("pet", events);
      end

      return events;
    end,
    force_events = "CONDITIONS_CHECK",
    name = L["Conditions"],
    loadFunc = function(trigger)
      if (trigger.use_ismoving ~= nil) then
        WeakAuras.WatchForPlayerMoving();
      end
      if (trigger.use_HasPet ~= nil) then
        AddWatchedUnits("pet")
      end
    end,
    init = function(trigger)
      return "";
    end,
    args = {
      {
        name = "alwaystrue",
        display = L["Always active trigger"],
        type = "tristate",
        init = "true"
      },
      {
        name = "incombat",
        display = L["In Combat"],
        type = "tristate",
        init = "UnitAffectingCombat('player')"
      },
      {
        name = "pvpflagged",
        display = L["PvP Flagged"],
        type = "tristate",
        init = "UnitIsPVP('player')",
      },
      {
        name = "alive",
        display = L["Alive"],
        type = "tristate",
        init = "not UnitIsDeadOrGhost('player')"
      },
      {
        name = "vehicle",
        display = L["In Vehicle"],
        type = "tristate",
        init = "UnitInVehicle('player')",
      },
      {
        name = "resting",
        display = L["Resting"],
        type = "tristate",
        init = "IsResting()"
      },
      {
        name = "mounted",
        display = L["Mounted"],
        type = "tristate",
        init = "IsMounted()"
      },
      {
        name = "HasPet",
        display = L["HasPet"],
        type = "tristate",
        init = "UnitExists('pet') and not UnitIsDead('pet')"
      },
      {
        name = "ismoving",
        display = L["Is Moving"],
        type = "tristate",
        init = "GetUnitSpeed('player') > 0"
      },
      {
        name = "afk",
        display = L["Is Away from Keyboard"],
        type = "tristate",
        init = "UnitIsAFK('player')"
      },
      {
        name = "ingroup",
        display = L["Group Type"],
        type = "multiselect",
        values = "group_types",
        init = "Private.ExecEnv.GroupType()",
        events = {"PARTY_MEMBERS_CHANGED", "RAID_ROSTER_UPDATE"}
      },
      {
        name = "ruleset",
        display = "PvP Ruleset",
        type = "multiselect",
        values = "ruleset_types",
        init = "WeakAuras.Ruleset()",
        events = {"ZONE_CHANGED_NEW_AREA", "PLAYER_FLAGS_CHANGED"}
      },
      {
        name = "instance_size",
        display = L["Instance Type"],
        desc = constants.instanceFilterDeprecated,
        type = "multiselect",
        values = "instance_types",
        sorted = true,
        init = "WeakAuras.InstanceType()",
        events = {"ZONE_CHANGED", "ZONE_CHANGED_INDOORS", "ZONE_CHANGED_NEW_AREA"}
      },
      {
        name = "instance_difficulty",
        display = L["Instance Difficulty"],
        desc = constants.instanceFilterDeprecated,
        type = "multiselect",
        values = "difficulty_types",
        init = "WeakAuras.InstanceDifficulty()"
      },
    },
    automaticrequired = true,
    progressType = "none"
  },

  ["Spell Known"] = {
    type = "spell",
    events = {
      ["events"] = {"SPELLS_CHANGED", "PLAYER_TALENT_UPDATE"},
      ["unit_events"] = {
        ["player"] = {"UNIT_PET"}
      }
    },
    internal_events = {
      "WA_DELAYED_PLAYER_ENTERING_WORLD"
    },
    force_events = "SPELLS_CHANGED",
    name = L["Spell Known"],
    statesParameter = "one",
    init = function(trigger)
      local spellName;
      local ret = {};

      spellName = type(trigger.spellName) == "number" and trigger.spellName or 0;
      table.insert(ret, ([[
        local spellName = %s;
        local name, _, icon = GetSpellInfo(spellName)
      ]]):format(spellName))

      local spellCheck = spellName ~= 0 and "true" or "false"
      if (trigger.use_inverse) then
        table.insert(ret, ([[
          local usePet = %s;
          local active = %s and IsSpellKnown(spellName, usePet) or false
        ]]):format(trigger.use_petspell and "true" or "false", spellCheck))
      else
        table.insert(ret, ([[
          local usePet = %s;
          local active = %s and IsSpellKnown(spellName, usePet)
        ]]):format(trigger.use_petspell and "true" or "false", spellCheck))
      end
      return table.concat(ret)
    end,
    GetNameAndIcon = function(trigger)
      local name, _, icon = GetSpellInfo(trigger.spellName or 0)
      return name, icon
    end,
    args = {
      {
        name = "spellName",
        required = true,
        display = L["Spell"],
        type = "spell",
        test = "true",
      },
      {
        name = "petspell",
        display = L["Pet Spell"],
        type = "toggle",
        test = "true"
      },
      {
        name = "inverse",
        display = WeakAuras.newFeatureString .. L["Inverse"],
        type = "toggle",
        test = "true",
      },
      {
        name = "name",
        display = L["Name"],
        hidden = true,
        init = "name",
        test = "true",
        store = true,
        conditionType = "string"
      },
      {
        name = "icon",
        hidden = true,
        init = "icon",
        test = "true",
        store = true
      },
      {
        hidden = true,
        test = "active"
      }
    },
    automaticrequired = true,
    progressType = "none"
  },

  ["Pet Behavior"] = {
    type = "unit",
    events = function(trigger)
      local result = {};
      if (trigger.use_behavior) then
        tinsert(result, "PET_BAR_UPDATE");
      end
      return {
        ["events"] = result,
        ["unit_events"] = {
          ["player"] = {"UNIT_PET"}
        }
      };
    end,
    internal_events = {
      "WA_DELAYED_PLAYER_ENTERING_WORLD"
    },
    force_events = "WA_DELAYED_PLAYER_ENTERING_WORLD",
    name = L["Pet"],
    init = function(trigger)
      local ret = "local activeIcon\n";
      if (trigger.use_behavior) then
        ret = [[
            local inverse = %s
            local check_behavior = %s
            local name, i, token, active, behavior, _
            for index = 1, NUM_PET_ACTION_SLOTS do
              name, _, i, token, active = GetPetActionInfo(index)
              if active then
                activeIcon = not token and i or _G[i]
                if name == "PET_MODE_AGGRESSIVE" then
                  behavior = "aggressive"
                  break
                elseif name == "PET_MODE_DEFENSIVE" then
                  behavior = "defensive"
                  break
                elseif name == "PET_MODE_PASSIVE" then
                  behavior = "passive"
                  break
                end
              end
            end
        ]]
        ret = ret:format(trigger.use_inverse and "true" or "false", trigger.use_behavior and ('"' .. (trigger.behavior or "") .. '"') or "nil");
      end
      return ret;
    end,
    statesParameter = "one",
    args = {
      {
        name = "behavior",
        display = L["Pet Behavior"],
        type = "select",
        values = "pet_behavior_types",
        test = "UnitExists('pet') and (not check_behavior or (inverse and check_behavior ~= behavior) or (not inverse and check_behavior == behavior))",
      },
      {
        name = "inverse",
        display = L["Inverse Pet Behavior"],
        type = "toggle",
        test = "true",
        enable = function(trigger) return trigger.use_behavior end
      },
      {
        hidden = true,
        name = "icon",
        init = "activeIcon",
        store = "true",
        test = "true"
      },
    },
    automaticrequired = true,
    progressType = "none"
  },

  ["Queued Action"] = {
    type = "spell",
    events = {
      ["events"] = {"ACTIONBAR_UPDATE_STATE"}
    },
    internal_events = {
      "ACTIONBAR_SLOT_CHANGED",
      "ACTIONBAR_PAGE_CHANGED"
    },
    name = L["Queued Action"],
    init = function(trigger)
      local spellName = type(trigger.spellName) ~= "table" and trigger.spellName or 0
      if trigger.use_exact_spellName then
        spellName = trigger.spellName
      else
        spellName = type(trigger.spellName) == "number" and GetSpellInfo(trigger.spellName) or trigger.spellName
      end
      local ret = [=[
        local spellname = %q
      ]=]
      return ret:format(spellName)
    end,
    args = {
      {
        name = "spellName",
        required = true,
        display = L["Spell"],
        type = "spell",
        test = "true",
        showExactOption = true,
      },
      {
        hidden = true,
        test = "spellname and IsCurrentSpell(spellname)";
      },
    },
    iconFunc = function(trigger)
      local _, _, icon = GetSpellInfo(trigger.spellName or 0);
      return icon;
    end,
    automaticrequired = true,
    progressType = "none"
  },

  ["Range Check"] = {
    type = "unit",
    events = {
      ["events"] = {"FRAME_UPDATE"}
    },
    name = L["Range Check"],
    init = function(trigger)
      trigger.unit = trigger.unit or "target";
      local ret = [=[
          local unit = %q;
          local min, max = WeakAuras.GetRange(unit, true);
          min = min or 0;
          max = max or 999;
          local triggerResult = true;
      ]=]
      if (trigger.use_range) then
        trigger.range = trigger.range or 8;
        if (trigger.range_operator == "<=") then
          ret = ret .. "triggerResult = max <= " .. tostring(trigger.range) .. "\n";
        else
          ret = ret .. "triggerResult = min >= " .. tostring(trigger.range).. "\n";
        end
      end
      return ret:format(trigger.unit);
    end,
    statesParameter = "one",
    args = {
      {
        name = "note",
        type = "description",
        display = "",
        text = function() return L["Note: This trigger type estimates the range to the hitbox of a unit. The actual range of friendly players is usually 3 yards more than the estimate. Range checking capabilities depend on your current class and known abilities as well as the type of unit being checked. Some of the ranges may also not work with certain NPCs.|n|n|cFFAAFFAAFriendly Units:|r %s|n|cFFFFAAAAHarmful Units:|r %s|n|cFFAAAAFFMiscellanous Units:|r %s"]:format(RangeCacheStrings.friend or "", RangeCacheStrings.harm or "", RangeCacheStrings.misc or "") end
      },
      {
        name = "unit",
        required = true,
        display = L["Unit"],
        type = "unit",
        init = "unit",
        values = "unit_types_range_check",
        test = "true",
        store = true
      },
      {
        hidden = true,
        name = "minRange",
        display = L["Minimum Estimate"],
        type = "number",
        init = "min",
        store = true,
        test = "true",
        conditionType = "number",
        operator_types = "without_equal",
      },
      {
        hidden = true,
        name = "maxRange",
        display = L["Maximum Estimate"],
        type = "number",
        init = "max",
        store = true,
        test = "true",
        conditionType = "number",
        operator_types = "without_equal",
      },
      {
        name = "range",
        display = L["Distance"],
        type = "number",
        operator_types = "without_equal",
        test = "triggerResult",
        conditionType = "number",
        conditionTest = function(state, needle, needle2)
          return state and state.show and WeakAuras.CheckRange(state.unit, needle, needle2);
        end,
        noProgressSource = true
      },
      {
        hidden = true,
        test = "UnitExists(unit)"
      }
    },
    automaticrequired = true,
    progressType = "none"
  },
  ["Money"] = {
    type = "unit",
    statesParameter = "one",
    progressType = "none",
    automaticrequired = true,
    events = {
      ["events"] = {"PLAYER_MONEY"}
    },
    internal_events = {"WA_DELAYED_PLAYER_ENTERING_WORLD"},
    force_events = "WA_DELAYED_PLAYER_ENTERING_WORLD",
    name = WeakAuras.newFeatureString..L["Player Money"],
    init = function()
      return [=[
        local money = GetMoney()
        local gold = floor(money / 1e4)
        local silver = floor(money / 100 % 100)
        local copper = money % 100
      ]=]
    end,
    args = {
      {
        name = "money",
        init = "money",
        type = "number",
        display = L["Money"],
        store = true,
        hidden = true,
        test = "true",
      },
      {
        name = "gold",
        init = "gold",
        type = "number",
        display = Private.coin_icons.gold .. L["Gold"],
        store = true,
        conditionType = "number",
        formatter = "BigNumber"
      },
      {
        name = "silver",
        init = "silver",
        type = "number",
        display = Private.coin_icons.silver .. L["Silver"],
        store = true,
        hidden = true,
        test = "true",
      },
      {
        name = "copper",
        init = "copper",
        type = "number",
        display = Private.coin_icons.copper .. L["Copper"],
        store = true,
        hidden = true,
        test = "true",
      },
      {
        name = "icon",
        init = "GetCoinIcon(money)",
        store = true,
        hidden = true,
        test = "true",
      },
    },
    GetNameAndIcon = function()
      return MONEY, GetCoinIcon(GetMoney())
    end,
  },
  ["Currency"] = {
    type = "unit",
    progressType = "static",
    events = {
      ["events"] = {
        "CURRENCY_DISPLAY_UPDATE",
      }
    },
    force_events = "CURRENCY_DISPLAY_UPDATE",
    name = WeakAuras.newFeatureString..L["Currency"],
    init = function(trigger)
      if type(trigger.value) ~= "string" then trigger.value = "" end
      if type(trigger.value_operator) ~= "string" then trigger.value_operator = "" end
      local ret = [=[
        local currencyID = %d
        local quantity, totalEarned, icon
        if currencyID == 43308 then
          quantity, totalEarned = GetHonorCurrency()
          icon = UnitFactionGroup("player") == "Alliance" and
            "Interface/Icons/inv_misc_tournaments_symbol_human" or
            "Interface/Icons/Achievement_PVP_H_16"
        elseif currencyID == 43307 then
          quantity, totalEarned = GetArenaCurrency()
          icon = "Interface/PVPFrame/PVP-ArenaPoints-Icon"
        end
        local name = GetItemInfo(currencyID)
        local currencyInfo = {
          name = name or "Unknown Currency",
          quantity = quantity or GetItemCount(currencyID) or 0,
          icon = icon or GetItemIcon(currencyID) or "Interface/Icons/INV_Misc_QuestionMark",
          totalEarned = totalEarned or Private.ExecEnv.GetTotalCountCurrencies(currencyID) or 0,
          discovered = ((Private.ExecEnv.GetDiscoveredCurrencies() or {})[currencyID]) and true or false
        }
      ]=]
      return ret:format(tonumber(trigger.currencyId) or 0)
    end,
    statesParameter = "one",
    args = {
      {
        name = "currencyId",
        type = "currency",
        itemControl = "Dropdown-Currency",
        values = Private.ExecEnv.GetDiscoveredCurrencies,
        headers = Private.GetDiscoveredCurrenciesHeaders,
        sorted = true,
        sortOrder = function()
          local discovered_currencies_sorted = Private.GetDiscoveredCurrenciesSorted()
          local sortOrder = {}
          for key, value in pairs(Private.ExecEnv.GetDiscoveredCurrencies()) do
            tinsert(sortOrder, key)
          end
          table.sort(sortOrder, function(aKey, bKey)
            local aValue = discovered_currencies_sorted[aKey]
            local bValue = discovered_currencies_sorted[bKey]
            return aValue < bValue
          end)
          return sortOrder
        end,
        required = true,
        display = L["Currency"],
        store = true,
        test = "true",
      },
      {
        name = "name",
        init = "currencyInfo.name",
        hidden = true,
        store = true,
        test = "true",
      },
      {
        name = "value",
        init = "currencyInfo.quantity",
        type = "number",
        display = L["Quantity"],
        store = true,
        conditionType = "number",
      },
      {
        name = "totalEarned",
        init = "currencyInfo.totalEarned",
        type = "number",
        display = L["Total"],
        store = true,
        conditionType = "number",
      },
      {
        name = "progressType",
        init = "'static'",
        hidden = true,
        store = true,
        test = "true",
      },
      {
        name = "icon",
        init = "currencyInfo.icon",
        store = true,
        hidden = true,
        test = "true",
      },
      {
        name = "description",
        init = "currencyInfo.description",
        type = "string",
        display = L["Description"],
        store = true,
        hidden = true,
        test = "true",
      },
      {
        name = "discovered",
        init = "currencyInfo.discovered",
        type = "tristate",
        display = L["Discovered"],
        store = true,
        conditionType = "bool",
      },
    },
    GetNameAndIcon = function(trigger)
      local id = trigger.currencyId or 0
      local name = GetItemInfo(id)
      local icon = GetItemIcon(id)
      if id == 43308 then
        icon = UnitFactionGroup("player") == "Alliance" and
          "Interface/Icons/inv_misc_tournaments_symbol_human" or
          "Interface/Icons/Achievement_PVP_H_16"
      elseif id == 43307 then
        icon = "Interface/PVPFrame/PVP-ArenaPoints-Icon"
      end
      return name, icon
    end,
    automaticrequired = true
  },
  ["Location"] = {
    type = "unit",
    events = {
      ["events"] = {
        "ZONE_CHANGED",
        "ZONE_CHANGED_INDOORS",
        "ZONE_CHANGED_NEW_AREA",
        "PLAYER_DIFFICULTY_CHANGED",
        "WA_DELAYED_PLAYER_ENTERING_WORLD"
      }
    },
    internal_events = {"INSTANCE_LOCATION_CHECK"},
    force_events = "INSTANCE_LOCATION_CHECK",
    name = WeakAuras.newFeatureString..L["Location"],
    init = function(trigger)
      local ret = [=[
        local uiMapId = GetCurrentMapAreaID()
        local instanceName, instanceType, difficultyID = GetInstanceInfo()
        local minimapZoneText = GetMinimapZoneText()
        local zoneText = GetZoneText()
      ]=]
      return ret
    end,
    statesParameter = "one",
    args = {
      {
        name = "zoneIds",
        display = L["Player Location ID(s)"],
        desc = function()
          return ("\n|cffffd200%s|r%s: %d\n\n%s"):format(L["Current Zone\n"], GetRealZoneText(), GetCurrentMapAreaID(), L["Supports multiple entries, separated by commas. Prefix with '-' for negation."])
        end,
        type = "string",
        multiline = true,
        preamble = "local zoneChecker = Private.ExecEnv.ParseZoneCheck(%q)",
        test = "zoneChecker:Check(MapId)",
        conditionType = "string",
        conditionPreamble = function(input)
          return Private.ExecEnv.ParseZoneCheck(input)
        end,
        conditionTest = function(state, needle, op, preamble)
          return preamble:Check(state.zoneId)
        end,
        operator_types = "none",
      },
      {
        name = "zoneId",
        display = L["Zone ID"],
        init = "uiMapId",
        store = true,
        hidden = true,
        test = "true",
      },
      {
        name = "zone",
        display = L["Zone Name"],
        desc = function()
          return ("|cffffd200%s|r%s"):format(L["Current Zone\n"], GetRealZoneText())
        end,
        type = "string",
        conditionType = "string",
        store = true,
        init = "zoneText",
        multiEntry = {
          operator = "or",
        }
      },
      {
        name = "subzone",
        display = L["Subzone Name"],
        desc = function()
          return ("%s\n\n|cffffd200%s|r%s"):format(L["Name of the (sub-)zone currently shown above the minimap."], L["Current Zone\n"], GetMinimapZoneText())
        end,
        type = "string",
        conditionType = "string",
        store = true,
        init = "minimapZoneText",
        multiEntry = {
          operator = "or",
        },
      },
      {
        type = "header",
        name = "instanceHeader",
        display = L["Instance Info"],
      },
      {
        name = "instance",
        display = L["Instance Name"],
        test = "true",
        hidden = "true",
        store = true,
      },
      {
        name = "instanceSize",
        display = L["Instance Size Type"],
        type = "multiselect",
        values = "instance_types",
        sorted = true,
        init = "WeakAuras.InstanceType()",
        conditionType = "select",
        store = true,
      },
      {
        name = "instanceDifficulty",
        display = L["Instance Difficulty"],
        type = "multiselect",
        values = "difficulty_types",
        init = "WeakAuras.InstanceDifficulty()",
        conditionType = "select",
        store = true,
      },
    },
    automaticrequired = true,
    progressType = "none"
  },
};

--[[
Disable any event here
if () then
  Private.event_prototypes[] = nil
  end
]]

Private.category_event_prototype = {}
for name, prototype in pairs(Private.event_prototypes) do
  Private.category_event_prototype[prototype.type] = Private.category_event_prototype[prototype.type] or {}
  Private.category_event_prototype[prototype.type][name] = prototype.name
end

Private.dynamic_texts = {
  ["p"] = {
    get = function(state)
      if not state then return nil end
      if state.progressType == "static" then
        return state.value or nil
      end
      if state.progressType == "timed" then
        if state.paused then
          return state.remaining and state.remaining >= 0 and state.remaining or nil
        end

        if not state.expirationTime or not state.duration then
          return nil
        end
        local remaining = state.expirationTime - GetTime();
        return remaining >= 0 and remaining or nil
      end
    end,
    func = function(remaining, state, progressPrecision)
      progressPrecision = progressPrecision or 1

      if not state or state.progressType ~= "timed" then
        return remaining
      end
      if type(remaining) ~= "number" then
        return ""
      end

      local remainingStr = "";
      if remaining == math.huge then
        remainingStr = " ";
      elseif remaining > 60 then
        remainingStr = string.format("%i:", math.floor(remaining / 60));
        remaining = remaining % 60;
        remainingStr = remainingStr..string.format("%02i", remaining);
      elseif remaining > 0 then
        if progressPrecision == 4 and remaining <= 3 then
          remainingStr = remainingStr..string.format("%.1f", remaining);
        elseif progressPrecision == 5 and remaining <= 3 then
          remainingStr = remainingStr..string.format("%.2f", remaining);
        elseif progressPrecision == 6 and remaining <= 3 then
          remainingStr = remainingStr..string.format("%.3f", remaining);
        elseif (progressPrecision == 4 or progressPrecision == 5 or progressPrecision == 6) and remaining > 3 then
          remainingStr = remainingStr..string.format("%d", remaining);
        else
          remainingStr = remainingStr..string.format("%.".. progressPrecision .."f", remaining);
        end
      else
        remainingStr = " ";
      end
      return remainingStr
    end
  },
  ["t"] = {
    get = function(state)
      if not state then return "" end
      if state.progressType == "static" then
        return state.total, false
      end
      if state.progressType == "timed" then
        if not state.duration then
          return nil
        end
        return state.duration, true
      end
    end,
    func = function(duration, state, totalPrecision)
      if not state or state.progressType ~= "timed" then
        return duration
      end
      if type(duration) ~= "number" then
        return ""
      end
      local durationStr = "";
      if math.abs(duration) == math.huge or tostring(duration) == "nan" then
        durationStr = " ";
      elseif duration > 60 then
        durationStr = string.format("%i:", math.floor(duration / 60));
        duration = duration % 60;
        durationStr = durationStr..string.format("%02i", duration);
      elseif duration > 0 then
        if totalPrecision == 4 and duration <= 3 then
          durationStr = durationStr..string.format("%.1f", duration);
        elseif totalPrecision == 5 and duration <= 3 then
          durationStr = durationStr..string.format("%.2f", duration);
        elseif totalPrecision == 6 and duration <= 3 then
          durationStr = durationStr..string.format("%.3f", duration);
        elseif (totalPrecision == 4 or totalPrecision == 5 or totalPrecision == 6) and duration > 3 then
          durationStr = durationStr..string.format("%d", duration);
        else
          durationStr = durationStr..string.format("%."..totalPrecision.."f", duration);
        end
      else
        durationStr = " ";
      end
      return durationStr
    end
  },
  ["n"] = {
    get = function(state)
      if not state then return "" end
      return state.name or state.id or "", true
    end,
    func = function(v)
      return v
    end
  },
  ["i"] = {
    get = function(state)
      if not state then return "" end
      return state.icon or "Interface\\Icons\\INV_Misc_QuestionMark"
    end,
    func = function(v)
      return "|T".. v ..":12:12:0:0:64:64:4:60:4:60|t"
    end
  },
  ["s"] = {
    get = function(state)
      if not state or state.stacks == 0 then return "" end
      return state.stacks
    end,
    func = function(v)
      return v
    end
  }
};

-- Events in that list can be filtered by unitID
Private.UnitEventList = {
  PLAYER_GUILD_UPDATE = true,
  MINIMAP_PING = true,
  PARTY_MEMBER_DISABLE = true,
  PARTY_MEMBER_ENABLE = true,
  READY_CHECK_CONFIRM = true,
  PLAYER_GAINS_VEHICLE_DATA = true,
  PLAYER_LOSES_VEHICLE_DATA = true,
  KNOWN_TITLES_UPDATE = true,
  PLAYER_DAMAGE_DONE_MODS = true,
  PLAYER_FLAGS_CHANGED = true,
  PLAYER_PVP_KILLS_CHANGED = true,
  PLAYER_PVP_RANK_CHANGED = true,
  PLAYER_XP_UPDATE = true,
}

Private.InternalEventByIDList = {
  ITEM_COOLDOWN_STARTED = true,
  ITEM_COOLDOWN_CHANGED = true,
  ITEM_COOLDOWN_READY = true,
  ITEM_SLOT_COOLDOWN_STARTED = true,
  ITEM_SLOT_COOLDOWN_CHANGED = true,
  ITEM_SLOT_COOLDOWN_READY = true,
  ITEM_SLOT_COOLDOWN_ITEM_CHANGED = true,
  SPELL_COOLDOWN_CHANGED = true,
  SPELL_COOLDOWN_READY = true,
  SPELL_CHARGES_CHANGED = true,
  WA_UPDATE_OVERLAY_GLOW = true,
}
