
-- A bit of terminology
-- Templates:
--   The potential conditions that are offered by the triggers
-- The data structure returned by GetTriggerConditions(data) is
-- [] Trigger number
--   [] Condition name
--      - display: Display Name
--      - type: Type, e.g. "select", "number", "timer", "unit"
--      - values: (only for "select" and "unit")
--      - test: a test function template

-- Conditions + Changes: Actually active settings on a aura
-- Datastructure:
-- [] Index
--    - check
--      - trigger: Trigger number. Negative values indicate a special check:
--          -1: Global conditions
--          -2: Combinator
--      - variable: Variable inside the trigger state to check
--      - op: Operator to use for check
--      - value: Value to check
--      - checks: Sub Checks for Combinations, each containing trigger, variable, op, value or checks
--      - (for merged) references
--          - id => conditionIndex
--               => op
--               => value
--      - (for merged) referenceCount
--      - (for merged) samevalue
--      - (for merged) sameop
--    - changes
--      [] Index
--         - property: Property that is changed
--         - value: New value
--         - (for merged) references
--              => id => changeIndex
--                    => value
--         - (for merged) referenceCount
--         - (for merged) samevalue
--  Properties: The parts of the region than can be changed via
--             the condition system
-- [] Property Name
--   - display: A display Name
--   - setter: The setter function, called both on activating and deactivating a property change
---  - action: The action function, called on activating a condition
--   - type: The type
if not WeakAuras.IsLibsOK() then return end
---@type string
local AddonName = ...
---@class OptionsPrivate
local OptionsPrivate = select(2, ...)

---@class WeakAuras
local WeakAuras = WeakAuras;
local L = WeakAuras.L;

local SharedMedia = LibStub("LibSharedMedia-3.0");

local function addSpace(args, order)
  args["space" .. order] = {
    type = "description",
    name = "",
    image = function() return "", 0, 0 end,
    order = order,
    width = WeakAuras.normalWidth
  }
  order = order + 1;
  return order;
end

local function compareValues(a, b, propertytype)
  if (propertytype == "color") then
    if (type(a) ~= "table" or type(b) ~= "table") then
      return a == b;
    end
    return a[1] == b[1]
      and a[2] == b[2]
      and a[3] == b[3]
      and a[4] == b[4];
  elseif propertytype == "progressSource" then
    if type(a) == "table" and type(b) == "table" then
      local triggerA, propertyA, triggerB, propertyB = a[1], a[2], b[1], b[2]
      if triggerA ~= triggerB or propertyA ~= propertyB then
        return false
      end
      if triggerA == 0 then
        return a[3] == b[3] and a[4] == b[4]
      end
      return true
    end
  end
  return a == b;
end

local function valueToString(a, propertytype)
  if (propertytype == "color") then
    if (type(a) == "table") then
      local r, g, b, alpha = floor((a[1] or 0) * 255), floor((a[2] or 0) * 255), floor((a[3] or 0) * 255), floor((a[4] or 0) * 255)
      return string.format("|c%02X%02X%02X%02X", alpha, r, g, b) .. L["color"];
    else
      return "";
    end
  elseif (propertytype == "chat" or propertytype == "sound" or propertytype == "customcode"
          or propertytype == "glowexternal" or propertytype == "customcheck") then
    return tostring(a);
  elseif (propertytype == "alwaystrue") then
    return ""
  elseif (propertytype == "bool") then
    return (a == 1 or a == true) and L["True"] or L["False"];
  end
  return tostring(a);
end

local function isSubset(data, reference, totalAuraCount)
  if (data.controlledChildren) then
    if (totalAuraCount > reference.referenceCount) then
      return true;
    end
  end
  return false;
end

local function blueIfSubset(data, reference, totalAuraCount)
  if (isSubset(data, reference, totalAuraCount)) then
    return "|cFF4080FF";
  end
  return "";
end

local function blueIfNoValue(data, object, variable, blueString, normalString)
  if (data.controlledChildren) then
    if (object["same" .. variable] == false) then
      return "|cFF4080FF" .. blueString;
    end
  end
  return normalString or "";
end

local function blueIfNoValue2(data, object, variable, subvariable, blueString, normalString)
  if (data.controlledChildren) then
    if (not object["same" .. variable] or not object["same" .. variable][subvariable]) then
      return "|cFF4080FF" .. blueString;
    end
  end
  return normalString or "";
end

local function descIfSubset(data, reference, totalAuraCount)
  if (isSubset(data, reference, totalAuraCount)) then
    local desc = L["Used in auras:"];
    for id in pairs(reference.references) do
      desc = desc .. "\n" .. id;
    end
    return desc;
  end
  return "";
end

local function descIfNoValue(data, object, variable, propertyType, values)
  if (data.controlledChildren) then
    if (object["same" .. variable] == false) then
      local desc = "";
      for id, reference in pairs(object.references) do
        if propertyType == "list" and values then
          desc = desc .."|cFFE0E000".. id .. ": |r" .. (values[reference[variable]] or "") .. "\n";
        elseif propertyType == "progressSource" then
          desc = desc .."|cFFE0E000".. id .. ": |r"
          local progressSource = reference[variable]
          if type(progressSource) == "table" then
            local trigger = progressSource[1]
            if trigger == 0 then
              desc = desc .. L["Manual with %i/%i"]:format(progressSource[3] or 0, progressSource[4] or 100)
            else
              local p = OptionsPrivate.Private.GetProgressValueConstant(progressSource)
              local description = values[p] or ""
              if type(description) == "string" then
                desc = desc .. description
              elseif type(description) == "table"
                      and type(description[1]) == "string"
                      and type(description[2])  == "string"
              then
                desc = desc .. description[1] .. " " .. description[2]
              end
            end
          end
          desc = desc .."\n"
        else
          desc = desc .."|cFFE0E000".. id .. ": |r" .. (valueToString(reference[variable], propertyType) or "") .. "\n";
        end
      end
      return desc;
    end
  end
  return nil;
end

local function descIfNoValue2(data, object, variable, subvariable, type, values)
  if (data.controlledChildren) then
    if (object["same" .. variable] and object["same" .. variable][subvariable] == false) then
      local desc = "";
      for id, reference in pairs(object.references) do
        if (values) then
          desc = desc .."|cFFE0E000".. id .. ": |r" .. (values[reference[variable][subvariable]] or "") .. "\n";
        else
          desc = desc .."|cFFE0E000".. id .. ": |r" .. valueToString(reference[variable][subvariable], type or "") .. "\n";
        end
      end
      return desc;
    end
  end
  return nil;
end

local function filterUsedProperties(indexToProperty, allDisplays, usedProperties, ownProperty)
  local filtered = {};
  for index, value in pairs(allDisplays) do
    local property = indexToProperty[index];
    local isUsed = property and usedProperties[property];
    local isOwn = ownProperty and property == ownProperty;
    if ( not isUsed or isOwn) then
      filtered[index] = value;
    end
  end

  return filtered;
end

--- @type number? the time at which the last sound was played, so that we don't play
---  a sound from each setter
local lastPlayedSoundFromSet
local function wrapWithPlaySound(func, kit)
  return function(info, v)
    func(info, v);
    if (tonumber(v)) then
      if lastPlayedSoundFromSet ~= GetTime() then
        pcall(PlaySound, tonumber(v), "Master")
        lastPlayedSoundFromSet = GetTime()
      end
    else
      if lastPlayedSoundFromSet ~= GetTime() then
        pcall(PlaySoundFile, v, "Master")
        lastPlayedSoundFromSet = GetTime()
      end
    end
  end
end

local dynamicTextInputs = {}

local function addControlsForChange(args, order, data, conditionVariable, totalAuraCount, conditions, i, j, allProperties, usedProperties)
  local thenText = (j == 1) and L["Then "] or L["And "];
  local display = isSubset(data, conditions[i].changes[j], totalAuraCount) and allProperties.displayWithCopy or allProperties.display;
  local valuesForProperty = filterUsedProperties(allProperties.indexToProperty, display, usedProperties, conditions[i].changes[j].property);
  args["condition" .. i .. "property" .. j] = {
    type = "select",
    width = WeakAuras.normalWidth,
    name = blueIfSubset(data, conditions[i].changes[j], totalAuraCount) .. thenText,
    desc = descIfSubset(data, conditions[i].changes[j], totalAuraCount),
    order = order,
    values = valuesForProperty,
    control = "WeakAurasTwoColumnDropdown",
    get = function()
      local property = conditions[i].changes[j].property;
      return property and allProperties.propertyToIndex[property];
    end,
    set = function(info, index)
      local property = allProperties.indexToProperty[index];
      if (property == "COPY") then
        for child in OptionsPrivate.Private.TraverseLeafs(data) do
          if (conditions[i].changes[j].references[child.id]) then
          -- Already exist
          else
            local insertPoint = 1;
            for index = j, 1, -1 do
              if (conditions[i].changes[index].references[child.id]) then
                insertPoint = index + 1;
                break;
              end
            end

            local change = {};
            change.property = conditions[i].changes[j].property;
            if (type(conditions[i].changes[j].value) == "table") then
              change.value = CopyTable(conditions[i].changes[j].value)
            else
              change.value = conditions[i].changes[j].value;
            end

            local reference = conditions[i].check.references[child.id]
            if reference then
              local conditionIndex = reference.conditionIndex;
              tinsert(child[conditionVariable][conditionIndex].changes, insertPoint, change);
              WeakAuras.Add(child);
              OptionsPrivate.ClearOptions(child.id)
            end
          end
        end
        WeakAuras.ClearAndUpdateOptions(data.id)
        return;
      elseif (property == "DELETE") then
        if (data.controlledChildren) then
          for id, reference in pairs(conditions[i].changes[j].references) do
            local auraData = WeakAuras.GetData(id);
            local conditionIndex = conditions[i].check.references[id].conditionIndex;
            tremove(auraData[conditionVariable][conditionIndex].changes, reference.changeIndex);
            WeakAuras.Add(auraData);
            OptionsPrivate.ClearOptions(auraData.id)
          end
          WeakAuras.ClearAndUpdateOptions(data.id)
        else
          tremove(conditions[i].changes, j);
          WeakAuras.Add(data);
          WeakAuras.ClearAndUpdateOptions(data.id)
        end
        return;
      end

      local default = allProperties.propertyMap[property].default;
      if (data.controlledChildren) then
        for id, reference in pairs(conditions[i].changes[j].references) do
          local auraData = WeakAuras.GetData(id);
          local conditionIndex = conditions[i].check.references[id].conditionIndex;
          auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].property = property;
          auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value = default;
          WeakAuras.Add(auraData);
          OptionsPrivate.ClearOptions(auraData.id)
        end
        conditions[i].changes[j].property = property;
        WeakAuras.ClearAndUpdateOptions(data.id)
      else
        conditions[i].changes[j].property = property;
        conditions[i].changes[j].value = default;
        WeakAuras.Add(data);
        WeakAuras.ClearAndUpdateOptions(data.id)
      end
    end
  }
  order = order + 1;

  local setValue;
  local setValueTable
  local setValueColor;
  local setValueComplex;
  local setValueColorComplex;
  if (data.controlledChildren) then
    setValue = function(info, v)
      for id, reference in pairs(conditions[i].changes[j].references) do
        local auraData = WeakAuras.GetData(id);
        local conditionIndex = conditions[i].check.references[id].conditionIndex;
        auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value = v;
        WeakAuras.Add(auraData);
        OptionsPrivate.ClearOptions(auraData.id)
      end
      conditions[i].changes[j].value = v;
      WeakAuras.ClearAndUpdateOptions(data.id)
    end
    setValueTable = function(info, v)
      for id, reference in pairs(conditions[i].changes[j].references) do
        local auraData = WeakAuras.GetData(id)
        local conditionIndex = conditions[i].check.references[id].conditionIndex
        auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value = CopyTable(v)
        WeakAuras.Add(auraData)
        OptionsPrivate.ClearOptions(auraData.id)
      end
      conditions[i].changes[j].value = CopyTable(v)
      WeakAuras.ClearAndUpdateOptions(data.id)
    end
    setValueColor = function(info, r, g, b, a)
      for id, reference in pairs(conditions[i].changes[j].references) do
        local auraData = WeakAuras.GetData(id);
        local conditionIndex = conditions[i].check.references[id].conditionIndex;
        auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value = auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value or {};
        auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value[1] = r;
        auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value[2] = g;
        auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value[3] = b;
        auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value[4] = a;
        WeakAuras.Add(auraData);
        OptionsPrivate.ClearOptions(auraData.id)
      end
      conditions[i].changes[j].value = conditions[i].changes[j].value or {};
      conditions[i].changes[j].value[1] = r;
      conditions[i].changes[j].value[2] = g;
      conditions[i].changes[j].value[3] = b;
      conditions[i].changes[j].value[4] = a;
      WeakAuras.ClearAndUpdateOptions(data.id)
    end

    setValueComplex = function(property)
      return function(info, v)
        for id, reference in pairs(conditions[i].changes[j].references) do
          local auraData = WeakAuras.GetData(id);
          local conditionIndex = conditions[i].check.references[id].conditionIndex;
          if (type(auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value) ~= "table") then
            auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value = {};
          end
          auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value[property] = v;
          WeakAuras.Add(auraData);
          OptionsPrivate.ClearOptions(auraData.id)
        end
        if (type(conditions[i].changes[j].value) ~= "table") then
          conditions[i].changes[j].value = {};
        end
        conditions[i].changes[j].value[property] = v;
        WeakAuras.ClearAndUpdateOptions(data.id)
      end
    end

    setValueColorComplex = function(property)
      return function(info, r, g, b, a)
        for id, reference in pairs(conditions[i].changes[j].references) do
          local auraData = WeakAuras.GetData(id);
          local conditionIndex = conditions[i].check.references[id].conditionIndex;
          if (type(auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value) ~= "table") then
            auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value = {};
          end
          if (type(auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value[property]) ~= "table") then
            auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value[property] = {};
          end
          auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value[property][1] = r;
          auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value[property][2] = g;
          auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value[property][3] = b;
          auraData[conditionVariable][conditionIndex].changes[reference.changeIndex].value[property][4] = a;
          WeakAuras.Add(auraData);
          OptionsPrivate.ClearOptions(auraData.id)
        end
        if (type(conditions[i].changes[j].value) ~= "table") then
          conditions[i].changes[j].value = {};
        end
        if (type(conditions[i].changes[j].value[property]) ~= "table") then
          conditions[i].changes[j].value[property] = {};
        end
        conditions[i].changes[j].value[property][1] = r;
        conditions[i].changes[j].value[property][2] = g;
        conditions[i].changes[j].value[property][3] = b;
        conditions[i].changes[j].value[property][4] = a;
        WeakAuras.ClearAndUpdateOptions(data.id)
      end
    end
  else
    setValue = function(info, v)
      conditions[i].changes[j].value = v;
      WeakAuras.Add(data);
      WeakAuras.ClearAndUpdateOptions(data.id)
    end
    setValueTable = function(info, v)
      conditions[i].changes[j].value = CopyTable(v)
      WeakAuras.Add(data)
      WeakAuras.ClearAndUpdateOptions(data.id)
    end
    setValueColor = function(info, r, g, b, a)
      conditions[i].changes[j].value = conditions[i].changes[j].value or {};
      conditions[i].changes[j].value[1] = r;
      conditions[i].changes[j].value[2] = g;
      conditions[i].changes[j].value[3] = b;
      conditions[i].changes[j].value[4] = a;
      WeakAuras.Add(data);
      WeakAuras.ClearAndUpdateOptions(data.id)
    end

    setValueComplex = function(property)
      return function(info, v)
        if (type (conditions[i].changes[j].value) ~= "table") then
          conditions[i].changes[j].value = {};
        end
        conditions[i].changes[j].value[property] = v;
        WeakAuras.Add(data);
        WeakAuras.ClearAndUpdateOptions(data.id)
      end
    end

    setValueColorComplex = function(property)
      return function(info, r, g, b, a)
        if (type (conditions[i].changes[j].value) ~= "table") then
          conditions[i].changes[j].value = {};
        end
        if (type (conditions[i].changes[j].value[property]) ~= "table") then
          conditions[i].changes[j].value[property] = {};
        end
        conditions[i].changes[j].value[property][1] = r;
        conditions[i].changes[j].value[property][2] = g;
        conditions[i].changes[j].value[property][3] = b;
        conditions[i].changes[j].value[property][4] = a;
        WeakAuras.Add(data);
        WeakAuras.ClearAndUpdateOptions(data.id)
      end
    end
  end

  local propertyType;
  local property = conditions[i].changes[j].property;
  if (property) then
    propertyType = allProperties.propertyMap[property] and allProperties.propertyMap[property].type;
  end
  if (propertyType == "bool" or propertyType == "number") then
    args["condition" .. i .. "value" .. j] = {
      type = "toggle",
      width = WeakAuras.normalWidth,
      desc = descIfNoValue(data, conditions[i].changes[j], "value", propertyType),
      order = order,
      get = function()
        return conditions[i].changes[j].value;
      end,
      set = setValue
    }
    order = order + 1;
    if (propertyType == "number") then
      args["condition" .. i .. "value" .. j].name = blueIfNoValue(data, conditions[i].changes[j], "value", L["Differences"])
      local properties = allProperties.propertyMap[property];
      if (properties.min or properties.softMin) and (properties.max or properties.softMax) then
        args["condition" .. i .. "value" .. j].type = "range";
        args["condition" .. i .. "value" .. j].control = "WeakAurasSpinBox"
        args["condition" .. i .. "value" .. j].min = properties.min;
        args["condition" .. i .. "value" .. j].softMin = properties.softMin;
        args["condition" .. i .. "value" .. j].max = properties.max;
        args["condition" .. i .. "value" .. j].softMax = properties.softMax;
        args["condition" .. i .. "value" .. j].step = properties.step;
        args["condition" .. i .. "value" .. j].bigStep = properties.bigStep;
        args["condition" .. i .. "value" .. j].isPercent = properties.isPercent;
      else
        args["condition" .. i .. "value" .. j].type = "input";
        args["condition" .. i .. "value" .. j].validate = WeakAuras.ValidateNumeric;
      end
    else
      args["condition" .. i .. "value" .. j].name = function()
        local value = conditions[i].changes[j].value
        return blueIfNoValue(data, conditions[i].changes[j], "value", L["Differences"], value and L["ON"] or L["OFF"])
      end
    end
  elseif (propertyType == "string" or propertyType == "texture") then
    args["condition" .. i .. "value" .. j] = {
      type = "input",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue(data, conditions[i].changes[j], "value", L["Text"]),
      desc = descIfNoValue(data, conditions[i].changes[j], "value", propertyType),
      order = order,
      get = function()
        return conditions[i].changes[j].value;
      end,
      set = setValue,
      control = allProperties.propertyMap[property].control
    }
    order = order + 1;
    if propertyType == "texture" then
      args["condition" .. i .. "value" .. j].width =  WeakAuras.normalWidth - 0.15
      args["condition" .. i .. "value_browse" .. j] = {
        type = "execute",
        name = L["Choose"],
        width = 0.15,
        order = order,
        func = function()
          if data.controlledChildren then
            local paths = {}
            for id, reference in pairs(conditions[i].changes[j].references) do
              paths[id] = {"conditions", conditions[i].check.references[id].conditionIndex, "changes", reference.changeIndex}
            end
            OptionsPrivate.OpenTexturePicker(data, paths,
                                             {texture = "value"},
                                             OptionsPrivate.Private.texture_types)
          else
            OptionsPrivate.OpenTexturePicker(data, {[data.id] = { "conditions", i, "changes", j } },
                                             {texture = "value"},
                                             OptionsPrivate.Private.texture_types)
          end
        end,
        imageWidth = 24,
        imageHeight = 24,
        control = "WeakAurasIcon",
        image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\browse",
      }
      order = order + 1;
    end
  elseif (propertyType == "icon") then
    args["condition" .. i .. "value" .. j] = {
      type = "input",
      width = WeakAuras.normalWidth - 0.15,
      name = blueIfNoValue(data, conditions[i].changes[j], "value", L["Differences"]),
      desc = descIfNoValue(data, conditions[i].changes[j], "value", propertyType),
      order = order,
      get = function()
        local v = conditions[i].changes[j].value
        return v and tostring(v)
      end,
      set = setValue
    }
    order = order + 1
    args["condition" .. i .. "value_browse" .. j] = {
      type = "execute",
      width = 0.15,
      name = "",
      order = order,
      func = function()
        if data.controlledChildren then
          local paths = {}
          for id, reference in pairs(conditions[i].changes[j].references) do
            paths[id] = {"conditions", conditions[i].check.references[id].conditionIndex, "changes", reference.changeIndex, "value"}
          end
          OptionsPrivate.OpenIconPicker(data, paths)
        else
          OptionsPrivate.OpenIconPicker(data, {[data.id] = { "conditions", i, "changes", j, "value" } })
        end
      end,
      imageWidth = 24,
      imageHeight = 24,
      control = "WeakAurasIcon",
      image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\browse",
    }
  elseif (propertyType == "color") then
    args["condition" .. i .. "value" .. j] = {
      type = "color",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue(data, conditions[i].changes[j], "value", L["Differences"]),
      desc = descIfNoValue(data, conditions[i].changes[j], "value", propertyType),
      order = order,
      hasAlpha = true,
      get = function()
        if (conditions[i].changes[j].value and type(conditions[i].changes[j].value) == "table") then
          return conditions[i].changes[j].value[1], conditions[i].changes[j].value[2], conditions[i].changes[j].value[3], conditions[i].changes[j].value[4];
        end
        return 1, 1, 1, 1;
      end,
      set = setValueColor
    }
    order = order + 1;
  elseif (propertyType == "list" or propertyType == "progressSource" or propertyType == "textureLSM") then
    local values = property and allProperties.propertyMap[property] and allProperties.propertyMap[property].values;
    local dialogControl

    if propertyType == "textureLSM" then
      dialogControl = "WA_LSM30_StatusbarAtlas"
      local statusbarList = {}
      Mixin(statusbarList, SharedMedia:HashTable("statusbar"))
      Mixin(statusbarList, SharedMedia:HashTable("statusbar_atlas"))
      values = statusbarList
    end

    args["condition" .. i .. "value" .. j] = {
      type = "select",
      width = WeakAuras.normalWidth,
      values = values,
      dialogControl = dialogControl,
      name =  blueIfNoValue(data, conditions[i].changes[j], "value", L["Differences"], ""),
      desc =  descIfNoValue(data, conditions[i].changes[j], "value", propertyType, values),
      order = order,
      get = function()
        return conditions[i].changes[j].value;
      end,
      set = setValue,
    }
    order = order + 1

    if propertyType == "progressSource" then
      args["condition" .. i .. "value" .. j].control = "WeakAurasTwoColumnDropdown"
      args["condition" .. i .. "value" .. j].set = setValueTable
      args["condition" .. i .. "value" .. j].get = function()
        local v = conditions[i].changes[j].value
        return OptionsPrivate.Private.GetProgressValueConstant(v)
      end

      args["condition" .. i .. "progressSourceWarning" .. j] = {
        type = "description",
        width = WeakAuras.doubleWidth,
        name = L["Note: This progress source does not provide a total value/duration. A total value/duration must be set via \"Set Maximum Progress\""],
        order = order,
        hidden = function()
          local v = conditions[i].changes[j].value
          local progressSource = OptionsPrivate.Private.AddProgressSourceMetaData(data, v)
          -- Auto progress, Manual Progress or the progress source has a total property
          if progressSource and (progressSource[2] == "auto" or progressSource[1] == 0 or progressSource[4] ~= nil) then
            return true
          end
          return false
        end
      }
      order = order + 1

      local function hiddenManual()
        local v = conditions[i].changes[j].value
        local progressSource = OptionsPrivate.Private.AddProgressSourceMetaData(data, v)
        if progressSource and progressSource[1] == 0 then
          return false
        end
        return true
      end

      args["condition" .. i .. "progressSourceManualValue" .. j] = {
        type = "range",
        control = "WeakAurasSpinBox",
        width = WeakAuras.normalWidth,
        name = L["Value"],
        order = order,
        min = 0,
        softMax = 100,
        bigStep = 1,
        hidden = hiddenManual,
        get = function()
          local v = conditions[i].changes[j].value
          return v and type(v[3]) == "number" and v[3] or 0
        end,
        set = setValueComplex(3)
      }
      order = order + 1

      args["condition" .. i .. "progressSourceManualTotal" .. j] = {
        type = "range",
        control = "WeakAurasSpinBox",
        width = WeakAuras.normalWidth,
        name = L["Total"],
        order = order,
        min = 0,
        softMax = 100,
        bigStep = 1,
        hidden = hiddenManual,
        get = function()
          local v = conditions[i].changes[j].value
          return v and type(v[4]) == "number" and v[4] or 100
        end,
        set = setValueComplex(4)
      }
      order = order + 1
    end
  elseif (propertyType == "sound") then
    args["condition" .. i .. "value" .. j .. "sound_type"] = {
      type = "select",
      width = WeakAuras.normalWidth,
      values = OptionsPrivate.Private.sound_condition_types,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "sound_type", L["Differences"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "sound_type", propertyType, OptionsPrivate.Private.sound_condition_types),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.sound_type;
      end,
      set = setValueComplex("sound_type"),
    }
    order = order + 1;

    local function anySoundType(needle)
      local sound_type = type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.sound_type;
      if (sound_type) then
        return sound_type == needle;
      end
      if (conditions[i].changes[j].references) then
        for id, reference in pairs(conditions[i].changes[j].references) do
          if (type(reference.value) == "table" and reference.value.sound_type == needle) then
            return true;
          end
        end
      end
      return false;
    end

    args["condition" .. i .. "value" .. j .. "sound"] = {
      type = "select",
      width = WeakAuras.normalWidth,
      values = OptionsPrivate.Private.sound_types,
      sorting = OptionsPrivate.Private.SortOrderForValues(OptionsPrivate.Private.sound_types),
      itemControl = "WeakAurasMediaSound",
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "sound", L["Differences"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "sound", propertyType, OptionsPrivate.Private.sound_types),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.sound;
      end,
      set = wrapWithPlaySound(setValueComplex("sound")),
      hidden = function() return not (anySoundType("Play") or anySoundType("Loop")) end
    }
    order = order + 1;

    args["condition" .. i .. "value" .. j .. "sound_channel"] = {
      type = "select",
      width = WeakAuras.normalWidth,
      values = OptionsPrivate.Private.sound_channel_types,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "sound_channel", L["Sound Channel"], L["Sound Channel"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "sound_channel", propertyType, OptionsPrivate.Private.sound_channel_types),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.sound_channel;
      end,
      set = setValueComplex("sound_channel"),
      hidden = function() return not (anySoundType("Loop") or anySoundType("Play")) end
    }
    order = order + 1;

    args["condition" .. i .. "value" .. j .. "sound_repeat"] = {
      type = "range",
      control = "WeakAurasSpinBox",
      width = WeakAuras.normalWidth,
      min = 0,
      softMax = 60,
      bigStep = 1,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "sound_repeat", L["Repeat every"], L["Repeat every"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "sound_repeat", propertyType),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.sound_repeat;
      end,
      set = setValueComplex("sound_repeat"),
      disabled = function() return not anySoundType("Loop") end,
      hidden = function() return not (anySoundType("Loop")) end
    }
    order = order + 1;

    args["condition" .. i .. "value" .. j .. "sound_repeat_space"] = {
      type = "description",
      width = WeakAuras.normalWidth,
      name = "",
      order = order,
      hidden = function() return not (anySoundType("Loop")) end
    }
    order = order + 1;

    local function anySoundValue(needle)
      local sound_type = type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.sound;
      if (sound_type) then
        return sound_type == needle;
      end
      if (conditions[i].changes[j].references) then
        for id, reference in pairs(conditions[i].changes[j].references) do
          if (type(reference.value) == "table" and reference.value.sound == needle) then
            return true;
          end
        end
      end
      return false;
    end

    args["condition" .. i .. "value" .. j .. "sound_path"] = {
      type = "input",
      width = WeakAuras.doubleWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "sound_path", L["Sound File Path"], L["Sound File Path"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "sound_path", propertyType),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.sound_path;
      end,
      set = wrapWithPlaySound(setValueComplex("sound_path")),
      hidden = function() return not (anySoundValue(" custom") and (anySoundType("Loop") or anySoundType("Play"))) end
    }
    order = order + 1;

    args["condition" .. i .. "value" .. j .. "sound_kit_id"] = {
      type = "input",
      width = WeakAuras.doubleWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "sound_kit_id", L["Sound Kit ID"], L["Sound Kit ID"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "sound_kit_id", propertyType),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.sound_kit_id;
      end,
      set = wrapWithPlaySound(setValueComplex("sound_kit_id")),
      hidden = function() return not (anySoundValue(" KitID")  and (anySoundType("Loop") or anySoundType("Play"))) end
    }
    order = order + 1;

    args["condition" .. i .. "value" .. j .. "sound_fade"] = {
      type = "range",
      control = "WeakAurasSpinBox",
      width = WeakAuras.normalWidth,
      min = 0,
      softMax = 10,
      bigStep = 1,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "sound_fade", L["Fadeout Time (seconds)"], L["Fadeout Time (seconds)"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "sound_fade", propertyType),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.sound_fade;
      end,
      set = setValueComplex("sound_fade"),
      disabled = function() return not anySoundType("Stop") end,
      hidden = function() return not (anySoundType("Stop")) end
    }
    order = order + 1;

    args["condition" .. i .. "value" .. j .. "sound_fade_space"] = {
      type = "description",
      width = WeakAuras.normalWidth,
      name = "",
      order = order,
      hidden = function() return not (anySoundType("Stop")) end
    }
    order = order + 1;


  elseif (propertyType == "chat") then
    args["condition" .. i .. "value" .. j .. "message type"] = {
      type = "select",
      width = WeakAuras.normalWidth,
      values = OptionsPrivate.Private.send_chat_message_types,
      sorting = OptionsPrivate.Private.SortOrderForValues(OptionsPrivate.Private.send_chat_message_types),
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "message_type", L["Differences"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "message_type", propertyType, OptionsPrivate.Private.send_chat_message_types),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.message_type;
      end,
      set = setValueComplex("message_type"),
    }
    order = order + 1;

    local function anyMessageType(needle)
      local message_type = type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.message_type;
      if (message_type) then
        return message_type == needle;
      end
      if (conditions[i].changes[j].references) then
        for id, reference in pairs(conditions[i].changes[j].references) do
          if (type(reference.value) == "table" and reference.value.message_type == needle) then
            return true;
          end
        end
      end
      return false;
    end

    if WeakAuras.IsRetail() then
      args["condition" .. i .. "value" .. j .. "message type warning"] = {
        type = "description",
        width = WeakAuras.doubleWidth,
        name = L["Note: Automated Messages to SAY and YELL are blocked outside of Instances."],
        order = order,
        hidden = function()
          return not (anyMessageType("SAY") or anyMessageType("YELL") or anyMessageType("SMARTRAID"));
        end
      }
      order = order + 1;
    end

    args["condition" .. i .. "value" .. j .. "_indent"] = {
      type = "description",
      width = WeakAuras.normalWidth,
      name = "",
      order = order,
      hidden = function()
        return anyMessageType("WHISPER");
      end
    }
    order = order + 1;

    args["condition" .. i .. "value" .. j .. "message color"] = {
      type = "color",
      width = WeakAuras.normalWidth,
      hasAlpha = false,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "message_color", L["Color"], L["Color"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "message_color", propertyType),
      order = order,
      get = function()
        if (conditions[i].changes[j].value and type(conditions[i].changes[j].value) == "table") and type(conditions[i].changes[j].value.message_color) == "table" then
          return conditions[i].changes[j].value.message_color[1], conditions[i].changes[j].value.message_color[2], conditions[i].changes[j].value.message_color[3];
        end
        return 1, 1, 1, 1;
      end,
      set = setValueColorComplex("message_color"),
      hidden = function()
        return not (anyMessageType("COMBAT") or anyMessageType("PRINT") or anyMessageType("ERROR"));
      end
    }
    order = order + 1;

    args["condition" .. i .. "value" .. j .. "message dest"] = {
      type = "input",
      width = WeakAuras.normalWidth - 0.15,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "message_dest", L["Send To"], L["Send To"]),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.message_dest;
      end,
      set = setValueComplex("message_dest"),
      hidden = function()
        return not anyMessageType("WHISPER");
      end,
      control = "WeakAurasInput",
      callbacks = {
        OnEditFocusGained = function(self)
          local widget = dynamicTextInputs["condition" .. i .. "value" .. j .. "message dest"]
          OptionsPrivate.ToggleTextReplacements(data, widget, "OnEditFocusGained")
        end,
        OnEditFocusLost = function(self)
          OptionsPrivate.ToggleTextReplacements(nil, nil, "OnEditFocusLost")
        end,
        OnEnterPressed = function(self)
          OptionsPrivate.ToggleTextReplacements(nil, nil, "OnEnterPressed")
        end,
        OnShow = function(self)
          dynamicTextInputs["condition" .. i .. "value" .. j .. "message dest"] = self
        end,
      }
    }
    order = order + 1;

    args["condition" .. i .. "value" .. j .. "message dest_text_replacements_button"] = {
      type = "execute",
      width = 0.15,
      name = L["Dynamic Text Replacements"],
      desc = L["There are several special codes available to make this text dynamic. Click to view a list with all dynamic text codes."],
      order = order,
      hidden = function()
        return not anyMessageType("WHISPER");
      end,
      func = function()
        local widget = dynamicTextInputs["condition" .. i .. "value" .. j .. "message dest"]
        OptionsPrivate.ToggleTextReplacements(data, widget, "ToggleButton")
      end,
      imageWidth = 24,
      imageHeight = 24,
      control = "WeakAurasIcon",
      image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\sidebar",
    }
    order = order + 1;

    args["condition" .. i .. "value" .. j] = {
      type = "toggle",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue(data, conditions[i].changes[j], "value", "message_dest_isunit", L["Is Unit"]),
      desc = descIfNoValue(data, conditions[i].changes[j], "value", "message_dest_isunit", propertyType),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.message_dest_isunit;
      end,
      set = setValueComplex("message_dest_isunit"),
      hidden = function()
        return not anyMessageType("WHISPER");
      end
    }
    order = order + 1;

    args["condition" .. i .. "value" .. j .. "message voice"] = {
      type = "execute",
      name = L["Voice Settings"],
      order = order,
      width = WeakAuras.normalWidth,
      func = function()
        ShowUIPanel(ChatConfigFrame)
        ChatConfigFrameChatTabManager:UpdateSelection(VOICE_WINDOW_ID)
        end,
      hidden = function()
        return not anyMessageType("TTS");
      end,
    }
    order = order + 1;

    local message_getter = function()
      return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.message;
    end

    args["condition" .. i .. "value" .. j .. "message"] = {
      type = "input",
      width = WeakAuras.doubleWidth - 0.15,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "message", L["Message"], L["Message"]),
      order = order,
      get = message_getter,
      set = setValueComplex("message"),
      control = "WeakAurasInput",
      callbacks = {
        OnEditFocusGained = function(self)
          local widget = dynamicTextInputs["condition" .. i .. "value" .. j .. "message"]
          OptionsPrivate.ToggleTextReplacements(data, widget, "OnEditFocusGained")
        end,
        OnEditFocusLost = function(self)
          OptionsPrivate.ToggleTextReplacements(nil, nil, "OnEditFocusLost")
        end,
        OnEnterPressed = function(self)
          OptionsPrivate.ToggleTextReplacements(nil, nil, "OnEnterPressed")
        end,
        OnShow = function(self)
          dynamicTextInputs["condition" .. i .. "value" .. j .. "message"] = self
        end,
      }
    }
    order = order + 1;

    args["condition" .. i .. "value" .. j .. "message_text_replacements_button"] = {
      type = "execute",
      width = 0.15,
      name = L["Dynamic Text Replacements"],
      desc = L["There are several special codes available to make this text dynamic. Click to view a list with all dynamic text codes."],
      order = order,
      func = function()
        local widget = dynamicTextInputs["condition" .. i .. "value" .. j .. "message"]
        OptionsPrivate.ToggleTextReplacements(data, widget, "ToggleButton")
      end,
      imageWidth = 24,
      imageHeight = 24,
      control = "WeakAurasIcon",
      image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\sidebar",
    }
    order = order + 1;


    local formatGet = function(key)
      return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value["message_format_" .. key]
    end

    local usedKeys = {}
    local function addOption(key, option)
      if usedKeys[key] then
        return
      end
      usedKeys[key] = true
      option.order = order
      order = order + 0.01
      local fullKey = "condition" .. i .. "value" .. j .. "message_format_" .. key
      option.get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value["message_format_" .. key];
      end
      local originalName = option.name
      if option.type ~= "header" then
        option.name = blueIfNoValue2(data, conditions[i].changes[j], "value", "message_format_" .. key, originalName, originalName)
        option.desc = descIfNoValue2(data, conditions[i].changes[j], "value", "message_format_" .. key, nil, option.values)
      end

      option.set = setValueComplex("message_format_" .. key)

      args[fullKey] = option
    end

    local hasTextFormatOption

    local hidden = function()
      return OptionsPrivate.IsCollapsed("format_option", "conditions", i .. "#" .. j , true)
    end

    local setHidden = function(hidden)
      OptionsPrivate.SetCollapsed("format_option", "conditions", i .. "#" .. j, hidden)
    end

    if data.controlledChildren then
      local ordered = {}
      for _, reference in pairs(conditions[i].changes[j].references) do
        tinsert(ordered, reference)
      end
      for index, reference in ipairs(ordered) do
        local input = reference.value and reference.value.message
        hasTextFormatOption = OptionsPrivate.AddTextFormatOption(input, true, formatGet, addOption, hidden, setHidden, true, index, #ordered)
      end
    else
      local input = type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value["message"]
      hasTextFormatOption = OptionsPrivate.AddTextFormatOption(input, true, formatGet, addOption, hidden, setHidden, true)
    end

    if hasTextFormatOption then
      local footerOption = {
        type = "header",
        name = "",
        width = WeakAuras.doubleWidth
      }
      addOption("footer", footerOption)
    end

    local function customHidden()
      local message = type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.message;
      local message_dest = type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.message_type == "WHISPER" and conditions[i].changes[j].value.message_dest
      if (not message and not message_dest) then return true; end
      return not OptionsPrivate.Private.ContainsCustomPlaceHolder(message) and not OptionsPrivate.Private.ContainsCustomPlaceHolder(message_dest);
    end

    args["condition" .. i .. "value" .. j .. "custom"] = {
      type = "input",
      width = WeakAuras.doubleWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "custom", L["Custom Code"], L["Custom Code"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "custom", propertyType),
      order = order,
      multiline = true,
      hidden = customHidden,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.custom;
      end,
      control = "WeakAurasMultiLineEditBox",
      set = setValueComplex("custom"),
      arg = {
        extraFunctions = {
          {
            buttonLabel = L["Expand"],
            func = function()
              if (data.controlledChildren) then
                -- Collect multi paths
                local multipath = {};
                for id, reference in pairs(conditions[i].changes[j].references) do
                  local conditionIndex = conditions[i].check.references[id].conditionIndex;
                  local changeIndex = reference.changeIndex;
                  multipath[id] = {"conditions", conditionIndex, "changes", changeIndex, "value", "custom"};
                end
                OptionsPrivate.OpenTextEditor(data, multipath, nil, true, nil, nil, "https://github.com/WeakAuras/WeakAuras2/wiki/Custom-Code-Blocks#chat-message---custom-code-1");
              else
                OptionsPrivate.OpenTextEditor(data, {"conditions", i, "changes", j, "value", "custom"}, nil, nil, nil, nil, "https://github.com/WeakAuras/WeakAuras2/wiki/Custom-Code-Blocks#chat-message---custom-code-1");
              end
            end
          }
        }
      }
    }

    order = order + 1;

    args["condition" .. i .. "value" .. j .. "custom_error"] = {
      type = "description",
      name = function()
        local custom = type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.custom;
        if not custom then
          return "";
        end
        local _, errorString = loadstring("return  " .. custom);
        return errorString and "|cFFFF0000"..errorString or "";
      end,
      width = WeakAuras.doubleWidth,
      order = order,
      hidden = function()
        local message = type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.message;
        if (not message) then
          return true;
        end
        if (not OptionsPrivate.Private.ContainsCustomPlaceHolder(message)) then
          return true;
        end

        local custom = type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.custom;

        if (not custom) then
          return true;
        end

        local loadedFunction, errorString = loadstring("return " .. custom);
        if(errorString and not loadedFunction) then
          return false;
        else
          return true;
        end
      end
    }
    order = order + 1;

  elseif(propertyType == "customcode") then
    order = addSpace(args, order);

    args["condition" .. i .. "value" .. j .. "custom"] = {
      type = "input",
      width = WeakAuras.doubleWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "message_custom", L["Custom Code"], L["Custom Code"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "message_custom", propertyType),
      order = order,
      multiline = true,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.custom;
      end,
      control = "WeakAurasMultiLineEditBox",
      set = setValueComplex("custom"),
      arg = {
        extraFunctions = {
          {
            buttonLabel = L["Expand"],
            func = function()
              if (data.controlledChildren) then
                -- Collect multi paths
                local multipath = {};
                for id, reference in pairs(conditions[i].changes[j].references) do
                  local conditionIndex = conditions[i].check.references[id].conditionIndex;
                  local changeIndex = reference.changeIndex;
                  local childData = WeakAuras.GetData(id);
                  childData.conditions[conditionIndex].changes[changeIndex].value = childData.conditions[conditionIndex].changes[changeIndex].value or {};
                  multipath[id] = {"conditions", conditionIndex, "changes", changeIndex, "value", "custom"};
                end
                OptionsPrivate.OpenTextEditor(data, multipath, true, true, nil, nil, "https://github.com/WeakAuras/WeakAuras2/wiki/Custom-Code-Blocks#run-custom-code");
              else
                data.conditions[i].changes[j].value = data.conditions[i].changes[j].value or {};
                OptionsPrivate.OpenTextEditor(data, {"conditions", i, "changes", j, "value", "custom"}, true, nil, nil, nil, "https://github.com/WeakAuras/WeakAuras2/wiki/Custom-Code-Blocks#run-custom-code");
              end
            end
          }
        }
      }
    }
    order = order + 1;

    args["condition" .. i .. "value" .. j .. "custom_error"] = {
      type = "description",
      name = function()
        local custom = type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.custom;
        if not custom then
          return "";
        end
        local _, errorString = loadstring("return function() " .. custom .. "\n end");
        return errorString and "|cFFFF0000"..errorString or "";
      end,
      width = WeakAuras.doubleWidth,
      order = order,
      hidden = function()
        local custom = type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.custom;

        if (not custom) then
          return true;
        end
        local loadedFunction, errorString = loadstring("return function() " .. custom .. "\n end");
        if(errorString and not loadedFunction) then
          return false;
        else
          return true;
        end
      end
    }
    order = order + 1;
  elseif (propertyType == "glowexternal") then
    local function anyGlowExternal(property, needle)
      local ref = type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value[property]
      if ref then
        if type(needle) == "table" then
          return needle[ref]
        else
          return ref == needle
        end
      end
      if conditions[i].changes[j].references then
        for id, reference in pairs(conditions[i].changes[j].references) do
          if type(reference.value) == "table" then
            if type(needle) == "table" then
              if needle[reference.value[property]] then
                return true
              end
            else
              if reference.value[property] == needle then
                return true
              end
            end
          end
        end
      end
      return false
    end

    local glowTypesExcepButtonOverlay = CopyTable(OptionsPrivate.Private.glow_types)
    glowTypesExcepButtonOverlay["buttonOverlay"] = nil
    local glowTypesExcepButtonOverlayAndProc = CopyTable(OptionsPrivate.Private.glow_types)
    glowTypesExcepButtonOverlayAndProc["buttonOverlay"] = nil
    glowTypesExcepButtonOverlayAndProc["Proc"] = nil

    args["condition" .. i .. "value" .. j .. "glow_action"] = {
      type = "select",
      values = OptionsPrivate.Private.glow_action_types,
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_action", L["Glow Action"], L["Glow Action"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_action", propertyType, OptionsPrivate.Private.glow_action_types),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_action;
      end,
      set = setValueComplex("glow_action")
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_frame_type"] = {
      type = "select",
      values = OptionsPrivate.Private.glow_frame_types,
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_frame_type", L["Glow Frame Type"], L["Glow Frame Type"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_frame_type", propertyType, OptionsPrivate.Private.glow_frame_types),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_frame_type;
      end,
      hidden = function() return not anyGlowExternal("glow_action", OptionsPrivate.Private.glow_action_types) end,
      set = setValueComplex("glow_frame_type")
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_type"] = {
      type = "select",
      values = OptionsPrivate.Private.glow_types,
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_type", L["Glow Type"], L["Glow Type"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_type", propertyType, OptionsPrivate.Private.glow_types),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_type;
      end,
      set = setValueComplex("glow_type"),
      hidden = function()
        return not (anyGlowExternal("glow_action", "show") and anyGlowExternal("glow_frame_type", OptionsPrivate.Private.glow_frame_types))
      end
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_frame"] = {
      type = "input",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_frame", L["Frame"], L["Frame"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_frame", propertyType),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_frame;
      end,
      set = setValueComplex("glow_frame"),
      hidden = function()
        return not anyGlowExternal("glow_frame_type", "FRAMESELECTOR")
      end
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "choose_glow_frame"] = {
      type = "execute",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_frame", L["Choose"], L["Choose"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_frame", propertyType),
      order = order,
      func = function()
        OptionsPrivate.StartFrameChooser(data, {"conditions", i, "changes", j, "value", "glow_frame"});
      end,
      hidden = function()
        return not anyGlowExternal("glow_frame_type", "FRAMESELECTOR")
      end
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "use_glow_color"] = {
      type = "toggle",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "use_glow_color", L["Glow Color"], L["Glow Color"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "use_glow_color", propertyType),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.use_glow_color;
      end,
      set = setValueComplex("use_glow_color"),
      hidden = function()
        return not (anyGlowExternal("glow_action", "show") and anyGlowExternal("glow_type", OptionsPrivate.Private.glow_types))
      end
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_color"] = {
      type = "color",
      hasAlpha = true,
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_color", L["Glow Color"], L["Glow Color"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_color", "color"),
      order = order,
      get = function()
        if (conditions[i].changes[j].value and type(conditions[i].changes[j].value) == "table") and type(conditions[i].changes[j].value.glow_color) == "table" then
          return conditions[i].changes[j].value.glow_color[1], conditions[i].changes[j].value.glow_color[2], conditions[i].changes[j].value.glow_color[3], conditions[i].changes[j].value.glow_color[4];
        end
        return 1, 1, 1, 1;
      end,
      set = setValueColorComplex("glow_color"),
      hidden = function()
        return not (anyGlowExternal("glow_action", "show")
                    and anyGlowExternal("glow_frame_type", OptionsPrivate.Private.glow_frame_types)
                    and anyGlowExternal("glow_type", OptionsPrivate.Private.glow_types))
      end,
      disabled = function() return not anyGlowExternal("use_glow_color", true) end
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_startAnim"] = {
      type = "toggle",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_startAnim", L["Start Animation"], L["Start Animation"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_startAnim", propertyType),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_startAnim;
      end,
      set = setValueComplex("glow_startAnim"),
      hidden = function()
        return not (anyGlowExternal("glow_action", "show") and anyGlowExternal("glow_type", "Proc"))
      end
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_duration"] = {
      type = "range",
      control = "WeakAurasSpinBox",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_duration", L["Duration"], L["Duration"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_duration", propertyType),
      order = order,
      softMin = 0.01,
      softMax = 3,
      step = 0.05,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_duration or 1;
      end,
      set = setValueComplex("glow_duration"),
      hidden = function()
        return not (anyGlowExternal("glow_action", "show") and anyGlowExternal("glow_type", "Proc"))
      end,
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_lines"] = {
      type = "range",
      control = "WeakAurasSpinBox",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_lines", L["Lines & Particles"], L["Lines & Particles"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_lines", propertyType),
      order = order,
      min = 1,
      softMax = 30,
      step = 1,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_lines or 8;
      end,
      set = setValueComplex("glow_lines"),
      hidden = function()
        return not (anyGlowExternal("glow_action", "show") and anyGlowExternal("glow_type", glowTypesExcepButtonOverlayAndProc))
      end,
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_frequency"] = {
      type = "range",
      control = "WeakAurasSpinBox",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_frequency", L["Frequency"], L["Frequency"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_frequency", propertyType),
      order = order,
      softMin = -2,
      softMax = 2,
      step = 0.05,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_frequency or 0.25;
      end,
      set = setValueComplex("glow_frequency"),
      hidden = function()
        return not (anyGlowExternal("glow_action", "show") and anyGlowExternal("glow_type", glowTypesExcepButtonOverlayAndProc))
      end,
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_length"] = {
      type = "range",
      control = "WeakAurasSpinBox",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_length", L["Length"], L["Length"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_length", propertyType),
      order = order,
      min = 0.05,
      softMax = 20,
      step = 0.05,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_length or 10;
      end,
      set = setValueComplex("glow_length"),
      hidden = function()
        return not (anyGlowExternal("glow_action", "show") and anyGlowExternal("glow_type", "Pixel"))
      end,
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_thickness"] = {
      type = "range",
      control = "WeakAurasSpinBox",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_thickness", L["Thickness"], L["Thickness"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_thickness", propertyType),
      order = order,
      min = 0.05,
      softMax = 20,
      step = 0.05,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_thickness or 1;
      end,
      set = setValueComplex("glow_thickness"),
      hidden = function()
        return not (anyGlowExternal("glow_action", "show") and anyGlowExternal("glow_type", "Pixel"))
      end,
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_XOffset"] = {
      type = "range",
      control = "WeakAurasSpinBox",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_XOffset", L["X-Offset"], L["X-Offset"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_XOffset", propertyType),
      order = order,
      softMin = -100,
      softMax = 100,
      step = 0.5,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_XOffset or 0;
      end,
      set = setValueComplex("glow_XOffset"),
      hidden = function()
        return not (anyGlowExternal("glow_action", "show") and anyGlowExternal("glow_type", glowTypesExcepButtonOverlay))
      end,
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_YOffset"] = {
      type = "range",
      control = "WeakAurasSpinBox",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_YOffset", L["Y-Offset"], L["Y-Offset"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_YOffset", propertyType),
      order = order,
      softMin = -100,
      softMax = 100,
      step = 0.5,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_YOffset or 0;
      end,
      set = setValueComplex("glow_YOffset"),
      hidden = function()
        return not (anyGlowExternal("glow_action", "show") and anyGlowExternal("glow_type", glowTypesExcepButtonOverlay))
      end,
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_scale"] = {
      type = "range",
      control = "WeakAurasSpinBox",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_scale", L["Scale"], L["Scale"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_scale", propertyType),
      order = order,
      min = 0.05,
      softMax = 10,
      step = 0.05,
      isPercent = true,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_scale or 1;
      end,
      set = setValueComplex("glow_scale"),
      hidden = function()
        return not (anyGlowExternal("glow_action", "show") and anyGlowExternal("glow_type", "ACShine"))
      end,
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_border"] = {
      type = "toggle",
      width = WeakAuras.normalWidth,
      name = blueIfNoValue2(data, conditions[i].changes[j], "value", "glow_border", L["Border"], L["Border"]),
      desc = descIfNoValue2(data, conditions[i].changes[j], "value", "glow_border", propertyType),
      order = order,
      get = function()
        return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.glow_border;
      end,
      set = setValueComplex("glow_border"),
      hidden = function() return
        not (anyGlowExternal("glow_action", "show") and anyGlowExternal("glow_type", "Pixel"))
      end,
    }
    order = order + 1
    args["condition" .. i .. "value" .. j .. "glow_spacer"] = {
      type = "description",
      width = WeakAuras.doubleWidth,
      name = "",
      order = order,
      hidden = false,
    }
    order = order + 1
  else -- Unknown property type
    order = addSpace(args, order);
  end
  return order;
end


local function checkSameValue(samevalue, propertyType)
  if (propertyType == "chat") then
    return samevalue.message_type and samevalue.message;
  elseif (propertyType == "sound") then
    return samevalue.sound and samevalue.sound_type;
  elseif (propertyType == "customcode") then
    return samevalue.custom;
  else
    return samevalue;
  end
end

local function getOrCreateSubCheck(base, path)
  for _, i in ipairs(path) do
    base.checks = base.checks or {};
    base.checks[i] = base.checks[i] or {};
    base = base.checks[i];
  end
  return base;
end


local function getSubCheck(base, path)
  for _, i in ipairs(path) do
    if (not base.checks or not base.checks[i]) then
      return nil;
    end
    base = base.checks[i];
  end
  return base;
end

local function removeSubCheck(base, path)
  -- Ensures that the parents exists
  getOrCreateSubCheck(base, path);

  local choppedPath = CopyTable(path);
  tremove(choppedPath, #path);

  local parent = getSubCheck(base, choppedPath);
  tremove(parent.checks, path[#path]);
end

local function addControlsForIfLine(args, order, data, conditionVariable, totalAuraCount, conditions, i, path, conditionTemplates, conditionTemplateWithoutCombinations, allProperties, parentType)
  local check = getSubCheck(conditions[i].check, path);

  local indentDepth = min(#path, 3); -- Be reasonable
  local indentWidth = (indentDepth > 0 and 0.02 or 0) + indentDepth * 0.03;
  local normalWidth = WeakAuras.normalWidth - indentWidth;

  local conditionTemplatesToUse = indentDepth < 3 and conditionTemplates or conditionTemplateWithoutCombinations;

  local optionsName = blueIfSubset(data, conditions[i].check, totalAuraCount);
  local needsTriggerName = check and check.trigger and check.trigger ~= -1 and check.trigger ~= -2;
  if (parentType) then
    local isFirst = path[#path] == 1;
    if (isFirst) then
      if (needsTriggerName) then
        optionsName = optionsName .. OptionsPrivate.GetTriggerTitle(data, check.trigger)
      end
    else
      if (needsTriggerName) then
        if (parentType == "AND") then
          optionsName = optionsName .. string.format(L["and %s"], OptionsPrivate.GetTriggerTitle(data, check.trigger));
        else
          optionsName = optionsName .. string.format(L["or %s"], OptionsPrivate.GetTriggerTitle(data, check.trigger));
        end
      end
    end
  else
    local isLinked = conditions[i].linked and i > 1
    if (needsTriggerName) then
      if isLinked then
        optionsName = optionsName .. string.format(L["Else If %s"], OptionsPrivate.GetTriggerTitle(data, check.trigger));
      else
        optionsName = optionsName .. string.format(L["If %s"], OptionsPrivate.GetTriggerTitle(data, check.trigger));
      end
    else
        optionsName = optionsName .. (isLinked and L["Else If"] or L["If"])
    end
  end

  if (indentWidth > 0) then
    -- Our container frame is not exactly at width = 2, due to some legacy
    -- Typically that works fine because the next widget doesn't fit into
    -- previous line. But the bullets are so small that we need to ensure
    -- that the previous line is full
    args["space" .. order] = {
      type = "description",
      name = "",
      image = function() return "", 0, 0 end,
      order = order,
      width = WeakAuras.doubleWidth * 1.5,
    }
    order = order + 1;

    if (indentWidth > 0.05) then
      args["condition" .. i .. tostring(path) .. "indent"] = {
        type = "description",
        width = indentWidth - 0.05,
        name = "",
        order = order
      }
      order = order + 1;
    end

    args["condition" .. i .. tostring(path) .. "bullet"] = {
      type = "description",
      width = 0.05,
      name = "",
      order = order,
      image = "Interface\\Addons\\WeakAuras\\Media\\Textures\\bullet" .. indentDepth,
      imageWidth = 10,
      imageHeight = 10,
    }
    order = order + 1;
  end

  local valuesForIf;
  if (indentDepth > 0) then
    valuesForIf = conditionTemplatesToUse.displayWithRemove;
  else
    valuesForIf = isSubset(data, conditions[i].check, totalAuraCount) and conditionTemplatesToUse.displayWithCopy or conditionTemplatesToUse.display;
  end

  args["condition" .. i .. tostring(path) .. "if"] = {
    type = "select",
    name = optionsName,
    desc = descIfSubset(data, conditions[i].check, totalAuraCount),
    order = order,
    values = valuesForIf,
    width = normalWidth;
    set = function(info, v)
      if (conditionTemplatesToUse.indexToTrigger[v] == "COPY") then
        for child in OptionsPrivate.Private.TraverseLeafs(data) do
          if (conditions[i].check.references[child.id]) then
          -- Already exists
          else
            -- find a good insertion point, if any other condition has a reference to this
            -- insert directly after that
            local insertPoint = 1;
            for index = i, 1, -1 do
              if (conditions[index].check.references[child.id]) then
                insertPoint = index + 1;
                break;
              end
            end

            local condition = {};
            condition.check = {};
            condition.check.trigger = conditions[i].check.trigger;
            condition.check.variable = conditions[i].check.variable;
            condition.check.op = conditions[i].check.op;
            condition.check.value = conditions[i].check.value;
            if (conditions[i].check.checks) then
              condition.check.checks = CopyTable(conditions[i].check.checks);
            end

            condition.changes = {};
            for changeIndex, change in ipairs(conditions[i].changes) do
              local propertyType = change.property and allProperties.propertyMap[change.property] and allProperties.propertyMap[change.property].type
              if (checkSameValue(change.samevalue, propertyType)) then
                local copy = {};
                copy.property = change.property;
                if (type(change.value) == "table") then
                  copy.value = CopyTable(change.value);
                else
                  copy.value = change.value;
                end
                tinsert(condition.changes, copy);
              end
            end

            tinsert(child[conditionVariable], insertPoint, condition);
            WeakAuras.Add(child);
            OptionsPrivate.ClearOptions(child.id)
          end
        end
        WeakAuras.ClearAndUpdateOptions(data.id)
        return;
      end

      if (conditionTemplatesToUse.indexToTrigger[v] == "REMOVE") then
        if (data.controlledChildren) then
          for id, reference in pairs(conditions[i].check.references) do
            local auraData = WeakAuras.GetData(id);
            removeSubCheck(auraData[conditionVariable][reference.conditionIndex].check, path);
            WeakAuras.Add(auraData)
            WeakAuras.ClearAndUpdateOptions(auraData.id)
          end
        else
          removeSubCheck(conditions[i].check, path);
          WeakAuras.Add(data)
          WeakAuras.ClearAndUpdateOptions(data.id)
        end
        return;
      end

      local trigger = conditionTemplatesToUse.indexToTrigger[v];
      local variable = conditionTemplatesToUse.indexToVariable[v];
      if (not trigger or not variable) then
        return;
      end

      if (data.controlledChildren) then
        for id, reference in pairs(conditions[i].check.references) do
          local auraData = WeakAuras.GetData(id);
          local childCheck = getOrCreateSubCheck(auraData[conditionVariable][reference.conditionIndex].check, path);
          childCheck.variable = variable;
          childCheck.trigger = trigger;
          childCheck.value = nil;
          WeakAuras.Add(auraData);
          OptionsPrivate.ClearOptions(auraData.id)
        end
        WeakAuras.ClearAndUpdateOptions(data.id)
      else
        local oldType;
        check = getOrCreateSubCheck(conditions[i].check, path);
        if (check.trigger and check.variable) then
          local templatesForTrigger = conditionTemplatesToUse.all[check.trigger];
          local templatesForTriggerAndCondition = templatesForTrigger and templatesForTrigger[check.variable];
          oldType = templatesForTriggerAndCondition and templatesForTriggerAndCondition.type;
        end
        check.variable = variable;
        check.trigger = trigger;
        local newType = conditionTemplatesToUse.all[trigger][variable].type;
        if (newType ~= oldType) then
          check.value = nil;
        end
        WeakAuras.Add(data);
        WeakAuras.ClearAndUpdateOptions(data.id)
      end
    end,
    get = function()
      local trigger = check and check.trigger;
      local variable = check and check.variable;
      if ( trigger and variable ) then
        return conditionTemplatesToUse.conditionToIndex[trigger .. "-" .. variable];
      end
      return "";
    end
  };

  order = order + 1;

  if (check and (check.variable == "AND" or check.variable == "OR")) then
    order = addSpace(args, order);

    local subCheckCount = check.checks and #check.checks or 0;
    -- We always want one more control than there are existing checks
    subCheckCount = subCheckCount + 1;

    for subCheck = 1, subCheckCount do
      local subPath = CopyTable(path);
      tinsert(subPath, subCheck);
      order = addControlsForIfLine(args, order, data, conditionVariable, totalAuraCount, conditions, i, subPath, conditionTemplates, conditionTemplateWithoutCombinations, allProperties, check.variable);
    end
  end

  local currentConditionTemplate = nil;
  local trigger = check and check.trigger;
  local variable = check and check.variable;
  if (trigger and variable) then
    if (conditionTemplatesToUse.all[trigger]) then
      currentConditionTemplate = conditionTemplatesToUse.all[trigger][variable];
    end
  end

  if (currentConditionTemplate and currentConditionTemplate.type and type(currentConditionTemplate.type) == "string") then
    local function makeSetter(field)
      if (data.controlledChildren) then
        return function(info, v)
          check = getOrCreateSubCheck(conditions[i].check, path);
          for id, reference in pairs(conditions[i].check.references) do
            local auraData = WeakAuras.GetData(id);
            local childCheck = getOrCreateSubCheck(auraData[conditionVariable][reference.conditionIndex].check, path);
            childCheck[field] = v;
            WeakAuras.Add(auraData);
            OptionsPrivate.ClearOptions(auraData.id)
          end
          check[field] = v;
          WeakAuras.ClearAndUpdateOptions(data.id)
        end
      else
        return function(info, v)
          check = getOrCreateSubCheck(conditions[i].check, path);
          check[field] = v;
          WeakAuras.Add(data);
          WeakAuras.ClearAndUpdateOptions(data.id)
        end
      end
    end

    local setOp = makeSetter("op")
    local setValue = makeSetter("value")

    if (currentConditionTemplate.type == "number" or currentConditionTemplate.type == "timer" or currentConditionTemplate.type == "elapsedTimer") then
      local opTypes = OptionsPrivate.Private.operator_types
      if currentConditionTemplate.operator_types == "without_equal" then
        opTypes = OptionsPrivate.Private.operator_types_without_equal
      elseif currentConditionTemplate.operator_types == "only_equal" then
        opTypes = OptionsPrivate.Private.equality_operator_types
      end

      args["condition" .. i .. tostring(path) .. "_op"] = {
        name = blueIfNoValue(data, conditions[i].check, "op", L["Differences"]),
        desc = descIfNoValue(data, conditions[i].check, "op", currentConditionTemplate.type),
        type = "select",
        order = order,
        values = opTypes,
        width = WeakAuras.halfWidth,
        get = function()
          return check.op;
        end,
        set = setOp,
      }
      order = order + 1;

      args["condition" .. i .. tostring(path) .. "_value"] = {
        type = "input",
        name = blueIfNoValue(data, conditions[i].check, "value", L["Differences"]),
        desc = descIfNoValue(data, conditions[i].check, "value", currentConditionTemplate.type),
        width = WeakAuras.halfWidth,
        order = order,
        validate = WeakAuras.ValidateNumeric,
        get = function()
          return check.value;
        end,
        set = setValue
      }
      order = order + 1;
    elseif (currentConditionTemplate.type == "select") or (currentConditionTemplate.type == "unit") then
      if (type(currentConditionTemplate.values) == "table") then
        args["condition" .. i .. tostring(path) .. "_op"] = {
          name = blueIfNoValue(data, conditions[i].check, "op", L["Differences"]),
          desc = descIfNoValue(data, conditions[i].check, "op", currentConditionTemplate.type),
          type = "select",
          width = WeakAuras.normalWidth,
          order = order,
          values = OptionsPrivate.Private.equality_operator_types,
          get = function()
            return check.op;
          end,
          set = setOp,
        }
        order = order + 1;

        order = addSpace(args, order);

        if (currentConditionTemplate.type == "unit") then
          args["condition" .. i .. tostring(path) .. "_value"] = {
            type = "select",
            width = WeakAuras.normalWidth,
            name = blueIfNoValue(data, conditions[i].check, "value", L["Differences"]),
            desc = descIfNoValue(data, conditions[i].check, "value", currentConditionTemplate.type),
            order = order,
            values = currentConditionTemplate.values,
            get = function()
              return currentConditionTemplate.values[check.value] and check.value or (check.value and "member")
            end,
            set = setValue
          }
          order = order + 1;

          args["condition" .. i .. tostring(path) .. "_member"] = {
            type = "input",
            width = WeakAuras.doubleWidth,
            name = blueIfNoValue(data, conditions[i].check, "value", L["Differences"]),
            desc = descIfNoValue(data, conditions[i].check, "value", currentConditionTemplate.type),
            order = order,
            get = function()
              return check and check.value
            end,
            set = setValue,
            hidden = function()
              return not conditions[i].check.value or currentConditionTemplate.values[conditions[i].check.value] and conditions[i].check.value ~= "member"
            end
          }
          order = order + 1;
        else
          args["condition" .. i .. tostring(path) .. "_value"] = {
            type = "select",
            width = WeakAuras.normalWidth,
            name = blueIfNoValue(data, conditions[i].check, "value", L["Differences"]),
            desc = descIfNoValue(data, conditions[i].check, "value", currentConditionTemplate.type),
            order = order,
            values = currentConditionTemplate.values,
            get = function()
              return check.value
            end,
            set = setValue
          }
          order = order + 1;
        end
      end
    elseif (currentConditionTemplate.type == "bool") then
      args["condition" .. i .. tostring(path) .. "_value"] = {
        type = "select",
        width = WeakAuras.normalWidth,
        name = blueIfNoValue(data, conditions[i].check, "value", L["Differences"]),
        desc = descIfNoValue(data, conditions[i].check, "value", currentConditionTemplate.type),
        order = order,
        values = OptionsPrivate.Private.bool_types,
        get = function()
          return check and check.value;
        end,
        set = setValue
      }
      order = order + 1;
    elseif (currentConditionTemplate.type == "string") then
      if currentConditionTemplate.operator_types ~= "none" then
        args["condition" .. i .. tostring(path) .. "_op"] = {
          name = blueIfNoValue(data, conditions[i].check, "op", L["Differences"]),
          desc = descIfNoValue(data, conditions[i].check, "op", currentConditionTemplate.type),
          type = "select",
          width = WeakAuras.normalWidth,
          order = order,
          values = OptionsPrivate.Private.string_operator_types,
          get = function()
            return check and check.op;
          end,
          set = setOp
        }
        order = order + 1;
        order = addSpace(args, order);
      end

      args["condition" .. i .. tostring(path) .. "_value"] = {
        type = "input",
        width = WeakAuras.normalWidth,
        name = blueIfNoValue(data, conditions[i].check, "value", L["Differences"]),
        desc = descIfNoValue(data, conditions[i].check, "value", currentConditionTemplate.type),
        order = order,
        get = function()
          return check and check.value;
        end,
        set = setValue
      }
      order = order + 1;
    elseif currentConditionTemplate.type == "alwaystrue" then
      order = addSpace(args, order)
    elseif (currentConditionTemplate.type == "range") then
      args["condition" .. i .. tostring(path) .. "_op_range"] = {
        name = blueIfNoValue(data, conditions[i].check, "op_range", L["Differences"]),
        desc = descIfNoValue(data, conditions[i].check, "op_range", currentConditionTemplate.type),
        type = "select",
        order = order,
        values = OptionsPrivate.Private.operator_types_without_equal,
        width = WeakAuras.halfWidth,
        get = function()
          return check.op_range;
        end,
        set = makeSetter("op_range"),
      }
      order = order + 1;

      args["condition" .. i .. tostring(path) .. "_range"] = {
        type = "input",
        name = L["Range in yards"],
        desc = descIfNoValue(data, conditions[i].check, "range", currentConditionTemplate.type),
        width = WeakAuras.halfWidth,
        order = order,
        validate = WeakAuras.ValidateNumeric,
        get = function()
          return check.range;
        end,
        set = makeSetter("range")
      }
      order = order + 1;

      if (indentWidth > 0) then
        args["condition" .. i .. tostring(path) .. "_space"] = {
          type = "description",
          name = "",
          order = order,
          width = WeakAuras.doubleWidth * 1.5,
        }
        order = order + 1;
        args["condition" .. i .. tostring(path) .. "_indent"] = {
          type = "description",
          width = indentWidth,
          name = "",
          order = order
        }
        order = order + 1;
      end

      args["condition" .. i .. tostring(path) .. "_type"] = {
        type = "select",
        width = normalWidth,
        name = blueIfNoValue(data, conditions[i].check, "type", L["Differences"]),
        desc = descIfNoValue(data, conditions[i].check, "type", currentConditionTemplate.type),
        order = order,
        values = {
          group = L["Group player(s) found"],
          enemies = L["Enemy nameplate(s) found"]
        },
        get = function()
          return check.type
        end,
        set = makeSetter("type"),
      }
      order = order + 1;

      args["condition" .. i .. tostring(path) .. "_op"] = {
        name = blueIfNoValue(data, conditions[i].check, "op", L["Differences"]),
        desc = descIfNoValue(data, conditions[i].check, "op", currentConditionTemplate.type),
        type = "select",
        order = order,
        values = OptionsPrivate.Private.operator_types,
        width = WeakAuras.halfWidth,
        get = function()
          return check.op;
        end,
        set = setOp,
      }
      order = order + 1;

      args["condition" .. i .. tostring(path) .. "_value"] = {
        type = "input",
        name = blueIfNoValue(data, conditions[i].check, "value", L["Differences"]),
        desc = descIfNoValue(data, conditions[i].check, "value", currentConditionTemplate.type),
        width = WeakAuras.halfWidth,
        order = order,
        validate = WeakAuras.ValidateNumeric,
        get = function()
          return check.value;
        end,
        set = setValue
      }
      order = order + 1;
    elseif currentConditionTemplate.type == "customcheck" then
      args["condition" .. i .. tostring(path) .. "_op"] = {
        name = blueIfNoValue(data, conditions[i].check, "op", L["Additional Events"], L["Additional Events"]),
        desc = descIfNoValue(data, conditions[i].check, "op", currentConditionTemplate.type) or "",
        type = "input",
        width = WeakAuras.doubleWidth,
        order = order,
        get = function()
          return check and check.op;
        end,
        set = setOp
      }
      order = order + 1;

      args["condition" .. i .. tostring(path) .. "_value"] = {
        type = "input",
        width = WeakAuras.doubleWidth,
        name = blueIfNoValue(data, conditions[i].check, "value", L["Custom Check"], L["Custom Check"]),
        desc = descIfNoValue(data, conditions[i].check, "value", currentConditionTemplate.type) or "",
        order = order,
        get = function()
          return check and check.value;
        end,
        set = setValue,
        multiline = true,
        control = "WeakAurasMultiLineEditBox",
        arg = {
          extraFunctions = {
            {
              buttonLabel = L["Expand"],
              func = function()
                if (data.controlledChildren) then
                  -- Collect multi paths
                  local multipath = {};
                  for id in pairs(conditions[i].check.references) do
                    local conditionIndex = conditions[i].check.references[id].conditionIndex;
                    multipath[id] ={ "conditions", conditionIndex, "check" }
                    for _, v in ipairs(path) do
                      tinsert(multipath[id], "checks")
                      tinsert(multipath[id], v)
                    end
                    tinsert(multipath[id], "value")
                  end
                  OptionsPrivate.OpenTextEditor(data, multipath, nil, true, nil, nil, "https://github.com/WeakAuras/WeakAuras2/wiki/Custom-Code-Blocks#custom-check");
                else
                  local fullPath = { "conditions", i, "check" }
                  for _, v in ipairs(path) do
                    tinsert(fullPath, "checks")
                    tinsert(fullPath, v)
                  end
                  tinsert(fullPath, "value")

                  OptionsPrivate.OpenTextEditor(data, fullPath, nil, nil, nil, nil, "https://github.com/WeakAuras/WeakAuras2/wiki/Custom-Code-Blocks#custom-check");
                end
              end
            }
          }
        }
      }
      order = order + 1

      args["condition" .. i .. tostring(path) .. "_value_error"] = {
        type = "description",
        name = function()
          if (not check.value) then
            return ""
          end
          local _, errorString = loadstring("return " .. check.value);
          return errorString and "|cFFFF0000"..errorString or "";
        end,
        width = WeakAuras.doubleWidth,
        order = order,
        hidden = function()
          if (not check.value) then
            return true;
          end

          local loadedFunction, errorString = loadstring("return " .. check.value);
          if(errorString and not loadedFunction) then
            return false;
          else
            return true;
          end
        end
      }
      order = order + 1
    elseif (currentConditionTemplate.type == "combination") then
      -- Do nothing
    else
      order = addSpace(args, order);
    end
  else
    order = addSpace(args, order);
  end
  return order;
end

local function fixUpLinkedInFirstCondition(conditions)
  if conditions[1] and conditions[1].linked then
    conditions[1].linked = false
  end
end

local function formatConditionTitle(text, propertyType, value)
  if propertyType == "color" and type(value) == "table" then
    local r, g, b = unpack(value)
    r, g, b = r or 1, g or 1, b or 1
    return ("|cFF%2x%2x%2x%s|r"):format(r * 220 + 35, g * 220 + 35, b * 220 + 35, text)
  elseif propertyType == "bool" then
    return ("%s: %s"):format(text, value and L["ON"] or L["OFF"])
  elseif propertyType == "sound" and type(value) == "table" and type(value.sound) == "string" and value.sound ~= "" then
    if OptionsPrivate.Private.sound_types[value.sound] then
      return ("%s: %s"):format(text, OptionsPrivate.Private.sound_types[value.sound])
    end
  elseif value ~= nil and type(value) ~= "table" then
    return ("%s: %s"):format(text, value)
  end
  return text
end

local function GetConditionTitle(changes, conditionnum, allProperties)
  if type(changes) == "table" and #changes > 0 then
    local outs = {}
    for i, change in ipairs(changes) do
      local property = change.property
      if property and allProperties.propertyMap[property] then
        local display = allProperties.propertyMap[property].display
        local propertyType = allProperties.propertyMap[property].type
        local title
        if type(display) == "string" then
          title = display
        elseif type(display) == "table" then
          title = ("%s %s"):format(display[1], display[2])
        end
        if title then
          tinsert(outs, formatConditionTitle(title, propertyType, change.value))
        end
      end
    end
    if #outs > 0 then
      return L["%i. %s"]:format(conditionnum, table.concat(outs, ", "))
    end
  end
  return L["%i."]:format(conditionnum)
end

local function addControlsForCondition(args, order, data, conditionVariable, totalAuraCount, conditions, i, conditionTemplates, conditionTemplateWithoutCombinations, allProperties)
  if (not conditions[i].check) then
    return order;
  end

  local defaultCollapsed = #conditions > 2
  local collapsed = false;
  if data.controlledChildren then
    for id, reference in pairs(conditions[i].check.references) do
      local index = reference.conditionIndex;
      if OptionsPrivate.IsCollapsed(id, "condition", index, defaultCollapsed) then
        collapsed = true;
        break;
      end
    end
  else
    collapsed = OptionsPrivate.IsCollapsed(data.id, "condition", i, defaultCollapsed);
  end

  args["condition" .. i .. "header"] = {
    type = "execute",
    name = GetConditionTitle(conditions[i].changes, i, allProperties),
    order = order,
    width = WeakAuras.doubleWidth - 0.6,
    func = function()
      if data.controlledChildren then
        for id, reference in pairs(conditions[i].check.references) do
          local index = reference.conditionIndex
          OptionsPrivate.SetCollapsed(id, "condition", index, not collapsed);
          OptionsPrivate.ClearOptions(id)
        end
      else
        OptionsPrivate.SetCollapsed(data.id, "condition", i, not collapsed);
      end
      WeakAuras.ClearAndUpdateOptions(data.id)
    end,
    image = collapsed and "Interface\\AddOns\\WeakAuras\\Media\\Textures\\expand" or "Interface\\AddOns\\WeakAuras\\Media\\Textures\\collapse" ,
    imageWidth = 18,
    imageHeight = 18,
    control = "WeakAurasExpand",
    fontObject = GameFontHighlight
  };
  order = order + 1;

  args["condition" .. i .. "up"] = {
    type = "execute",
    name = L["Move Up"],
    order = order,
    disabled = function()
      if (data.controlledChildren) then
        for _, reference in pairs(conditions[i].check.references) do
          local index = reference.conditionIndex;
          if (index > 1) then
            return false;
          end
        end
        return true;
      else
        return i == 1;
      end
    end,
    func = function()
      if (data.controlledChildren) then
        for id, reference in pairs(conditions[i].check.references) do
          local auraData = WeakAuras.GetData(id);
          local index = reference.conditionIndex;
          if (index > 1) then
            local tmp = auraData[conditionVariable][reference.conditionIndex];
            tremove(auraData[conditionVariable], reference.conditionIndex);
            tinsert(auraData[conditionVariable], reference.conditionIndex - 1, tmp);
            fixUpLinkedInFirstCondition(auraData[conditionVariable])
            WeakAuras.Add(auraData);
            OptionsPrivate.MoveCollapseDataUp(auraData.id, "condition", {reference.conditionIndex})
            OptionsPrivate.ClearOptions(auraData.id)
          end
        end
        WeakAuras.ClearAndUpdateOptions(data.id)
      else
        if (i > 1) then
          local tmp = conditions[i];
          tremove(conditions, i);
          tinsert(conditions, i - 1, tmp);
          fixUpLinkedInFirstCondition(conditions)
          WeakAuras.Add(data);
          OptionsPrivate.MoveCollapseDataUp(data.id, "condition", {i})
          WeakAuras.ClearAndUpdateOptions(data.id)
        end
      end
    end,
    width = 0.15,
    image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\moveup",
    imageWidth = 24,
    imageHeight = 24,
    control = "WeakAurasIcon"
  };
  order = order + 1;

  args["condition" .. i .. "down"] = {
    type = "execute",
    name = L["Move Down"],
    order = order,
    disabled = function()
      if (data.controlledChildren) then
        for id, reference in pairs(conditions[i].check.references) do
          local index = reference.conditionIndex;
          local auraData = WeakAuras.GetData(id);
          if (index < #auraData[conditionVariable]) then
            return false;
          end
        end
        return true;
      else
        return i == #conditions;
      end
    end,
    func = function()
      if (data.controlledChildren) then
        for id, reference in pairs(conditions[i].check.references) do
          local auraData = WeakAuras.GetData(id);
          local index = reference.conditionIndex;
          if (index < #auraData[conditionVariable]) then
            local tmp = auraData[conditionVariable][reference.conditionIndex];
            tremove(auraData[conditionVariable], reference.conditionIndex);
            tinsert(auraData[conditionVariable], reference.conditionIndex + 1, tmp);
            fixUpLinkedInFirstCondition(auraData[conditionVariable])
            WeakAuras.Add(auraData);
            OptionsPrivate.MoveCollapseDataDown(auraData.id, "condition", {reference.conditionIndex})
            OptionsPrivate.ClearOptions(auraData.id)
          end
        end
        WeakAuras.ClearAndUpdateOptions(data.id)
        return;
      else
        if (i < #conditions) then
          local tmp = conditions[i];
          tremove(conditions, i);
          tinsert(conditions, i + 1, tmp);
          fixUpLinkedInFirstCondition(conditions)
          WeakAuras.Add(data);
          OptionsPrivate.MoveCollapseDataDown(data.id, "condition", {i})
          WeakAuras.ClearAndUpdateOptions(data.id)
          return;
        end
      end
    end,
    width = 0.15,
    image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\movedown",
    imageWidth = 24,
    imageHeight = 24,
    control = "WeakAurasIcon"
  };
  order = order + 1;

  args["condition" .. i .. "duplicate"] = {
    type = "execute",
    name = L["Duplicate"],
    order = order,
    func = function()
      if (data.controlledChildren) then
        for id, reference in pairs(conditions[i].check.references) do
          local auraData = WeakAuras.GetData(id);
          local clone = CopyTable(auraData[conditionVariable][reference.conditionIndex])
          tinsert(auraData[conditionVariable], reference.conditionIndex + 1, clone);
          WeakAuras.Add(auraData);
          OptionsPrivate.DuplicateCollapseData(auraData.id, "condition", {reference.conditionIndex})
          OptionsPrivate.ClearOptions(auraData.id)
        end
        WeakAuras.ClearAndUpdateOptions(data.id)
        return;
      else
        local clone = CopyTable(conditions[i])
        tinsert(conditions, i + 1, clone);
        WeakAuras.Add(data);
        OptionsPrivate.DuplicateCollapseData(data.id, "condition", {i})
        WeakAuras.ClearAndUpdateOptions(data.id)
        return;
      end
    end,
    width = 0.15,
    image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\duplicate",
    imageWidth = 24,
    imageHeight = 24,
    control = "WeakAurasIcon"
  };
  order = order + 1;

  args["condition" .. i .. "delete"] = {
    type = "execute",
    name = L["Delete"],
    order = order,
    func = function()
      if (data.controlledChildren) then
        for id, reference in pairs(conditions[i].check.references) do
          local auraData = WeakAuras.GetData(id);
          tremove(auraData[conditionVariable], reference.conditionIndex);
          fixUpLinkedInFirstCondition(auraData[conditionVariable])
          WeakAuras.Add(auraData);
          OptionsPrivate.RemoveCollapsed(auraData.id, "condition", {reference.conditionIndex})
          OptionsPrivate.ClearOptions(auraData.id)
        end
        WeakAuras.ClearAndUpdateOptions(data.id)
        return;
      else
        tremove(conditions, i);
        fixUpLinkedInFirstCondition(conditions)
        WeakAuras.Add(data);
        OptionsPrivate.RemoveCollapsed(data.id, "condition", {i})
        WeakAuras.ClearAndUpdateOptions(data.id)
        return;
      end
    end,
    width = 0.15,
    image = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\delete",
    imageWidth = 24,
    imageHeight = 24,
    control = "WeakAurasIcon"
  };
  order = order + 1;

  if collapsed then
    return order;
  end

  order = addControlsForIfLine(args, order, data, conditionVariable, totalAuraCount, conditions, i, {}, conditionTemplates, conditionTemplateWithoutCombinations, allProperties);

  -- Add Property changes

  local usedProperties = {};
  for j = 1, conditions[i].changes and #conditions[i].changes or 0 do
    local property = conditions[i].changes[j].property;
    if (property) then
      usedProperties[property] = true;
    end
  end

  for j = 1, conditions[i].changes and #conditions[i].changes or 0 do
    order = addControlsForChange(args, order, data, conditionVariable, totalAuraCount, conditions, i, j, allProperties, usedProperties);
  end

  args["condition" .. i .. "_addChange"] = {
    type = "execute",
    width = WeakAuras.normalWidth,
    name = L["Add Property Change"],
    order = order,
    func = function()
      if (data.controlledChildren) then
        for id, reference in pairs(conditions[i].check.references) do
          local auradata = WeakAuras.GetData(id);
          auradata[conditionVariable][reference.conditionIndex].changes = auradata[conditionVariable][reference.conditionIndex].changes or {}
          tinsert(auradata[conditionVariable][reference.conditionIndex].changes, {})
          WeakAuras.Add(auradata);
          OptionsPrivate.ClearOptions(auradata.id)
        end
        WeakAuras.ClearAndUpdateOptions(data.id)
      else
        conditions[i].changes = conditions[i].changes or {};
        conditions[i].changes[#conditions[i].changes + 1] = {};
        WeakAuras.Add(data);
        WeakAuras.ClearAndUpdateOptions(data.id)
      end
    end
  }
  order = order + 1;

  local showElseIf = false
  local isLinked = false

  if (data.controlledChildren) then
    for id, reference in pairs(conditions[i].check.references) do
      if reference.conditionIndex > 1 then
        local auradata = WeakAuras.GetData(id);
        isLinked = auradata[conditionVariable][reference.conditionIndex].linked
        showElseIf = true
        break;
      end
    end
  else
    if i > 1 then
      showElseIf = true
      isLinked = conditions[i].linked
    end
  end

  if showElseIf then
    args["condition" .. i .. "_else"] = {
      type = "toggle",
      width = WeakAuras.normalWidth,
      name = L["Else If"],
      order = order,
      get = function()
        return isLinked
      end,
      set = function()
        if (data.controlledChildren) then
          for id, reference in pairs(conditions[i].check.references) do
            local auradata = WeakAuras.GetData(id);
            if reference.conditionIndex > 1 then
              auradata[conditionVariable][reference.conditionIndex].linked = not isLinked
              WeakAuras.Add(auradata);
              OptionsPrivate.ClearOptions(auradata.id)
            end
          end
          WeakAuras.ClearAndUpdateOptions(data.id)
        else
          conditions[i].linked = not isLinked
          WeakAuras.Add(data);
          WeakAuras.ClearAndUpdateOptions(data.id)
        end
      end
    }
    order = order + 1;
  else
    order = addSpace(args, order)
  end

  return order;
end

local function mergeConditionTemplates(allConditionTemplates, auraConditionsTemplate, numTriggers)
  for triggernum = 1, numTriggers do
    local auraTemplatesForTrigger = auraConditionsTemplate[triggernum];
    if (auraTemplatesForTrigger) then
      allConditionTemplates[triggernum] = allConditionTemplates[triggernum] or {};
      for conditionName in pairs(auraTemplatesForTrigger) do
        if not allConditionTemplates[triggernum][conditionName] then
          allConditionTemplates[triggernum][conditionName] = CopyTable(auraTemplatesForTrigger[conditionName]);
        else
          if (allConditionTemplates[triggernum][conditionName].type ~= auraTemplatesForTrigger[conditionName].type) then
            -- Two different trigger types have a condition of the same name, with incompatible types
            -- Setting the type to incompatible prevents the interface from showing options for it
            -- This can't currently happen
            allConditionTemplates[triggernum][conditionName].type = "incompatible";
          end
        end
      end
    end
  end
end

local function createConditionTemplatesValueList(allConditionTemplates, numTriggers, excludeCombinations, data)
  local conditionTemplates = {};
  conditionTemplates.all = allConditionTemplates;
  conditionTemplates.indexToTrigger = {};
  conditionTemplates.indexToVariable = {};
  conditionTemplates.conditionToIndex = {};
  conditionTemplates.display = {};

  local index = 1;
  local startTriggernum = excludeCombinations and -1 or -2;
  for triggernum = startTriggernum, numTriggers do
    local templatesForTrigger = allConditionTemplates[triggernum];
    if triggernum ~= 0 then
      -- Sort Conditions for one trigger
      local sorted = {};
      if (templatesForTrigger) then
        for conditionName in pairs(templatesForTrigger) do
          tinsert(sorted, conditionName);
        end
        table.sort(sorted, function(a, b)
          return templatesForTrigger[a].display < templatesForTrigger[b].display;
        end);

        if (#sorted > 0) then
          if (triggernum == -2) then
            -- Do Nothing
            conditionTemplates.display[index]  = string.format(L["Combinations"]);
          elseif (triggernum == -1) then
            conditionTemplates.display[index]  = string.format(L["Global Conditions"]);
          else
            conditionTemplates.display[index]  = OptionsPrivate.GetTriggerTitle(data, triggernum)
          end
          index = index + 1;

          for _, conditionName in ipairs(sorted) do
            conditionTemplates.display[index] = "    " .. templatesForTrigger[conditionName].display;
            conditionTemplates.indexToTrigger[index] = triggernum;
            conditionTemplates.indexToVariable[index] = conditionName;
            conditionTemplates.conditionToIndex[triggernum .. "-" .. conditionName] = index;
            index = index + 1;
          end
        end
      end
    end
  end

  conditionTemplates.displayWithRemove = CopyTable(conditionTemplates.display);
  conditionTemplates.displayWithRemove[9997] = "•" .. L["Remove"] .. "•";
  conditionTemplates.indexToTrigger[9997] = "REMOVE";
  conditionTemplates.indexToVariable[9997] = "REMOVE";

  return conditionTemplates;
end

local function createConditionTemplates(data)
  -- The allConditionTemplates contains a table per trigger.
  -- Each table contains a entry per condition variable
  -- For the DropDown we need a flat and sorted list that maps
  -- from a index to a display name
  -- And two auxillary data structures which map to the index from triggernum/conditionvalue
  -- And from the index to triggernum/conditionvalue

  local allConditionTemplates;
  local numTriggers = 0;
  if (data.controlledChildren) then
    allConditionTemplates = {};
    for child in OptionsPrivate.Private.TraverseLeafs(data) do
      numTriggers = max(numTriggers, #child.triggers);

      local auraConditionsTemplate = OptionsPrivate.Private.GetTriggerConditions(child);
      mergeConditionTemplates(allConditionTemplates, auraConditionsTemplate, numTriggers)
    end
  else
    allConditionTemplates = OptionsPrivate.Private.GetTriggerConditions(data);
    numTriggers = #data.triggers;
  end

  allConditionTemplates[-2] = {
    ["AND"] = {
      display = L["All of"],
      type = "combination"
    },
    ["OR"] = {
      display = L["Any of"],
      type = "combination"
    }
  }
  allConditionTemplates[-1] = OptionsPrivate.Private.GetGlobalConditions();

  local conditionTemplates = createConditionTemplatesValueList(allConditionTemplates, numTriggers, nil, data);

  if (data.controlledChildren) then
    conditionTemplates.displayWithCopy = CopyTable(conditionTemplates.display);

    conditionTemplates.displayWithCopy[9998] = "•" .. L["Copy to all auras"] .. "•";
    conditionTemplates.indexToTrigger[9998] = "COPY";
    conditionTemplates.indexToVariable[9998] = "COPY";
  end

  local conditionTemplateWithoutCombinations = createConditionTemplatesValueList(allConditionTemplates, numTriggers, true, data);

  return conditionTemplates, conditionTemplateWithoutCombinations;
end

local function buildAllPotentialProperties(data, category)
  local allProperties = {};
  allProperties.propertyMap = {};
  for child in OptionsPrivate.Private.TraverseLeafsOrAura(data) do
    local regionProperties = OptionsPrivate.Private.GetProperties(child);

    if (regionProperties) then
      for k, v in pairs(regionProperties) do
        if (v.category == category) then
          if (allProperties.propertyMap[k]) then
            if (allProperties.propertyMap[k].type ~= v.type) then
              allProperties.propertyMap[k].type = "incompatible";
            end

            if (allProperties.propertyMap[k].type == "list" or allProperties.propertyMap[k].type == "progressSource" ) then
              -- Merge value lists
              for key, value in pairs(v.values) do
                if (allProperties.propertyMap[k].values[key] == nil) then
                  allProperties.propertyMap[k].values[key] = value;
                end
              end
            end
          else
            allProperties.propertyMap[k] = CopyTable(v)
          end
        end
      end
    end
  end

  allProperties.indexToProperty = {};
  for k in pairs(allProperties.propertyMap) do
    tinsert(allProperties.indexToProperty, k);
  end
  table.sort(allProperties.indexToProperty, function(a, b)
    local av = allProperties.propertyMap[a].display
    av = type(av) == "table" and av[1] or av

    local bv = allProperties.propertyMap[b].display
    bv = type(bv) == "table" and bv[1] or bv
    return av < bv
  end);

  allProperties.propertyToIndex = {};
  for index, property in ipairs(allProperties.indexToProperty) do
    allProperties.propertyToIndex[property] = index;
  end

  allProperties.display = {};
  for index, property in ipairs(allProperties.indexToProperty) do
    allProperties.display[index] = allProperties.propertyMap[property].display;
  end

  allProperties.display[9999] = "•" .. L["Remove this property"] .. "•";
  allProperties.indexToProperty[9999] = "DELETE";

  if (data.controlledChildren) then
    allProperties.displayWithCopy = CopyTable(allProperties.display);

    allProperties.displayWithCopy[9998] = "•" .. L["Copy to all auras"] .. "•";
    allProperties.indexToProperty[9998] = "COPY";
  end

  return allProperties;
end

local function compareSubChecks(a, b, allConditionTemplates)
  if (a == nil and b == nil) then
    return true;
  end
  if (a == nil or b == nil) then
    return false;
  end

  if (#a ~= #b) then
    return false;
  end

  local count = #a;

  for i = 1, count do
    if (a[i].trigger ~= b[i].trigger or a[i].variable ~= b[i].variable) then
      return false;
    end

    if (a[i].trigger == -2) then
      if (not compareSubChecks(a[i].checks, b[i].checks, allConditionTemplates)) then
        return false;
      end
    else
      local currentConditionTemplate = allConditionTemplates[a[i].trigger] and allConditionTemplates[a[i].trigger][a[i].variable];
      if (not currentConditionTemplate) then
        return true;
      end

      local type = currentConditionTemplate.type;
      if (type == "number" or type == "timer" or type == "elapsedTimer" or type == "select" or type == "string" or type == "customcheck") then
        if (a[i].op ~= b[i].op or a[i].value ~= b[i].value) then
          return false;
        end
      elseif (type == "bool") then
        if (a[i].value ~= b[i].value) then
          return false;
        end
      elseif (type == "alwaystrue") then
        return true
      end
    end
  end
  return true;
end

local function findMatchingCondition(all, needle, start, allConditionTemplates)
  while (true) do
    local condition = all[start];
    if (not condition) then
      return nil;
    end

    if (condition.check.trigger == needle.check.trigger and condition.check.variable == needle.check.variable
        and condition.linked == needle.linked) then
      if condition.check.variable == "customcheck" then
        -- Be a bit more strict for custom checks, there's little benefit in merging them
        if condition.check.op == needle.check.op and condition.check.value == needle.check.value then
          return start
        end
      elseif (condition.check.trigger == -2) then
        if (compareSubChecks(condition.check.checks, needle.check.checks, allConditionTemplates)) then
          return start;
        end
      else
        return start;
      end
    end
    start = start + 1;
  end
end

local function findMatchingProperty(all, change, id)
  for index, allChange in ipairs(all) do
    if (allChange.property == change.property) then
      local alreadyReferenced = allChange.references and allChange.references[id];
      if (not alreadyReferenced) then
        return index;
      end
    end
  end
  return nil;
end

local function SubPropertiesForChange(change)
  if change.property == "sound" then
    return { "sound", "sound_channel", "sound_path", "sound_kit_id", "sound_repeat", "sound_type", "sound_fade"}
  elseif change.property == "customcode" then
    return { "custom" }
  elseif change.property == "glowexternal" then
    return {
      "glow_action", "glow_frame_type", "glow_type",
      "glow_frame", "choose_glow_frame",
      "use_glow_color", "glow_color",
      "glow_startAnim", "glow_duration", "glow_lines", "glow_frequency", "glow_length", "glow_thickness", "glow_XOffset", "glow_YOffset",
      "glow_scale", "glow_border"
    }
  elseif change.property == "chat" then
    local result = { "message_type", "message_dest", "message_channel", "message_color", "message", "custom", "message_voice" }
    local input = change.value and change.value.message
    if input then
      local getter = function(key)
        return change.value["message_format_" .. key]
      end
      OptionsPrivate.AddTextFormatOption(input, false, getter, function(key)
        tinsert(result, "message_format_" .. key)
      end, nil, nil, true)
    end
    return result
  end
end

local subPropertyToType = {
  glow_color = "color",
  message_color = "color"
}

local function mergeConditionChange(all, change, id, changeIndex, allProperties)
  local propertyType = all.property and allProperties.propertyMap[all.property] and allProperties.propertyMap[all.property].type
  if (propertyType == "chat" or propertyType == "sound" or propertyType == "customcode" or propertyType == "glowexternal") then
    if (type(all.value) ~= type(change.value)) then
      all.value = nil;
      all.samevalue = nil;
    else
      if (type(change.value) ~= "table") then
        if not compareValues(all.value, change.value, propertyType) then
          all.value = nil;
          all.samevalue = false;
        end
      else
        for _, propertyName in ipairs(SubPropertiesForChange(change)) do
          if all.samevalue[propertyName] == nil then
            -- NEW not yet seen property
            all.value[propertyName] = change.value[propertyName]
            all.samevalue[propertyName] = true
          elseif not compareValues(all.value[propertyName], change.value[propertyName], subPropertyToType[propertyName]) then
            all.value[propertyName] = nil;
            if all.samevalue then
              all.samevalue[propertyName] = false;
            end
          end
        end
      end
    end
  else
    if not compareValues(all.value, change.value, propertyType) then
      all.value = nil;
      all.samevalue = false;
    end
  end

  all.references = all.references or {};
  all.references[id] = {
    ["changeIndex"] = changeIndex,
    ["value"] = change.value
  };
  all.referenceCount = (all.referenceCount or 0) + 1;

end

local function mergeCondition(all, aura, id, conditionIndex, allProperties)
  if (all.check.op ~= aura.check.op) then
    all.check.op = nil;
    all.check.sameop = false;
  end

  if (all.check.value ~= aura.check.value) then
    all.check.value = nil;
    all.check.samevalue = false;
  end


  all.check.references = all.check.references or {};
  all.check.references[id] = {
    ["conditionIndex"] = conditionIndex,
    ["op"] = aura.check.op,
    ["value"] = aura.check.value
  };
  all.check.referenceCount = (all.check.referenceCount or 0) + 1;

  -- Merge properties
  local currentInsertPoint = 1;
  for changeIndex, change in ipairs(aura.changes) do
    local matchIndex = findMatchingProperty(all.changes, change, id);
    if (not matchIndex) then
      local copy = CopyTable(change);

      local propertyType = change.property and allProperties.propertyMap[change.property] and allProperties.propertyMap[change.property].type;
      if (propertyType == "chat" or propertyType == "sound" or propertyType == "customcode" or propertyType == "glowexternal") then
        copy.samevalue = {};
        for _, propertyName in ipairs(SubPropertiesForChange(change)) do
          copy.samevalue[propertyName] = true;
        end
      else
        copy.samevalue = true;
      end
      copy.references = {};
      copy.references[id] = {
        ["changeIndex"] = changeIndex,
        ["value"] = copy.value
      }

      copy.referenceCount = 1;
      tinsert(all.changes, currentInsertPoint, copy);
      currentInsertPoint = currentInsertPoint + 1;
    else
      mergeConditionChange(all.changes[matchIndex], change, id, changeIndex, allProperties);
      currentInsertPoint = matchIndex + 1;
    end
  end
end

local function mergeConditions(all, aura, id, allConditionTemplates, propertyTypes)
  if (not aura) then
    return;
  end

  local currentInsertPoint = 1;
  for conditionIndex, condition in ipairs(aura) do
    local match = findMatchingCondition(all, condition, currentInsertPoint, allConditionTemplates);
    if (not match) then
      local copy = CopyTable(condition);
      copy.check.samevalue = true;
      copy.check.sameop = true;
      copy.check.references = {};
      copy.check.references[id] = {
        ["conditionIndex"] = conditionIndex,
        ["op"] = condition.check.op,
        ["value"] = condition.check.value
      };
      copy.check.referenceCount = 1;

      if (copy.changes) then
        for changeIndex, change in pairs(copy.changes) do
          local propertyType = change.property and propertyTypes.propertyMap[change.property] and propertyTypes.propertyMap[change.property].type;
          if (propertyType == "chat" or propertyType == "sound" or propertyType == "customcode" or propertyType == "glowexternal") then
            change.samevalue = {};
            for _, propertyName in ipairs(SubPropertiesForChange(change)) do
              change.samevalue[propertyName] = true;
            end
          else
            change.samevalue = true;
          end
          change.references = {};
          change.references[id] = {
            ["changeIndex"] = changeIndex,
            ["value"] = condition.changes[changeIndex].value
          };
          change.referenceCount = 1;
        end
      end

      tinsert(all, currentInsertPoint, copy);
      currentInsertPoint = currentInsertPoint + 1;
    else
      mergeCondition(all[match], condition, id, conditionIndex, propertyTypes);
      currentInsertPoint = match + 1;
    end
  end
end

local fixupConditions = function(conditions)
  for _, condition in ipairs(conditions) do
    condition.check = condition.check or {}
    condition.changes = condition.changes or {}
  end
end

function OptionsPrivate.GetConditionOptions(data)
  local  options = {
    type = "group",
    name = L["Conditions"],
    order = 25,
    args = {}
  }

  local args = options.args

  local conditionVariable = "conditions"
  local startorder = 0
  local category = nil
  -- Build potential Conditions Templates structure
  local conditionTemplates, conditionTemplateWithoutCombinations = createConditionTemplates(data);

  -- Build potential properties structure
  local allProperties = buildAllPotentialProperties(data, category);

  -- Build currently selected conditions
  local conditions;
  local totalAuraCount

  if (data.controlledChildren) then
    local allChildren = {}
    for child in OptionsPrivate.Private.TraverseLeafs(data) do
      tinsert(allChildren, child)
    end
    totalAuraCount = #allChildren

    conditions = {};
    for index = totalAuraCount, 1, -1 do
      local child = allChildren[index]
      fixupConditions(child[conditionVariable])
      mergeConditions(conditions, child[conditionVariable], child.id, conditionTemplates.all, allProperties);
    end
  else
    totalAuraCount = 1
    data[conditionVariable] = data[conditionVariable] or {};
    conditions = data[conditionVariable];
    fixupConditions(data[conditionVariable])
  end

  local order = startorder;
  for i = 1, #conditions do
    order = addControlsForCondition(args, order, data, conditionVariable, totalAuraCount, conditions, i, conditionTemplates, conditionTemplateWithoutCombinations, allProperties);
  end

  args["addConditionHeader"] = {
    type = "header",
    width = WeakAuras.doubleWidth,
    name = "",
    order = order
  }
  order = order + 1

  args["addCondition"] = {
    type = "execute",
    width = WeakAuras.normalWidth,
    name = L["Add Condition"],
    order = order,
    func = function()
      for child in OptionsPrivate.Private.TraverseLeafsOrAura(data) do
        child[conditionVariable][#child[conditionVariable] + 1] = {};
        child[conditionVariable][#child[conditionVariable]].check = {};
        child[conditionVariable][#child[conditionVariable]].changes = {};
        child[conditionVariable][#child[conditionVariable]].changes[1] = {}
        child[conditionVariable][#child[conditionVariable]].category = category;
        OptionsPrivate.SetCollapsed(child.id, "condition", #child[conditionVariable], false);
        WeakAuras.Add(child);
        OptionsPrivate.ClearOptions(child.id)
      end
      WeakAuras.ClearAndUpdateOptions(data.id)
    end
  }
  order = order + 1;

  return options;
end
