-- Special layout for the New Aura Trigger template page

local AddonName, TemplatePrivate = ...

local AceGUI = LibStub("AceGUI-3.0");
local floor, ceil, tinsert = floor, ceil, tinsert;
local CreateFrame, UnitClass, UnitRace, GetSpecialization = CreateFrame, UnitClass, UnitRace, GetSpecialization;
local WeakAuras = WeakAuras;
local L = WeakAuras.L

AceGUI:RegisterLayout("WATemplateTriggerLayoutFlyout", function(content, children)
  local width = content.width or content:GetWidth() or 0
  local columns = floor(width / 250);

  local rows = columns > 0 and ceil(#children / columns) or 0;
  columns = rows > 0 and ceil(#children / rows) or 1;
  local relWidth = 1 / columns;
  for i = 1, #children do
    local child = children[i]
    if (not child:IsFullWidth()) then
      child:SetRelativeWidth(relWidth);
    end
  end
  local flowLayout = AceGUI:GetLayout("Flow");
  flowLayout(content, children);
end);

local colors = {
  grey = { 0.5, 0.5, 0.5, 1 },
  blue = { 0.5, 0.5, 1, 1 },
  red = { 0.8, 0.1, 0.1, 1 },
  white = { 1, 1, 1, 1 },
  yellow = { 1, 1, 0, 1 },
  green = { 0, 1, 0, 1},
};

local regionColorProperty = {
  icon = "color",
  aurabar= "barColor",
  progresstexture = "foregroundColor",
  text = "color",
  texture = "color",
};

local function changes(property, regionType)
  if colors[property] and regionColorProperty[regionType] then
    return {
      value = colors[property],
      property = regionColorProperty[regionType],
    };
  elseif property == "glow" and (regionType == "icon" or regionType == "aurabar") then
    local subregionPos = regionType == "aurabar" and 1 or 2
    return {
      value = true,
      property = "sub."..subregionPos..".glow"
    };
  elseif WeakAuras.regionTypes[regionType].default[property] == nil then
    return nil;
  elseif property == "cooldownSwipe" then
    return {
      value = true,
      property = "cooldownSwipe",
    };
  elseif property == "alpha" then
    return {
      value = 0.5,
      property = "alpha",
    };
  elseif property == "inverse" then
    return {
      value = false,
      property = "inverse",
    };
  end
end

local checks = {
  spellInRange = {
    variable = "spellInRange",
    value = 0,
  },
  itemInRange = {
    variable = "itemInRange",
    value = 0,
  },
  hasTarget = {
    trigger = -1,
    variable = "hastarget",
    value = 0,
  },
  insufficientResources =  {
    variable = "insufficientResources",
    value = 1,
  },
  buffedAuraFound = {
    variable = "show",
    value = 1,
  },
  buffedAuraAlways = {
    variable = "buffed",
    value = 1,
  },
  buffedFalseAuraAlways = {
    variable = "buffed",
    value = 0,
  },
  duration = {
    variable = "show",
    value = 1,
  },
  onCooldown = {
    variable = "onCooldown",
    value = 1,
  },
  charges = {
    variable = "charges",
    op = "==",
    value = "0",
  },
  usable = {
    variable = "spellUsable",
    value = 0,
  },
  totem = {
    variable = "show",
    value = 1,
  },
  overlayGlow = {
    variable = "show",
    value = 1,
  },
  uninterruptible = {
    variable = "interruptible",
    value = 0,
  },
  enchantMissing = {
    variable = "enchanted",
    value = 0
  },
  queued = {
    variable = "show",
    value = 1,
  }
}

local function buildCondition(trigger, check, properties)
  local result = {};
  result.check = CopyTable(check);
  if (not result.check.trigger) then
    result.check.trigger = trigger;
  end

  result.changes = {};
  local hasChanges = false;
  for index, v in ipairs(properties) do
    result.changes[index] = CopyTable(v);
    hasChanges = true;
  end
  return hasChanges and result or nil;
end

local function missingBuffGreyed(conditions, trigger, regionType)
  tinsert(conditions, buildCondition(trigger, checks.buffedFalseAuraAlways, {changes("grey", regionType)}));
end

local function hasTargetAlpha(conditions, regionType)
  tinsert(conditions, buildCondition(nil, checks.hasTarget, {changes("alpha", regionType)}));
end

local function isNotUsableBlue(conditions, trigger, regionType)
  tinsert(conditions, buildCondition(trigger, checks.usable, {changes("blue", regionType)}));
end

local function insufficientResourcesBlue(conditions, trigger, regionType)
  tinsert(conditions, buildCondition(trigger, checks.insufficientResources, {changes("blue", regionType)}));
end

local function hasChargesGrey(conditions, trigger, regionType)
  if regionType == "icon" then
    tinsert(conditions, buildCondition(trigger, checks.charges, {changes("cooldownSwipe", regionType)}));
  else
    tinsert(conditions, buildCondition(trigger, checks.charges, {changes("grey", regionType)}));
  end
end

local function isOnCdGrey(conditions, trigger, regionType)
  tinsert(conditions, buildCondition(trigger, checks.onCooldown, {changes("grey", regionType)}));
end

local function GenericGlow(conditions, trigger, regionType, check)
  if regionType == "icon" then
    tinsert(conditions, buildCondition(trigger, check, {changes("inverse", regionType), changes("glow", regionType), changes("white", regionType)}));
  elseif regionType == "aurabar" then
    tinsert(conditions, buildCondition(trigger, check, {changes("inverse", regionType), changes("glow", regionType), changes("yellow", regionType)}));
  elseif regionType == "progresstexture" then
    tinsert(conditions, buildCondition(trigger, check, {changes("inverse", regionType), changes("yellow", regionType)}));
  else
    tinsert(conditions, buildCondition(trigger, check, {changes("yellow", regionType)}));
  end
end

local function isQueuedGlow(conditions, trigger, regionType)
  GenericGlow(conditions, trigger, regionType, checks.queued)
end

local function isBuffedGlow(conditions, trigger, regionType)
  GenericGlow(conditions, trigger, regionType, checks.buffedAuraFound)
end

local function isDurationGlow(conditions, trigger, regionType)
  GenericGlow(conditions, trigger, regionType, checks.duration)
end

local function isBuffedGlowAuraAlways(conditions, trigger, regionType)
  GenericGlow(conditions, trigger, regionType, checks.buffedAuraAlways)
end

local function totemActiveGlow(conditions, trigger, regionType)
  GenericGlow(conditions, trigger, regionType, checks.totem)
end

local function isMissingEnchantGlow(conditions, trigger, regionType)
  tinsert(conditions, buildCondition(trigger, checks.enchantMissing, {changes("glow", regionType)}));
end

local function overlayGlow(conditions, trigger, regionType)
  if regionType == "icon" or regionType == "aurabar" then
    tinsert(conditions, buildCondition(trigger, checks.overlayGlow, {changes("glow", regionType)}));
  else
    tinsert(conditions, buildCondition(trigger, checks.overlayGlow, {changes("yellow", regionType)}));
  end
end

local function uninterruptibleRed(conditions, trigger, regionType)
  tinsert(conditions, buildCondition(trigger, checks.uninterruptible, {changes("red", regionType)}));
end

local function isSpellNotInRangeRed(conditions, trigger, regionType)
  tinsert(conditions, buildCondition(trigger, checks.spellInRange, {changes("red", regionType)}));
end

local function itemInRangeRed(conditions, trigger, regionType)
  tinsert(conditions, buildCondition(trigger, checks.itemInRange, {changes("red", regionType)}));
end

local function createBuffTrigger(triggers, position, item, buffShowOn, isBuff)
  triggers[position] = {
    trigger = {
      unit = item.unit or isBuff and "player" or "target",
      type = "aura2",
      matchesShowOn = buffShowOn,
      debuffType = isBuff and "HELPFUL" or "HARMFUL",
      ownOnly = not item.forceOwnOnly and true or item.ownOnly,
      unitExists = false,
    }
  };

  if item.spellIds then
    if item.exactSpellId then
      triggers[position].trigger.useExactSpellId = true
      triggers[position].trigger.auraspellids = {}
      for index, spell in ipairs(item.spellIds) do
        triggers[position].trigger.auraspellids[index] = tostring(spell)
      end
    else
      triggers[position].trigger.useName = true
      triggers[position].trigger.auranames = {}
      for index, spell in ipairs(item.spellIds) do
        triggers[position].trigger.auranames[index] = tostring(spell)
      end
    end
  else
    if item.exactSpellId then
      triggers[position].trigger.useExactSpellId = true
      triggers[position].trigger.auraspellids = { tostring(item.buffId or item.spell) }
    else
      triggers[position].trigger.useName = true
      triggers[position].trigger.auranames = { tostring(item.buffId or item.spell) }
    end
  end

  if triggers[position].trigger.unit == "multi" and buffShowOn == "showOnActive"  then
    local trigger = triggers[position].trigger
    trigger.useGroup_count = true
    trigger.group_countOperator =  ">="
    trigger.group_count = "1"
  end

  if (item.unit == "group") then
    triggers[position].trigger.name_info = "players";
  end
  if (item.unit == "multi") then
    triggers[position].trigger.spellId = item.buffId or item.spell;
  end
end

local function createDurationTrigger(triggers, position, item)
  triggers[position] = {
    trigger = {
      type = WeakAuras.GetTriggerCategoryFor("Combat Log"),
      event = "Combat Log",
      subeventSuffix = "_CAST_SUCCESS",
      use_sourceUnit = true,
      sourceUnit = item.unit or "player",
      use_spellId = true,
      spellId = tostring(item.spell),
      duration = tostring(item.duration),
    }
  };
end

local function createTotemTrigger(triggers, position, item)
  triggers[position] = {
    trigger = {
      type = WeakAuras.GetTriggerCategoryFor("Totem"),
      event = "Totem",
      use_totemName = item.totemNumber == nil,
      totemName = GetSpellInfo(item.spell),
    }
  };
  if (item.totemNumber) then
    triggers[position].trigger.use_totemType = true;
    triggers[position].trigger.totemType = item.totemNumber;
  end
end

local function createPowerTrigger(triggers, position, item)
  triggers[position] = {
    trigger = {
      type = WeakAuras.GetTriggerCategoryFor("Power"),
      event = "Power",
      use_unit = true,
      unit = "player",
      use_powertype = true,
      use_showCost = true,
      powertype = item.powertype,
    },
  };
end

local function createHealthTrigger(triggers, position, item)
  triggers[position] = {
    trigger = {
      type = WeakAuras.GetTriggerCategoryFor("Health"),
      event = "Health",
      unit = "player",
      use_unit = true,
      use_absorbMode = true,
      use_showAbsorb = true,
      use_showIncomingHeal = true,
    },
  };
end

local function createCastTrigger(triggers, position, item)
  triggers[position] = {
    trigger = {
      type = WeakAuras.GetTriggerCategoryFor("Cast"),
      event = "Cast",
      use_unit = true,
      unit = item.unit or "player",
    },
  };
end

local function createAbilityTrigger(triggers, position, item, genericShowOn)
  triggers[position] = {
    trigger = {
      type = WeakAuras.GetTriggerCategoryFor("Cooldown Progress (Spell)"),
      event = "Cooldown Progress (Spell)",
      spellName = item.spell,
      use_genericShowOn = true,
      genericShowOn = genericShowOn,
    }
  };
  if genericShowOn == "showOnReady" then
    triggers[position].trigger.use_track = true
    triggers[position].trigger.track = "cooldown"
  end
end

local function createItemTrigger(triggers, position, item, genericShowOn)
  triggers[position] = {
    trigger = {
      type = WeakAuras.GetTriggerCategoryFor("Cooldown Progress (Item)"),
      event = "Cooldown Progress (Item)",
      use_genericShowOn = true,
      genericShowOn = genericShowOn,
      itemName = item.spell,
    }
  };
end

local function createOverlayGlowTrigger(triggers, position, item)
  triggers[position] = {
    trigger = {
      type = WeakAuras.GetTriggerCategoryFor("Spell Activation Overlay"),
      event = "Spell Activation Overlay",
      spellName = item.spell,
    }
  };
end

local function createWeaponEnchantTrigger(triggers, position, item, showOn)
  triggers[position] = {
    trigger = {
      type = WeakAuras.GetTriggerCategoryFor("Weapon Enchant"),
      event = "Weapon Enchant",
      use_enchant = true,
      enchant = tostring(item.enchant),
      weapon = item.weapon,
      showOn = showOn
    }
  }
end

local function createQueuedActionTrigger(triggers, position, item)
  triggers[position] = {
    trigger = {
      type = WeakAuras.GetTriggerCategoryFor("Queued Action"),
      event = "Queued Action",
      spellName = item.spell
    }
  }
end

local function createAbilityAndQueuedActionTrigger(triggers, item)
  createAbilityTrigger(triggers, 1, item, "showAlways");
  createQueuedActionTrigger(triggers, 2, item);
end

local function createAbilityAndDurationTrigger(triggers, item)
  createDurationTrigger(triggers, 1, item);
  createAbilityTrigger(triggers, 2, item, "showAlways");
end

local function createAbilityAndBuffTrigger(triggers, item)
  createBuffTrigger(triggers, 1, item, "showOnActive", true);
  createAbilityTrigger(triggers, 2, item, "showAlways");
end

local function createAbilityAndDebuffTrigger(triggers, item)
  createBuffTrigger(triggers, 1, item, "showOnActive", false);
  createAbilityTrigger(triggers, 2, item, "showAlways");
end

local function createAbilityAndOverlayGlowTrigger(triggers, item)
  createAbilityTrigger(triggers, 1, item, "showAlways");
  createOverlayGlowTrigger(triggers, 2, item);
end

-- Create preview thumbnail
local function createThumbnail(parent)
  -- Preview frame
  local borderframe = CreateFrame("FRAME", nil, parent);
  borderframe:SetWidth(32);
  borderframe:SetHeight(32);

  -- Preview border
  local border = borderframe:CreateTexture(nil, "OVERLAY");
  border:SetAllPoints(borderframe);
  border:SetTexture("Interface\\BUTTONS\\UI-Quickslot2.blp");
  border:SetTexCoord(0.2, 0.8, 0.2, 0.8);

  -- Main region
  local region = CreateFrame("FRAME", nil, borderframe);
  borderframe.region = region;

  -- Preview children
  region.children = {};

  -- Return preview
  return borderframe;
end

local function subTypesFor(item, regionType)
  local types = {};
  local icon = {
    target = function()
      local thumbnail = createThumbnail();
      local t1 = thumbnail:CreateTexture(nil, "ARTWORK");
      t1:SetTexture(134376);
      t1:SetAllPoints(thumbnail);

      thumbnail.elapsed = 0;
      thumbnail:SetScript("OnUpdate", function(self, elapsed)
        self.elapsed = self.elapsed + elapsed;
        if(self.elapsed < 0.5) then
          t1:SetVertexColor(1,0,0,1);
        elseif(self.elapsed < 1.5) then
          t1:SetVertexColor(1,1,1,1);
        elseif(self.elapsed < 3) then
          -- Do nothing
        else
          self.elapsed = self.elapsed - 3;
        end
      end);
      return thumbnail;
    end, -- 132212,
    glow = function()
      local thumbnail = createThumbnail();
      local t1 = thumbnail:CreateTexture(nil, "ARTWORK");
      t1:SetTexture(134376);
      t1:SetAllPoints(thumbnail);
      WeakAuras.ShowOverlayGlow(thumbnail); -- where to call HideOverlayGlow() ?
      return thumbnail;
    end, -- 571554
    charges = function()
      local thumbnail = createThumbnail();
      local t1 = thumbnail:CreateTexture(nil, "ARTWORK");
      t1:SetTexture(134376);
      t1:SetAllPoints(thumbnail);
      local t2 = thumbnail:CreateFontString(nil, "ARTWORK");
      t2:SetFont(STANDARD_TEXT_FONT, 14, "OUTLINE");
      t2:SetTextColor(1,1,1,1);
      t2:SetText("2");
      t2:SetPoint("BOTTOMRIGHT", -2, 2);
      return thumbnail;
    end,
    cd = 134377,
    cd2 = 134376,
  };
  local data = {}
  local dataGlow = {}
  if regionType == "aurabar" then
    dataGlow.subRegions = {
      [1] = WeakAuras.getDefaultGlow(regionType)
    }
  end
  if (item.type == "ability") then
    tinsert(types, {
      fallback = true,
      icon = icon.cd,
      title = L["Basic Show On Cooldown"],
      description = L["Only shows the aura when the ability is on cooldown."],
      createTriggers = function(triggers, item)
        createAbilityTrigger(triggers, 1, item, "showOnCooldown");
      end,
    });
    tinsert(types, {
      icon = icon.cd,
      title = L["Basic Show On Cooldown"],
      description = L["Only shows the aura when the ability is on cooldown."],
      createTriggers = function(triggers, item)
        createAbilityTrigger(triggers, 1, item, "showOnCooldown");
      end,
      createConditions = function(conditions, item, regionType)
        isNotUsableBlue(conditions, 1, regionType)
      end,
    });
    tinsert(types, {
      icon = icon.cd,
      title = L["Basic Show On Ready"],
      description = L["Only shows the aura when the ability is ready to use."],
      createTriggers = function(triggers, item)
        createAbilityTrigger(triggers, 1, item, "showOnReady");
      end,
      createConditions = function(conditions, item, regionType)
        isNotUsableBlue(conditions, 1, regionType)
      end,
    });
    if (item.charges) then
      data.cooldownSwipe = false
      data.cooldownEdge = true
      dataGlow.cooldownSwipe = false
      dataGlow.cooldownEdge = true
      tinsert(types, {
        icon = icon.charges,
        title = L["Charge Tracking"],
        description = L["Always shows the aura, turns blue on insufficient resources."],
        createTriggers = function(triggers, item)
          createAbilityTrigger(triggers, 1, item, "showAlways");
        end,
        createConditions = function(conditions, item, regionType)
          insufficientResourcesBlue(conditions, 1, regionType);
          hasChargesGrey(conditions, 1, regionType);
        end,
        data = data,
      });
      if (item.duration) then
        tinsert(types, {
          icon = icon.glow,
          title = L["Charge and Duration Tracking"],
          description = L["Tracks the charge and the duration of spell, highlight while the spell is active, blue on insufficient resources."],
          createTriggers = createAbilityAndDurationTrigger,
          createConditions = function(conditions, item, regionType)
            insufficientResourcesBlue(conditions, 2, regionType);
            hasChargesGrey(conditions, 2, regionType);
            isDurationGlow(conditions, 1, regionType);
          end,
          data = dataGlow,
        });
      elseif (item.buff) then
        tinsert(types, {
          icon = icon.glow,
          title = L["Charge and Buff Tracking"],
          description = L["Tracks the charge and the buff, highlight while the buff is active, blue on insufficient resources."],
          createTriggers = createAbilityAndBuffTrigger,
          createConditions = function(conditions, item, regionType)
            insufficientResourcesBlue(conditions, 2, regionType);
            hasChargesGrey(conditions, 2, regionType);
            isBuffedGlow(conditions, 1, regionType);
          end,
          data = dataGlow,
        });
      elseif(item.debuff) then
        tinsert(types, {
          icon = icon.glow,
          title = L["Charge and Debuff Tracking"],
          description = L["Tracks the charge and the debuff, highlight while the debuff is active, blue on insufficient resources."],
          createTriggers = createAbilityAndDebuffTrigger,
          createConditions = function(conditions, item, regionType)
            insufficientResourcesBlue(conditions, 2, regionType);
            hasChargesGrey(conditions, 2, regionType);
            isBuffedGlow(conditions, 1, regionType);
          end,
          data = dataGlow,
        })
      elseif(item.requiresTarget) then
        tinsert(types,  {
          icon = icon.target,
          title = L["Show Charges with Range Tracking"],
          description = L["Always shows the aura, turns red when out of range, blue on insufficient resources."],
          genericShowOn = "showAlways",
          createTriggers = function(triggers, item)
            createAbilityTrigger(triggers, 1, item, "showAlways");
          end,
          createConditions = function(conditions, item, regionType)
            insufficientResourcesBlue(conditions, 1, regionType);
            hasChargesGrey(conditions, 1, regionType);
            isSpellNotInRangeRed(conditions, 1, regionType);
          end,
          data = data,
        });
        if (item.usable) then
          tinsert(types,  {
            icon = icon.target,
            title = L["Show Charges with Usable Check"],
            description = L["Always shows the aura, turns red when out of range, blue on insufficient resources."],
            createTriggers = function(triggers, item)
              createAbilityTrigger(triggers, 1, item, "showAlways");
            end,
            createConditions = function(conditions, item, regionType)
              isNotUsableBlue(conditions, 1, regionType);
              hasChargesGrey(conditions, 1, regionType);
              isSpellNotInRangeRed(conditions, 1, regionType);
            end,
            data = data,
          });
        end
        if (item.overlayGlow) then
          tinsert(types,  {
            icon = icon.glow,
            title = L["Show Charges with Proc Tracking"],
            description = L["Track the charge and proc, highlight while proc is active, turns red when out of range, blue on insufficient resources."],
            createTriggers = createAbilityAndOverlayGlowTrigger,
            createConditions = function(conditions, item, regionType)
              isNotUsableBlue(conditions, 1, regionType);
              hasChargesGrey(conditions, 1, regionType);
              isSpellNotInRangeRed(conditions, 1, regionType);
              overlayGlow(conditions, 2, regionType);
            end,
            data = dataGlow,
          });
        end
      elseif(item.totem) then
        tinsert(types, {
          icon = icon.charges,
          title = L["Show Totem and Charge Information"],
          description = L["Always shows the aura, highlight when active, turns blue on insufficient resources."],
          createTriggers = function(triggers, item)
            createTotemTrigger(triggers, 1, item);
            createAbilityTrigger(triggers, 2, item, "showAlways");
          end,
          createConditions = function(conditions, item, regionType)
            insufficientResourcesBlue(conditions, 2, regionType);
            hasChargesGrey(conditions, 2, regionType);
            totemActiveGlow(conditions, 1, regionType);
          end,
          data = dataGlow,
        });
      elseif(item.usable) then
        tinsert(types, {
          icon = icon.charges,
          title = L["Show Charges and Check Usable"],
          description = L["Always shows the aura, turns blue when not usable."],
          createTriggers = function(triggers, item)
            createAbilityTrigger(triggers, 1, item, "showAlways");
          end,
          createConditions = function(conditions, item, regionType)
            isNotUsableBlue(conditions, 1, regionType);
            hasChargesGrey(conditions, 1, regionType);
          end,
          data = data,
        });
        if (item.overlayGlow) then
          tinsert(types,  {
            icon = icon.glow,
            title = L["Show Charges with Proc Tracking"],
            description = L["Always shows the aura, highlight while proc is active, blue when not usable."],
            createTriggers = createAbilityAndOverlayGlowTrigger,
            createConditions = function(conditions, item, regionType)
              isNotUsableBlue(conditions, 1, regionType);
              hasChargesGrey(conditions, 1, regionType);
              overlayGlow(conditions, 2, regionType);
            end,
            data = dataGlow,
          });
        end
      end
    else -- Ability without charges
      tinsert(types, {
        icon = icon.cd2,
        title = L["Cooldown Tracking"],
        description = L["Always shows the aura, turns blue when not usable."],
        createTriggers = function(triggers, item)
          createAbilityTrigger(triggers, 1, item, "showAlways");
        end,
        createConditions = function(conditions, item, regionType)
          insufficientResourcesBlue(conditions, 1, regionType);
          isOnCdGrey(conditions, 1, regionType);
        end,
      });
      if (item.duration) then
        data.cooldownSwipe = false
        data.cooldownEdge = true
        dataGlow.cooldownSwipe = false
        dataGlow.cooldownEdge = true
        tinsert(types, {
          icon = icon.glow,
          title = L["Show Cooldown and Duration"],
          description = L["Highlight while spell is active."],
          createTriggers = createAbilityAndDurationTrigger,
          createConditions = function(conditions, item, regionType)
            insufficientResourcesBlue(conditions, 2, regionType);
            isOnCdGrey(conditions, 2, regionType);
            isDurationGlow(conditions, 1, regionType);
          end,
          data = dataGlow
        });
        if (item.usable) then
          tinsert(types, {
            icon = icon.glow,
            title = L["Show Cooldown and Duration and Check Usable"],
            description = L["Highlight while active."],
            createTriggers = createAbilityAndDurationTrigger,
            createConditions = function(conditions, item, regionType)
              isNotUsableBlue(conditions, 2, regionType);
              isOnCdGrey(conditions, 2, regionType);
              isDurationGlow(conditions, 1, regionType);
            end,
            data = dataGlow
          });
        end
        if (item.requiresTarget) then
          tinsert(types, {
            icon = icon.target,
            title = L["Show Cooldown and Duration and Check for Target"],
            description = L["Highlight while active, red when out of range."],
            createTriggers = createAbilityAndDurationTrigger,
            createConditions = function(conditions, item, regionType)
              insufficientResourcesBlue(conditions, 2, regionType);
              isOnCdGrey(conditions, 2, regionType);
              isSpellNotInRangeRed(conditions, 2, regionType);
              isDurationGlow(conditions, 1, regionType);
            end,
            data = dataGlow
          });
        end
      elseif (item.queued) then
        tinsert(types, {
          icon = icon.glow,
          title = L["Show Cooldown and Action Queued"],
          description = L["Highlight while action is queued."],
          createTriggers = createAbilityAndQueuedActionTrigger,
          createConditions = function(conditions, item, regionType)
            insufficientResourcesBlue(conditions, 1, regionType);
            isOnCdGrey(conditions, 1, regionType);
            isQueuedGlow(conditions, 2, regionType);
          end,
          data = dataGlow
        });
      elseif (item.buff) then
        tinsert(types, {
          icon = icon.glow,
          title = L["Show Cooldown and Buff"],
          description = L["Highlight while buffed."],
          createTriggers = createAbilityAndBuffTrigger,
          createConditions = function(conditions, item, regionType)
            insufficientResourcesBlue(conditions, 2, regionType);
            isOnCdGrey(conditions, 2, regionType);
            isBuffedGlow(conditions, 1, regionType);
          end,
          data = dataGlow
        });
        if (item.usable) then
          tinsert(types, {
            icon = icon.glow,
            title = L["Show Cooldown and Buff and Check Usable"],
            description = L["Highlight while buffed."],
            createTriggers = createAbilityAndBuffTrigger,
            createConditions = function(conditions, item, regionType)
              isNotUsableBlue(conditions, 2, regionType);
              isOnCdGrey(conditions, 2, regionType);
              isBuffedGlow(conditions, 1, regionType);
            end,
            data = dataGlow
          });
        end
        if (item.requiresTarget) then
          tinsert(types, {
            icon = icon.target,
            title = L["Show Cooldown and Buff and Check for Target"],
            description = L["Highlight while buffed, red when out of range."],
            createTriggers = createAbilityAndBuffTrigger,
            createConditions = function(conditions, item, regionType)
              if item.usable then
                isNotUsableBlue(conditions, 2, regionType);
              else
                insufficientResourcesBlue(conditions, 2, regionType);
              end
              isOnCdGrey(conditions, 2, regionType);
              isSpellNotInRangeRed(conditions, 2, regionType);
              isBuffedGlow(conditions, 1, regionType);
            end,
            data = dataGlow
          });
        end
      elseif(item.debuff) then
        tinsert(types, {
          icon = icon.glow,
          title = L["Show Cooldown and Debuff"],
          description = L["Highlight while debuffed."],
          createTriggers = createAbilityAndDebuffTrigger,
          createConditions = function(conditions, item, regionType)
            insufficientResourcesBlue(conditions, 2, regionType);
            isOnCdGrey(conditions, 2, regionType);
            isBuffedGlow(conditions, 1, regionType);
          end,
          data = dataGlow
        });
        if (item.requiresTarget) then
          tinsert(types, {
            icon = icon.target,
            title = L["Show Cooldown and Debuff and Check for Target"],
            description = L["Highlight while debuffed, red when out of range."],
            createTriggers = createAbilityAndDebuffTrigger,
            createConditions = function(conditions, item, regionType)
              if item.usable then
                isNotUsableBlue(conditions, 2, regionType);
              else
                insufficientResourcesBlue(conditions, 2, regionType);
              end
              isOnCdGrey(conditions, 2, regionType);
              isSpellNotInRangeRed(conditions, 2, regionType);
              isBuffedGlow(conditions, 1, regionType);
            end,
            data = dataGlow
          });
        end
      elseif(item.totem) then
        tinsert(types, {
          icon = icon.cd2,
          title = L["Show Cooldown and Totem Information"],
          description = L["Always shows the aura, turns grey if the ability is not usable."],
          createTriggers = function(triggers, item)
            createTotemTrigger(triggers, 1, item);
            createAbilityTrigger(triggers, 2, item, "showAlways");
          end,
          createConditions = function(conditions, item, regionType)
            insufficientResourcesBlue(conditions, 2, regionType);
            isOnCdGrey(conditions, 2, regionType);
            totemActiveGlow(conditions, 1, regionType);
          end,
          data = dataGlow
        });
      else
        if (item.requiresTarget) then
          tinsert(types, {
            icon = icon.target,
            title = L["Show Cooldown and Check for Target"],
            description = L["Always shows the aura, turns red when out of range."],
            createTriggers = function(triggers, item)
              createAbilityTrigger(triggers, 1, item, "showAlways");
            end,
            createConditions = function(conditions, item, regionType)
              insufficientResourcesBlue(conditions, 1, regionType);
              isOnCdGrey(conditions, 1, regionType);
              isSpellNotInRangeRed(conditions, 1, regionType);
            end,
          });
          if (item.overlayGlow) then
            tinsert(types,  {
              icon = icon.glow,
              title = L["Show Cooldown and Check for Target & Proc Tracking"],
              description = L["Always shows the aura, highlight while proc is active, turns red when out of range, blue on insufficient resources."],
              createTriggers = createAbilityAndOverlayGlowTrigger,
              createConditions = function(conditions, item, regionType)
                insufficientResourcesBlue(conditions, 1, regionType);
                isOnCdGrey(conditions, 1, regionType);
                isSpellNotInRangeRed(conditions, 1, regionType);
                overlayGlow(conditions, 2, regionType);
              end,
              data = dataGlow
            });
          end
        elseif (item.overlayGlow) then
          tinsert(types,  {
            icon = icon.glow,
            title = L["Show Cooldown and Proc Tracking"],
            description = L["Always shows the aura, highlight while proc is active, blue on insufficient resources."],
            createTriggers = createAbilityAndOverlayGlowTrigger,
            createConditions = function(conditions, item, regionType)
              insufficientResourcesBlue(conditions, 1, regionType);
              isOnCdGrey(conditions, 1, regionType);
              overlayGlow(conditions, 2, regionType);
            end,
            data = dataGlow
          });
        end
        if (item.usable) then
          tinsert(types, {
            icon = icon.cd2,
            title = L["Show Cooldown and Check Usable"],
            description = L["Always shows the aura, turns grey if the ability is not usable."],
            createTriggers = function(triggers, item)
              createAbilityTrigger(triggers, 1, item, "showAlways");
            end,
            createConditions = function(conditions, item, regionType)
              isNotUsableBlue(conditions, 1, regionType);
              isOnCdGrey(conditions, 1, regionType);
            end,
          });
          if (item.requiresTarget) then
            tinsert(types, {
              icon = icon.target,
              title = L["Show Cooldown and Check Usable & Target"],
              description = L["Always shows the aura, turns grey if the ability is not usable and red when out of range."],
              createTriggers = function(triggers, item)
                createAbilityTrigger(triggers, 1, item, "showAlways");
              end,
              createConditions = function(conditions, item, regionType)
                isNotUsableBlue(conditions, 1, regionType);
                isOnCdGrey(conditions, 1, regionType);
                isSpellNotInRangeRed(conditions, 1, regionType);
              end,
            });
            if (item.overlayGlow) then
              tinsert(types,  {
                icon = icon.glow,
                title = L["Show Cooldown and Check Usable, Target & Proc Tracking"],
                description = L["Always shows the aura, highlight while proc is active, turns red when out of range, blue on insufficient resources."],
                createTriggers = createAbilityAndOverlayGlowTrigger,
                createConditions = function(conditions, item, regionType)
                  isNotUsableBlue(conditions, 1, regionType);
                  isOnCdGrey(conditions, 1, regionType);
                  isSpellNotInRangeRed(conditions, 1, regionType);
                  overlayGlow(conditions, 2, regionType);
                end,
                data = dataGlow
              });
            end
          else
            if (item.overlayGlow) then
              tinsert(types,  {
                icon = icon.glow,
                title = L["Show Cooldown and Check Usable, Proc Tracking"],
                description = L["Always shows the aura, highlight while proc is active, blue on insufficient resources."],
                createTriggers = createAbilityAndOverlayGlowTrigger,
                createConditions = function(conditions, item, regionType)
                  isNotUsableBlue(conditions, 1, regionType);
                  isOnCdGrey(conditions, 1, regionType);
                  overlayGlow(conditions, 2, regionType);
                end,
                data = dataGlow
              });
            end
          end
        end
      end
    end
  elseif(item.type == "buff") then
    data.inverse = false
    dataGlow.inverse = false
    tinsert(types, {
      icon = icon.cd,
      title = L["Show Only if Buffed"],
      description = L["Only shows the aura if the target has the buff."],
      createTriggers = function(triggers, item)
        createBuffTrigger(triggers, 1, item, "showOnActive", true);
      end,
      data = data,
    });
    tinsert(types, {
      icon = icon.glow,
      title = L["Always Show"],
      description = L["Always shows the aura, highlight it if buffed."],
      buffShowOn = "showAlways",
      createTriggers = function(triggers, item)
        createBuffTrigger(triggers, 1, item, "showAlways", true);
      end,
      createConditions = function(conditions, item, regionType)
        isBuffedGlowAuraAlways(conditions, 1, regionType);
      end,
      data = dataGlow,
    });
    tinsert(types, {
      icon = icon.cd2,
      title = L["Always Show"],
      description = L["Always shows the aura, grey if buff not active."],
      createTriggers = function(triggers, item)
        createBuffTrigger(triggers, 1, item, "showAlways", true);
      end,
      createConditions = function(conditions, item, regionType)
        missingBuffGreyed(conditions, 1, regionType);
      end,
      data = data,
    });
  elseif(item.type == "debuff") then
    data.inverse = false
    dataGlow.inverse = false
    tinsert(types, {
      icon = icon.cd,
      title = L["Show Only if Debuffed"],
      description = L["Only show the aura if the target has the debuff."],
      createTriggers = function(triggers, item)
        createBuffTrigger(triggers, 1, item, "showOnActive", false);
      end,
      data = data,
    });
    tinsert(types, {
      icon = icon.glow,
      title = L["Always Show"],
      description = L["Always show the aura, highlight it if debuffed."],
      createTriggers = function(triggers, item)
        createBuffTrigger(triggers, 1, item, "showAlways", false);
      end,
      createConditions = function(conditions, item, regionType)
        isBuffedGlowAuraAlways(conditions, 1, regionType);
      end,
      data = dataGlow,
    });
    tinsert(types, {
      icon = icon.cd2,
      title = L["Always Show"],
      description = L["Always show the aura, turns grey if the debuff not active."],
      createTriggers = function(triggers, item)
        createBuffTrigger(triggers, 1, item, "showAlways", false);
      end,
      createConditions = function(conditions, item, regionType)
        missingBuffGreyed(conditions, 1, regionType);
      end,
      data = data,
    });
  elseif(item.type == "item") then
    tinsert(types, {
      icon = icon.cd,
      title = L["Show Only if on Cooldown"],
      description = L["Only show the aura when the item is on cooldown."],
      createTriggers = function(triggers, item)
        createItemTrigger(triggers, 1, item, "showOnCooldown");
      end,
    });
    tinsert(types, {
      icon = icon.cd,
      title = L["Show on Ready"],
      description = L["Only shows the aura when the ability is ready to use."],
      createTriggers = function(triggers, item)
        createItemTrigger(triggers, 1, item, "showOnReady");
      end,
    });
    tinsert(types, {
      icon = icon.cd2,
      title = L["Always Show"],
      description = L["Always show the aura, turns grey if on cooldown."],
      createTriggers = function(triggers, item)
        createItemTrigger(triggers, 1, item, "showAlways");
      end,
      createConditions = function(conditions, item, regionType)
        isOnCdGrey(conditions, 1, regionType);
      end,
    });
  elseif(item.type == "totem") then
    tinsert(types, {
      fallback = true,
      icon = icon.cd2,
      title = L["Always Show"],
      description = L["Always shows the aura."],
      createTriggers = function(triggers, item)
        createTotemTrigger(triggers, 1, item);
      end,
    });
    tinsert(types, {
      icon = icon.cd2,
      title = L["Always Show"],
      description = L["Always shows the aura, turns grey if on cooldown."],
      createTriggers = function(triggers, item)
        createTotemTrigger(triggers, 1, item);
      end,
      createConditions = function(conditions, item, regionType)
        totemActiveGlow(conditions, 1, regionType);
      end,
      data = dataGlow
    });
  elseif(item.type == "power") then
    data.inverse = false
    data.icon = false
    data.text = false
    tinsert(types, {
      icon = item.icon,
      title = item.title,
      createTriggers = function(triggers, item)
        createPowerTrigger(triggers, 1, item);
      end,
      data = data,
    });
  elseif(item.type == "health") then
    data.inverse = false
    data.icon = false
    data.text = false
    tinsert(types, {
      icon = item.icon,
      title = item.title,
      createTriggers = function(triggers, item)
        createHealthTrigger(triggers, 1, item);
      end,
      data = data,
    });
  elseif(item.type == "cast") then
    data.inverse = false
    tinsert(types, {
      fallback = true,
      icon = item.icon,
      title = item.title,
      createTriggers = function(triggers, item)
        createCastTrigger(triggers, 1, item);
      end,
      data = data,
    });
    tinsert(types, {
      icon = item.icon,
      title = item.title,
      createTriggers = function(triggers, item)
        createCastTrigger(triggers, 1, item);
      end,
      createConditions = function(conditions, item, regionType)
        uninterruptibleRed(conditions, 1, regionType);
      end,
      data = data,
    });
  elseif (item.type == "weaponenchant") then
    data.inverse = false
    tinsert(types, {
      icon = icon.cd,
      title = L["Show Only if Enchanted"],
      description = L["Only shows if the weapon is enchanted."],
      createTriggers = function(triggers, item)
        createWeaponEnchantTrigger(triggers, 1, item, "showOnActive");
      end,
      data = data,
    });
    tinsert(types, {
      icon = icon.cd,
      title = L["Show if Enchant Missing"],
      description = L["Only shows if the weapon is not enchanted."],
      createTriggers = function(triggers, item)
        createWeaponEnchantTrigger(triggers, 1, item, "showOnMissing");
      end,
      data = data,
    });
    tinsert(types, {
      icon = icon.glow,
      title = L["Show Always, Glow on Missing"],
      description = L["Always shows highlights if enchant missing."],
      createTriggers = function(triggers, item)
        createWeaponEnchantTrigger(triggers, 1, item, "showAlways");
      end,
      createConditions = function(conditions, item, regionType)
        isMissingEnchantGlow(conditions, 1, regionType);
      end,
      data = dataGlow,
    });
  end

  -- filter when createConditions return nothing for this regionType
  local fallbacks = {}
  for index = #types, 1, -1 do
    local type = types[index];
    if type.createConditions then
      local conditions = {}
      type.createConditions(conditions, item, regionType)
      if #conditions == 0 then
        tremove(types, index);
      end
    elseif type.fallback then
      tremove(types, index);
      tinsert(fallbacks, type)
    end
  end

  if #types > 0 then
    return types
  end
  return fallbacks
end

function WeakAuras.CreateTemplateView(Private, frame)
  TemplatePrivate.Private = Private

  local newView = AceGUI:Create("InlineGroup");
  newView.frame:SetParent(frame);
  newView.frame:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -17, 42);
  newView.frame:SetPoint("TOPLEFT", frame, "TOPLEFT", 17, -10);
  newView.frame:Hide();
  newView:SetLayout("fill");

  local newViewScroll = AceGUI:Create("ScrollFrame");
  newViewScroll:SetLayout("flow");
  newViewScroll.frame:SetClipsChildren(true);
  newView:AddChild(newViewScroll);

  local function createNewId(prefix)
    local new_id = prefix or "New";
    local num = 2;
    while(WeakAuras.GetData(new_id)) do
      new_id = prefix .. " " .. num;
      num = num + 1;
    end
    return new_id;
  end

  local function createConditionsFor(item, subType, regionType)
    if (subType.createConditions) then
      local conditions = {};
      subType.createConditions(conditions, item, regionType);
      return conditions;
    end
  end

  local function replaceCondition(data, item, subType)
    local conditions = createConditionsFor(item, subType, data.regionType);
    if conditions then
      data.conditions = CopyTable(conditions);
    end
  end

  local function addCondition(data, item, subType, prevNumTriggers)
    local conditions = createConditionsFor(item, subType, data.regionType);
    if conditions then
      if data.conditions then
        local position = #data.conditions + 1;
        for i,v in pairs(conditions) do
          data.conditions[position] = data.conditions[position] or {};
          if v.check.trigger ~= -1 then
            v.check.trigger = v.check.trigger + prevNumTriggers;
          end
          data.conditions[position] = CopyTable(v);
          position = position + 1;
        end
      else
        data.conditions = CopyTable(conditions);
      end
    end
  end

  local function createTriggersFor(item, subType)
    local triggers = {};
    subType.createTriggers(triggers, item);
    return triggers;
  end

  -- Trigger Template
  local function sortedPairs (t, f)
    local a = {}
    for n in pairs(t) do table.insert(a, n) end
    table.sort(a, f)
    local i = 0      -- iterator variable
    local iter = function ()   -- iterator function
      i = i + 1
      if a[i] == nil then return nil
      else return a[i], t[a[i]]
      end
    end
    return iter
  end

  local function createSortFunctionFor(table)
    return function(a, b)
      return table[a].title < table[b].title;
    end
  end

  local function replaceTrigger(data, item, subType)
    local triggers;
    if (item.triggers) then
      triggers = item.triggers;
    else
      triggers = createTriggersFor(item, subType);
    end

    data.triggers = {}
    for i, v in pairs(triggers) do
      data.triggers[i] = data.triggers[i] or {};
      data.triggers[i].trigger = CopyTable(v.trigger);
      data.triggers[i].untrigger = {};
      if (v.untrigger) then
        data.triggers[i].untrigger = CopyTable(v.untrigger);
      end
    end
    if (#data.triggers > 1) then -- Multiple triggers
      data.triggers.disjunctive = "any";
      data.triggers.activeTriggerMode = -10;
    end
  end

  local function addTrigger(data, item, subType)
    local triggers;
    if (item.triggers) then
      triggers = item.triggers;
    else
      triggers = createTriggersFor(item, subType);
    end

    for i, v in pairs(triggers) do
      local position = #data.triggers + 1
      data.triggers[position] = data.triggers[position] or {};
      data.triggers[position].trigger = CopyTable(v.trigger);
      data.triggers[position].untrigger = {};
      if (v.untrigger) then
        data.triggers[position].untrigger = CopyTable(v.untrigger);
      end
    end
     -- Multiple Triggers, override disjunctive, even if the users set it previously
    if (triggers[2]) then
      data.triggers.disjunctive = "any";
      data.triggers.activeTriggerMode = -10;
    end
  end

  local createButtons;

  local function createRegionButton(regionType, regionData, selectedItem)
    local button = AceGUI:Create("WeakAurasNewButton");
    button:SetTitle(regionData.displayName);
    if(type(regionData.icon) == "string" or type(regionData.icon) == "table") then
      button:SetIcon(regionData.icon);
    end
    button:SetDescription(regionData.description);
    button:SetFullWidth(true);
    if (regionType == selectedItem) then
      button.frame:LockHighlight(true);
    end
    button:SetClick(function()
      createButtons((selectedItem ~= regionType) and regionType);
    end);
    return button;
  end

  local function createRegionFlyout(regionType, regionData)
    local group = AceGUI:Create("WeakAurasTemplateGroup");
    group:SetFullWidth(true);
    group:SetLayout("WATemplateTriggerLayoutFlyout");
    for _, item in ipairs(regionData.templates) do
      local templateButton = AceGUI:Create("WeakAurasNewButton");
      if (item.icon) then
        templateButton:SetIcon(item.icon);
      else
        templateButton:SetThumbnail(regionType, item.data)
      end

      templateButton:SetTitle(item.title);
      templateButton:SetDescription(item.description);
      templateButton:SetClick(function()
        newView.data = CopyTable(item.data);
        WeakAuras.validate(newView.data, WeakAuras.data_stub);
        newView.data.internalVersion = WeakAuras.InternalVersion();
        newView.data.regionType = regionType;
        createButtons();
      end);
      group:AddChild(templateButton);
    end
    return group;
  end

  local function createDropdown(member, values)
    local selector = AceGUI:Create("Dropdown");
    selector:SetList(values);
    selector:SetValue(newView[member]);
    selector:SetCallback("OnValueChanged", function(self, callback, v)
      newView[member] = v;
      createButtons();
    end);
    return selector;
  end

  local function createSpacer()
    local spacer = AceGUI:Create("Label");
    spacer:SetFullWidth(true);
    spacer:SetText(" ");
    return spacer;
  end

  local function relativeWidth(totalWidth)
    local columns = floor(totalWidth / 300);
    return 1 / columns;
  end

  local function batchModeToggler(value)
    if (not value) then
      -- clean selection
      for k in pairs(newView.chosenItemBatch) do
        newView.chosenItemBatch[k] = nil;
      end
      for k, f in pairs(newView.chosenItemButtonsBatch) do
        f.frame:UnlockHighlight();
        newView.chosenItemButtonsBatch[k] = nil;
      end
      newView.batchButton:Hide();
    end
  end

  local function createTriggerFlyout(section, fullWidth)
    local group = AceGUI:Create("WeakAurasTemplateGroup");
    group:SetFullWidth(true);
    newView.chosenItemBatch = {};
    newView.chosenItemButtonsBatch = {};
    group:SetLayout("WATemplateTriggerLayoutFlyout");
    if (section) then
      for j, item in sortedPairs(section, createSortFunctionFor(section)) do
        local button = AceGUI:Create("WeakAurasNewButton");
        button:SetTitle(item.title);
        button:SetDescription(item.description);
        if (fullWidth) then
          button:SetFullWidth(true);
        end
        if(item.icon) then
          button:SetIcon(item.icon);
        end
        button:SetClick(function()
          if (IsControlKeyDown() and not newView.existingAura) then
            if newView.chosenItemBatch[item] then
              button.frame:UnlockHighlight();
              newView.chosenItemBatch[item] = nil;
              newView.chosenItemButtonsBatch[j] = nil;
            else
              button.frame:LockHighlight();
              newView.chosenItemBatch[item] = true;
              newView.chosenItemButtonsBatch[j] = button;
            end
            local count = 0;
            for _ in pairs(newView.chosenItemBatch) do
              count = count + 1;
            end
            if count == 0 then
              newView.batchButton:Hide();
              newView.backButton:ClearAllPoints()
              newView.backButton:SetPoint("BOTTOMRIGHT", -147, -23);
            else
              newView.batchButton:Show();
              newView.backButton:ClearAllPoints()
              newView.backButton:SetPoint("BOTTOMRIGHT", -267, -23);
            end
          else
            newView.backButton:ClearAllPoints()
            newView.backButton:SetPoint("BOTTOMRIGHT", -147, -23);
            local subTypes = subTypesFor(item, newView.data.regionType);
            if #subTypes < 2 then
              local subType = subTypes[1] or {}
              if (newView.existingAura) then
                newView.chosenItem = item;
                newView.chosenSubType = subType;
                createButtons();
              else
                replaceTrigger(newView.data, item, subType);
                replaceCondition(newView.data, item, subType);
                newView.data.id = WeakAuras.FindUnusedId(item.title);
                newView.data.load = {};
                if (item.load) then
                  newView.data.load = CopyTable(item.load);
                end
                if (subType.data) then
                  WeakAuras.DeepMixin(newView.data, subType.data)
                end
                newView:CancelClose();
                WeakAuras.NewAura(newView.data, newView.data.regionType, newView.targetId);
              end
            else
              -- create trigger type selection
              newView.chosenItem = item;
              createButtons();
            end
          end
        end);
        group:AddChild(button);
      end
    end
    return group;
  end

  local function createTriggerTypeButtons()
    local item = newView.chosenItem;
    local group = AceGUI:Create("WeakAurasTemplateGroup");
    group:SetFullWidth(true);
    local subTypes = subTypesFor(item, newView.data.regionType);
    local subTypesButtons = {}
    local lastButton
    for k, subType in pairs(subTypes) do
      local button = AceGUI:Create("WeakAurasNewButton");
      subTypesButtons[k] = button;
      button:SetTitle(subType.title);
      button:SetDescription(subType.description);
      if subType.icon then
        if type(subType.icon) == "function" then
          button:SetIcon(subType.icon());
        else
          button:SetIcon(subType.icon);
        end
      end
      button:SetFullWidth(true);
      button:SetClick(function()
        if (newView.batchStep) then
          for index, subTypesButton in pairs(subTypesButtons) do
            if (index == k) then
              subTypesButton.frame:LockHighlight();
            else
              subTypesButton.frame:UnlockHighlight();
            end
          end
          newView.chosenItemBatchSubType[item] = subType;
        elseif (newView.existingAura) then
          newView.chosenItem = item;
          newView.chosenSubType = subType;
          createButtons();
        else
          replaceTrigger(newView.data, item, subType);
          replaceCondition(newView.data, item, subType);
          newView.data.id = WeakAuras.FindUnusedId(item.title);
          newView.data.load = {};
          if (item.load) then
            newView.data.load = CopyTable(item.load);
          end
          if (subType.data) then
            WeakAuras.DeepMixin(newView.data, subType.data)
          end
          newView:CancelClose();
          WeakAuras.NewAura(newView.data, newView.data.regionType, newView.targetId);
        end
      end);
      if newView.batchStep then
        button.frame:LockHighlight();
        newView.chosenItemBatchSubType[item] = subType;
        if lastButton then
          lastButton.frame:UnlockHighlight();
        end
        lastButton = button
      end
      group:AddChild(button);
    end
    return group;
  end

  local function createTriggerButton(section, selectedItem, fullWidth)
    local button = AceGUI:Create("WeakAurasNewButton");
    button:SetTitle(section.title);
    button:SetDescription(section.description);
    if (section.icon) then
      button:SetIcon(section.icon);
    end
    button:SetFullWidth(true);
    button:SetClick(function()
      createButtons((selectedItem ~= section) and section);
    end);
    newViewScroll:AddChild(button);
    if (section == selectedItem) then
      button.frame:LockHighlight(true);
      local group = createTriggerFlyout(section.args, fullWidth);
      newViewScroll:AddChild(group);
    end
  end
  -- Creates a button + flyout (if the button is selected) for one section
  local function createTriggerButtons(templates, selectedItem, fullWidth)
    for k, section in ipairs(templates) do
      if section.args and next(section.args) then
        createTriggerButton(section, selectedItem, fullWidth);
      end
    end
  end

  local function replaceTriggers(data, item, subType)
    local function handle(data, item, subType)
      replaceTrigger(data, item, subType);
      replaceCondition(data, item, subType);
      newView:CancelClose();
      WeakAuras.Add(data);
      WeakAuras.ClearAndUpdateOptions(data.id)
      WeakAuras.FillOptions()
      WeakAuras.NewDisplayButton(data);
      WeakAuras.UpdateThumbnail(data);
      WeakAuras.UpdateDisplayButton(data);
    end
    for child in TemplatePrivate.Private.TraverseLeafsOrAura(data) do
      handle(child, item, subType);
    end
    WeakAuras.ClearAndUpdateOptions(data.id)
  end

  local function addTriggers(data, item, subType)
    local function handle(data, item, subType)
      local prevNumTriggers = #data.triggers;
      addTrigger(data, item, subType);
      addCondition(data, item, subType, prevNumTriggers);
      newView:CancelClose();
      WeakAuras.Add(data);
      WeakAuras.ClearAndUpdateOptions(data.id)
      WeakAuras.FillOptions()
      WeakAuras.NewDisplayButton(data);
      WeakAuras.UpdateThumbnail(data);
      WeakAuras.UpdateDisplayButton(data);
    end
    for child in TemplatePrivate.Private.TraverseLeafsOrAura(data) do
      handle(child, item, subType);
    end
    WeakAuras.ClearAndUpdateOptions(data.id)
  end

  local function createLastPage()
    local replaceButton = AceGUI:Create("WeakAurasNewButton");
    replaceButton:SetTitle(L["Replace Triggers"]);
    replaceButton:SetDescription(L["Replace all existing triggers"]);
    replaceButton:SetIcon("Interface\\Icons\\Spell_ChargeNegative");
    replaceButton:SetFullWidth(true);
    replaceButton:SetClick(function()
      replaceTriggers(newView.data, newView.chosenItem, newView.chosenSubType);
      for _,v in pairs({"class", "spec", "talent", "pvptalent", "race", "covenant"}) do
        newView.data.load[v] = nil;
        newView.data.load["use_"..v] = nil;
      end
      newView.data.load.class = CopyTable(WeakAuras.data_stub.load.class);
      newView.data.load.spec = CopyTable(WeakAuras.data_stub.load.spec);
      if (newView.chosenItem.load) then
        WeakAuras.DeepMixin(newView.data.load, newView.chosenItem.load)
      end
    end);
    newViewScroll:AddChild(replaceButton);

    local addButton = AceGUI:Create("WeakAurasNewButton");
    addButton:SetTitle(L["Add Triggers"]);
    addButton:SetDescription(L["Keeps existing triggers intact"]);
    addButton:SetIcon("Interface\\Icons\\Spell_ChargePositive");
    addButton:SetFullWidth(true);
    addButton:SetClick(function()
      addTriggers(newView.data, newView.chosenItem, newView.chosenSubType);
    end);
    newViewScroll:AddChild(addButton);
  end

  createButtons = function(selectedItem) -- selectedItem is either a regionType or a trigger section
    newViewScroll:ReleaseChildren();
    newView.makeBatchButton:Hide();
    newView.batchButton:Hide();
    newView.batchModeLabel:Hide();
    if (not newView.data) then
      -- First step: Show region types
      for regionType, regionData in pairs(WeakAuras.regionOptions) do
        if (regionData.templates) then
          local button = createRegionButton(regionType, regionData, selectedItem);
          newViewScroll:AddChild(button);
          if (regionType == selectedItem) then
            local group = createRegionFlyout(regionType, regionData);
            newViewScroll:AddChild(group);
          end
        end
      end
      newView.backButton:Hide();
    elseif (newView.data and newView.batchStep) then
      -- Batch
      if (newView.batchStep) then
        newView.chosenItemBatchSubType = {};
        for item in pairs(newView.chosenItemBatch) do
          local classHeader = AceGUI:Create("Heading");
          classHeader:SetFullWidth(true);
          newViewScroll:AddChild(classHeader);

          local button = AceGUI:Create("WeakAurasNewButton");
          button:SetTitle(item.title);
          button:SetDescription(item.description);
          button:SetFullWidth(true);
          if(item.icon) then
            button:SetIcon(item.icon);
          end
          newViewScroll:AddChild(button);

          newView.chosenItem = item;
          local typesButtons = createTriggerTypeButtons();
          newViewScroll:AddChild(typesButtons);
        end
        newView.makeBatchButton:Show()
        newView.backButton:ClearAllPoints()
        newView.backButton:SetPoint("BOTTOMRIGHT", -267, -23);
      end
      newView.batchModeLabel:Hide();
    elseif (newView.data and not newView.chosenItem) then
      -- Second step: Trigger selection screen

      -- Class
      local classSelector = createDropdown("class", WeakAuras.class_types);
      newViewScroll:AddChild(classSelector);

      if WeakAuras.IsRetail() then
        local specSelector = createDropdown("spec", WeakAuras.spec_types_specific[newView.class]);
        newViewScroll:AddChild(specSelector);
        newViewScroll:AddChild(createSpacer());
      end
      if (WeakAuras.triggerTemplates.class[newView.class] and WeakAuras.triggerTemplates.class[newView.class][newView.spec]) then
        createTriggerButtons(WeakAuras.triggerTemplates.class[newView.class][newView.spec], selectedItem);
      end
      local classHeader = AceGUI:Create("Heading");
      classHeader:SetFullWidth(true);
      newViewScroll:AddChild(classHeader);

      createTriggerButton(WeakAuras.triggerTemplates.general, selectedItem);

      -- Race
      local raceHeader = AceGUI:Create("Heading");
      raceHeader:SetFullWidth(true);
      newViewScroll:AddChild(raceHeader);
      local raceSelector = createDropdown("race", WeakAuras.race_types);
      newViewScroll:AddChild(raceSelector);
      newViewScroll:AddChild(createSpacer());
      if (WeakAuras.triggerTemplates.race[newView.race]) then
        local group = createTriggerFlyout(WeakAuras.triggerTemplates.race[newView.race], true);
        newViewScroll:AddChild(group);
      end

      -- backButton
      if (not newView.existingAura) then
        newView.backButton:Show();
      end

      -- batchButton
      newView.chosenItemBatch = {};
      if not newView.existingAura then
        newView.batchModeLabel:Show();
      end
    elseif (newView.data and newView.chosenItem and not newView.chosenSubType) then
      -- Multi-Type template
      local typeHeader = AceGUI:Create("Heading");
      typeHeader:SetFullWidth(true);
      newViewScroll:AddChild(typeHeader);
      local group = createTriggerTypeButtons();
      newViewScroll:AddChild(group);
      newView.backButton:Show();
    else
      --Third Step: (only for existing auras): replace or add triggers?
      createLastPage();
      newView.backButton:Show();
    end
  end

  local batchModeLabel = CreateFrame("FRAME", "batchModeLabel", newView.frame);
  batchModeLabel:SetSize(300, 20);
  local batchModeLabelString = batchModeLabel:CreateFontString(nil, "ARTWORK");
  batchModeLabelString:SetFont(STANDARD_TEXT_FONT, 10); -- "OUTLINE"
  batchModeLabelString:SetTextColor(1,1,1,1);
  batchModeLabelString:SetText(L["Hold CTRL to create multiple auras at once"]);
  batchModeLabelString:SetJustifyH("LEFT")
  batchModeLabelString:SetAllPoints(batchModeLabel);
  batchModeLabel:SetPoint("BOTTOMLEFT", 10, -23);
  newView.batchModeLabel = batchModeLabel;

  local newViewMakeBatch = CreateFrame("Button", nil, newView.frame, "UIPanelButtonTemplate");
  newViewMakeBatch:SetScript("OnClick", function()
    local saveData = CopyTable(newView.data);
    for item in pairs(newView.chosenItemBatch) do
      -- clean data
      newView.data = CopyTable(saveData);
      -- copy data
      local subType = newView.chosenItemBatchSubType[item]
      replaceTrigger(newView.data, item, subType);
      replaceCondition(newView.data, item, subType);
      newView.data.id = WeakAuras.FindUnusedId(item.title);
      newView.data.load = {};
      if (item.load) then
        newView.data.load = CopyTable(item.load);
      end
      if (subType.data) then
        WeakAuras.DeepMixin(newView.data, subType.data)
      end
      -- create aura
      WeakAuras.NewAura(newView.data, newView.data.regionType, newView.targetId);
    end
    newView:CancelClose();
  end);
  newViewMakeBatch:SetPoint("BOTTOMRIGHT", -147, -23);
  newViewMakeBatch:SetHeight(20);
  newViewMakeBatch:SetWidth(100);
  newViewMakeBatch:SetText(L["Create Auras"]);
  newView.makeBatchButton = newViewMakeBatch;
  newView.makeBatchButton:Hide();

  local newViewBatch = CreateFrame("Button", nil, newView.frame, "UIPanelButtonTemplate");
  newViewBatch:SetScript("OnClick", function()
    newView.batchStep = true;
    newView.batchButton:Hide();
    createButtons();
  end);
  newViewBatch:SetPoint("BOTTOMRIGHT", -147, -23);
  newViewBatch:SetHeight(20);
  newViewBatch:SetWidth(100);
  newViewBatch:SetText(L["Next"]);
  newView.batchButton = newViewBatch;
  newView.batchButton:Hide();

  local newViewBack = CreateFrame("Button", nil, newView.frame, "UIPanelButtonTemplate");
  newViewBack:SetScript("OnClick", function()
    if (newView.existingAura) then
      if newView.chosenSubType then
        newView.chosenSubType = nil;
        local subTypes = subTypesFor(newView.chosenItem, newView.data.regionType);
        if #subTypes < 2 then -- No subtype selection, go back twice
          newView.chosenItem = nil;
        end
      else
        newView.chosenItem = nil;
      end
    else
      if newView.chosenSubType then
        newView.chosenSubType = nil;
      else
        if newView.chosenItem then
          newView.chosenItem = nil;
        else
          newView.data = nil;
        end
      end
    end
    newView.batchButton:Hide();
    newView.chosenItemBatch = {};
    newView.batchStep = nil;
    newView.backButton:ClearAllPoints()
    newView.backButton:SetPoint("BOTTOMRIGHT", -147, -23);
    createButtons();
  end);
  newViewBack:SetPoint("BOTTOMRIGHT", -147, -23);
  newViewBack:SetHeight(20);
  newViewBack:SetWidth(100);
  newViewBack:SetText(L["Back"]);
  newView.backButton = newViewBack;

  local newViewCancel = CreateFrame("Button", nil, newView.frame, "UIPanelButtonTemplate");
  newViewCancel:SetScript("OnClick", function() newView:CancelClose() end);
  newViewCancel:SetPoint("BOTTOMRIGHT", -27, -23);
  newViewCancel:SetHeight(20);
  newViewCancel:SetWidth(100);
  newViewCancel:SetText(L["Cancel"]);

  function newView.Open(self, data, targetId)
    frame.window = "newView";
    frame:UpdateFrameVisible()
    self.targetId = targetId
    self.data = data -- Might be nil
    if (data) then
      newView.existingAura = true;
      newView.chosenItem = nil;
      newView.chosenSubType = nil;
    else
      newView.existingAura = false;
      newView.chosenItem = nil;
      newView.chosenSubType = nil;
      newView.batchStep = nil;
      newView.chosenItemBatch = {};
    end
    newView.class = select(2, UnitClass("player"));
    if WeakAuras.IsRetail() then
      newView.spec = GetSpecialization() or 1;
    else
      newView.spec = 1
    end
    newView.race = select(2, UnitRace('player'));

    createButtons();
  end

  function newView.CancelClose(self)
    frame.window = "default";
    frame:UpdateFrameVisible()
    if (not self.data) then
      frame:NewAura();
    end
  end

  return newView;
end
