-- Copyright © 2024 @Groick (Twitch). All rights reserved.
local function L(key, ...) return EasyOptions:Localize(key, ...) end
function EasyOptions:BuildOptions(addonName, addonDB, optionItems, optionalParameters, ...)
  local optionalParametersLocal = EasyOptions:BuildOptions_GetValidOptionalParametersFormat(optionalParameters, ...) or {}
  if (not EasyOptions:BuildOptions_ValidateParameters(addonName, addonDB, optionItems, optionalParametersLocal)) then
    return
  end

  -- Determine parameters.
  local currentVersion = EasyOptions:GetAddonVersion(addonName, false)
  local previousVersion = addonDB[EasyOptions.Fields.AddonVersionOptionName]
  local hasVersionChanged = (currentVersion ~= previousVersion)

  -- Set fields.
  EasyOptions.Current.AddonName = addonName
  EasyOptions.Current.AddonDB = addonDB
  EasyOptions.Current.OptionItemsParent = optionItems
  EasyOptions.Current.OptionItemsDictionary = {} -- init
  EasyOptions.Current.OptionControls = {} -- init
  EasyOptions.Current.OptionControlCounts = {} -- init
  --
  EasyOptions.Fields.AddonInfo[addonName] = {}
  EasyOptions.Fields.AddonInfo[addonName].AddonName = addonName
  EasyOptions.Fields.AddonInfo[addonName].AddonDB = addonDB
  EasyOptions.Fields.AddonInfo[addonName].OptionItems = EasyOptions.Current.OptionItemsParent
  EasyOptions.Fields.AddonInfo[addonName].OptionItemsDictionary = EasyOptions.Current.OptionItemsDictionary
  EasyOptions.Fields.AddonInfo[addonName].OptionControls = EasyOptions.Current.OptionControls
  EasyOptions.Fields.AddonInfo[addonName].OptionControlCounts = EasyOptions.Current.OptionControlCounts
  EasyOptions.Fields.AddonInfo[addonName].OnOptionValueChangedHandler = optionalParametersLocal.OnOptionValueChangedHandler
  EasyOptions.Fields.AddonInfo[addonName].OnOptionValuesChangingHandler = optionalParametersLocal.OnOptionValuesChangingHandler
  EasyOptions.Fields.AddonInfo[addonName].OnOptionValuesChangedHandler = optionalParametersLocal.OnOptionValuesChangedHandler
  EasyOptions.Fields.AddonInfo[addonName].OnOptionPanelFrameShowHandler = optionalParametersLocal.OnOptionPanelFrameShowHandler
  EasyOptions.Fields.AddonInfo[addonName].OnOptionPanelFrameHideHandler = optionalParametersLocal.OnOptionPanelFrameHideHandler
  EasyOptions.Fields.AddonInfo[addonName].IsValidSaveDataVersionProvider = optionalParametersLocal.IsValidSaveDataVersionProvider

  -- Initialize OptionItems.
  EasyOptions:SetOptionItemsProperties(EasyOptions.Current.OptionItemsParent, addonName, addonDB, true)
  EasyOptions:AddToOptionItemsDictionary(EasyOptions.Current.OptionItemsDictionary, EasyOptions.Current.OptionItemsParent, true)
  EasyOptions:InitializeOptionValues(EasyOptions.Current.OptionItemsParent, true)
  
  -- Build the controls in the Options frame.
  local mainOptionPanelFrame, optionPanelFrames = EasyOptions:CreateOptionFrames(optionItems)
  EasyOptions.Fields.AddonInfo[addonName].MainOptionPanelFrame = mainOptionPanelFrame
  EasyOptions.Fields.AddonInfo[addonName].OptionPanelFrames = optionPanelFrames

  -- Reset fields.
  EasyOptions.Current.AddonName = nil
  EasyOptions.Current.AddonDB = nil
  EasyOptions.Current.OptionItemsParent = nil
  EasyOptions.Current.OptionItemsDictionary = nil
  EasyOptions.Current.OptionControls = nil
  EasyOptions.Current.OptionControlCounts = nil

  -- Update internally managed option values.
  addonDB[EasyOptions.Fields.AddonVersionOptionName] = currentVersion

  -- Return values.
  return hasVersionChanged, previousVersion
end
function EasyOptions:BuildOptions_GetValidOptionalParametersFormat(optionalParametersOrOnOptionValueChangedHandler, ...)
  -- This method was added to support backwards compatibility with the original signature of EasyOptions:BuildOptions which had optional parameters passed individually.
  local args = {...} -- Adds all extra parameters that were passed to this method to a table.
  local onOptionValuesChangingHandler = args[1]
  local onOptionValuesChangedHandler = args[2]
  local onOptionPanelFrameShowHandler = args[3]
  local onOptionPanelFrameHideHandler = args[4]
  local isValidSaveDataVersionProvider = args[5]
  local isLegacyFormat = 
    ((optionalParametersOrOnOptionValueChangedHandler ~= nil) or (onOptionValuesChangingHandler ~= nil) or (onOptionValuesChangedHandler ~= nil) or (onOptionPanelFrameShowHandler ~= nil) or (onOptionPanelFrameHideHandler ~= nil) or (isValidSaveDataVersionProvider ~= nil)) and 
    ((optionalParametersOrOnOptionValueChangedHandler == nil) or (type(optionalParametersOrOnOptionValueChangedHandler) == "function")) and 
    ((onOptionValuesChangingHandler == nil) or (type(onOptionValuesChangingHandler) == "function")) and 
    ((onOptionValuesChangedHandler == nil) or (type(onOptionValuesChangedHandler) == "function")) and 
    ((onOptionPanelFrameShowHandler == nil) or (type(onOptionPanelFrameShowHandler) == "function")) and 
    ((onOptionPanelFrameHideHandler == nil) or (type(onOptionPanelFrameHideHandler) == "function")) and 
    ((isValidSaveDataVersionProvider == nil) or (type(isValidSaveDataVersionProvider) == "function"))
  if (not isLegacyFormat) then
    return optionalParametersOrOnOptionValueChangedHandler -- The parameters do not appear to be in the legacy format if we get here. Note that additional validation of the optionalParameters parameter is performed in EasyOptions:BuildOptions_ValidateParameters.
  end
  -- local addonNameLocal = EasyOptions:FormatName(addonName)
  -- local message = L("The addon \"{1}\" had an outdated format for parameters that are passed to EasyOptions:BuildOptions. All optional parameters (e.g., callbacks) should be contained in a single table that is passed to the optionalParameters parameter of that method.", addonNameLocal)
  -- EasyOptions:PrintErrorMessageOnce(message)
  local optionalParameters = { -- Convert parameters in the legacy format (i.e., individual parameters) to the current format (i.e., a table).
    OnOptionValueChangedHandler = optionalParametersOrOnOptionValueChangedHandler, 
    OnOptionValuesChangingHandler = onOptionValuesChangingHandler, 
    OnOptionValuesChangedHandler = onOptionValuesChangedHandler, 
    OnOptionPanelFrameShowHandler = onOptionPanelFrameShowHandler, 
    OnOptionPanelFrameHideHandler = onOptionPanelFrameHideHandler, 
    IsValidSaveDataVersionProvider = isValidSaveDataVersionProvider, 
  }
  return optionalParameters
end
function EasyOptions:BuildOptions_ValidateParameters(addonName, addonDB, optionItems, optionalParameters)
  local message
  if (addonName == nil) then
    message = L("Unable to build options because the specified addon name is nil.")
    EasyOptions:PrintErrorMessageOnce(message)
    return false
  end
  if (addonName == "") then
    message = L("Unable to build options because the specified addon name is an empty string.")
    EasyOptions:PrintErrorMessageOnce(message)
    return false
  end
  if (type(addonName) ~= "string") then
    message = L("Unable to build options because the specified addon name is a \"{1}\" rather than a string.", type(addonName))
    EasyOptions:PrintErrorMessageOnce(message)
    return false
  end
  if (EasyOptions.Fields.AddonInfo[addonName] ~= nil) then
    message = L("Unable to build options for \"{1}\" because options for an addon with that name have already been built.", addonName)
    EasyOptions:PrintErrorMessageOnce(message)
    return false
  end
  if (addonDB == nil) then
    message = L("Unable to build options for the addon \"{1}\" because the specified database table is nil.", addonName)
    EasyOptions:PrintErrorMessageOnce(message)
    return false
  end
  if (type(addonDB) ~= "table") then
    message = L("Unable to build options for the addon \"{1}\" because the specified database type is \"{2}\" rather than a table.", addonName, type(addonDB))
    EasyOptions:PrintErrorMessageOnce(message)
    return false
  end
  if (optionItems == nil) then
    message = L("Unable to build options for the addon \"{1}\" because the optionItems parameter is nil.", addonName)
    EasyOptions:PrintErrorMessageOnce(message)
    return false
  end
  if (type(optionItems) ~= "table") then
    message = L("Unable to build options for the addon \"{1}\" because the optionItems parameter is type \"{2}\" rather than a table.", addonName, type(optionItems))
    EasyOptions:PrintErrorMessageOnce(message)
    return false
  end
  if (optionalParameters ~= nil) and (type(optionalParameters) ~= "table") then
    message = L("Unable to build options for the addon \"{1}\" because the optionalParameters parameter is type \"{2}\" rather than a table.", addonName, type(optionalParameters))
    EasyOptions:PrintErrorMessageOnce(message)
    return false
  end
  return true
end
function EasyOptions:InitializeOptionValue(optionItem) -- Sets the option value to its default value if it has not already been set.
  if (optionItem == nil) then
    return
  end

  local addonDB = optionItem.AddonDB
  local optionName = optionItem.Name
  if (optionName == nil) then -- Validate optionName.
    return
  elseif (addonDB[optionName] ~= nil) then -- Determine whether the option value has already been set.
    return -- Do not modify the currently stored value of addonDB[optionName] if addonDB[optionName] has already been set.
  else
    addonDB[optionName] = optionItem.DefaultValue -- Set the option value to its default value.
  end
end
function EasyOptions:InitializeOptionValues(optionItems, includeChildren) -- Sets the option values to their default value if they have not already been set.
  if (optionItems == nil) then
    return
  end
  local length = EasyOptions:GetLength(optionItems)
  for i = 1, length do
    EasyOptions:InitializeOptionValue(optionItems[i])
  end
  --
  if (includeChildren) then
    local childrenOptionItems = optionItems.Children
    local childrenLength = EasyOptions:GetLength(childrenOptionItems)
    for i = 1, childrenLength do
      EasyOptions:InitializeOptionValues(childrenOptionItems[i], includeChildren)
    end
  end
end
function EasyOptions:AddToOptionItemsDictionary(optionItemsDictionary, optionItems, includeChildren)
  if (optionItems == nil) then
    return
  end
  --
  local length = EasyOptions:GetLength(optionItems)
  local optionItem
  local optionName
  local isValid
  for i = 1, length do
    optionItem = optionItems[i]
    if (optionItem ~= nil) and (optionItem.Name ~= nil) then
      optionName = optionItem.Name
      if (optionItemsDictionary[optionName] == nil) then
        optionItemsDictionary[optionName] = optionItem
      else
        EasyOptions:PrintErrorMessage(L("\"{1}\" is a duplicate item name in options.", optionName))
      end
    end
  end
  --
  if (includeChildren) then
    local childrenLength = EasyOptions:GetLength(optionItems.Children)
    for i = 1, childrenLength do
      EasyOptions:AddToOptionItemsDictionary(optionItemsDictionary, optionItems.Children[i], includeChildren)
    end
  end
end

function EasyOptions:SetOptionItemProperties(optionItem, parent, addonName, addonDB)
  -- Validate optionItem.
  if (optionItem == nil) then
    return
  end
  
  -- Determine parameters.
  local parentName
  if (parent == nil) then
    parentName = nil
  else
    parentName = parent.Name
  end

  -- Set common properties.
  optionItem.AddonName = addonName
  optionItem.CategoryName = parentName
  optionItem.AddonDB = addonDB
  optionItem.Parent = parent
  optionItem.GetOptionValue = function(self)
    local optionItem = self
    if (EasyOptions.Current.IsBuildingOptionFrame) then
      return EasyOptions:GetOptionValueOrDefaultValue(optionItem.Name, optionItem.DefaultValue)
    else
      return EasyOptions:GetOptionValue(optionItem.AddonName, optionItem.Name)
    end
  end

  -- BlankLine
  -- [1] EmptyLineCount (Optional, Default = 1) -- NOTE: Positive values of EmptyLineCount correspond to a decrease in the y-position of the next control.
  local isNumber = (optionItem ~= nil) and (type(optionItem) == "table") and (EasyOptions:GetLength(optionItem) == 1) and (type(optionItem[1]) == "number")
  optionItem.IsBlankLine = (optionItem == nil) or (isNumber) or ((optionItem[5] == nil) and (optionItem.ControlType == nil))
  if (optionItem.IsBlankLine) then
    optionItem.BlankLineCount = EasyOptions:IIF(isNumber, optionItem[1], 1.0) -- Specifies the number of consecutive blank lines that this OptionItem represents.
    return
  end
  
  -- Set standard properties.
  -- [1] Name, [2] Text, [3] TooltipText, [4] PositionInfo, [5] Type (or ControlType), [6+] [Type Specific]
  optionItem.Name = optionItem.Name or optionItem[1]
  optionItem.Text = optionItem.Text or optionItem[2]
  optionItem.TooltipText = optionItem.TooltipText or optionItem[3]
  optionItem.PositionInfo = optionItem.PositionInfo or optionItem[4] or 0
  optionItem.Type = optionItem.Type or optionItem.ControlType or optionItem[5] -- Allow "ControlType" to be used instead of "Type" if the OptionItem is declared using parameter name format.
  optionItem.ControlType = optionItem.Type

  -- Set Type-specific properties.
  local controlType = optionItem.ControlType
  if (controlType == "Hidden") then
    -- [6] DefaultValue (Any Value or Table)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]

  elseif (controlType == "GroupTitle") then
    -- GroupTitle: [6] Size (Optional), [7] WordWrap (Optional), [8] FontInfo (Optional), [9] TextColor (Optional), [10] IncludeUnderline (Optional), [11] UnderlineWidth (Optional)
    optionItem.Size = optionItem.Size or optionItem[6]
    optionItem.WordWrap = optionItem.WordWrap or optionItem[7]
    optionItem.FontInfo = optionItem.FontInfo or optionItem[8]
    optionItem.TextColor = optionItem.TextColor or optionItem[9]
    optionItem.IncludeUnderline = optionItem.IncludeUnderline or optionItem[10]
    optionItem.UnderlineWidth = optionItem.UnderlineWidth or optionItem[11]

  elseif (controlType == "GroupBox") then
    -- GroupBox: [6] Size (Optional), [7] FontInfo (Optional), [8] TextColor (Optional)
    optionItem.Size = optionItem.Size or optionItem[6]
    optionItem.FontInfo = optionItem.FontInfo or optionItem[7]
    optionItem.TextColor = optionItem.TextColor or optionItem[8]

  elseif (controlType == "StartGroupBox") then
    -- StartGroupBox: [6] Size (Optional), [7] AutoFitWidth (Optional), [8] AutoFitHeight (Optional), [9] FontInfo (Optional), [10] TextColor (Optional)
    optionItem.Size = optionItem.Size or optionItem[6]
    optionItem.AutoFitWidth = optionItem.AutoFitWidth or optionItem[7] -- init
    optionItem.AutoFitHeight = optionItem.AutoFitHeight or optionItem[8] -- init
    optionItem.FontInfo = optionItem.FontInfo or optionItem[9]
    optionItem.TextColor = optionItem.TextColor or optionItem[10]
    --
    -- Set the actual values of optionItem.AutoFitWidth and optionItem.AutoFitHeight.
    optionItem.AutoFitWidth, optionItem.AutoFitHeight = EasyOptions:GetAutoFitWidthAndHeightEffective(optionItem.Size, optionItem.AutoFitWidth, optionItem.AutoFitHeight)

  elseif (controlType == "EndGroupBox") then
    -- EndGroupBox: N/A
    
  elseif (controlType == "StartTabControl") then
    -- StartTabControl: [6] TabsOnBottom (Optional), [7] Size (Optional), [8] AutoFitWidth (Optional), [9] AutoFitHeight (Optional), [10] FontInfo (Optional), [11] TextColor (Optional)
    optionItem.DefaultValue = 1 -- Always use the first tab as the default tab.
    optionItem.TabsOnBottom = optionItem.TabsOnBottom or optionItem[6]
    optionItem.Size = optionItem.Size or optionItem[7]
    optionItem.AutoFitWidth = optionItem.AutoFitWidth or optionItem[8] -- init
    optionItem.AutoFitHeight = optionItem.AutoFitHeight or optionItem[9] -- init
    optionItem.FontInfo = optionItem.FontInfo or optionItem[10]
    optionItem.TextColor = optionItem.TextColor or optionItem[11]
    --
    -- Set the actual values of optionItem.AutoFitWidth and optionItem.AutoFitHeight.
    optionItem.AutoFitWidth, optionItem.AutoFitHeight = EasyOptions:GetAutoFitWidthAndHeightEffective(optionItem.Size, optionItem.AutoFitWidth, optionItem.AutoFitHeight)

  elseif (controlType == "EndTabControl") then
    -- EndTabControl: N/A

  elseif (controlType == "StartTab") then
    -- StartTab: [6] Width (Optional), [7] OnClickHandler (Optional)
    optionItem.Width = optionItem.Width or optionItem[6]
    optionItem.OnClickHandler = optionItem.OnClickHandler or optionItem[7]

  elseif (controlType == "EndTab") then
    -- EndTab: N/A
    
  elseif (controlType == "StartListView") then
    -- StartListView: [6] ListViewPanelWidth (Optional), [7] Size (Optional), [8] AutoFitWidth (Optional), [9] AutoFitHeight (Optional), [10] FontInfo (Optional), [11] TextColor (Optional)
    optionItem.DefaultValue = 1 -- Always use the first item as the default item.
    optionItem.ListViewPanelWidth = optionItem.ListViewPanelWidth or optionItem[6]
    optionItem.Size = optionItem.Size or optionItem[7]
    optionItem.AutoFitWidth = optionItem.AutoFitWidth or optionItem[8] -- init
    optionItem.AutoFitHeight = optionItem.AutoFitHeight or optionItem[9] -- init
    optionItem.FontInfo = optionItem.FontInfo or optionItem[10]
    optionItem.TextColor = optionItem.TextColor or optionItem[11]
    --
    -- Set the actual values of optionItem.AutoFitWidth and optionItem.AutoFitHeight.
    optionItem.AutoFitWidth, optionItem.AutoFitHeight = EasyOptions:GetAutoFitWidthAndHeightEffective(optionItem.Size, optionItem.AutoFitWidth, optionItem.AutoFitHeight)

  elseif (controlType == "EndListView") then
    -- EndListView: N/A

  elseif (controlType == "StartListViewItem") then
    -- StartListViewItem: [6] OnClickHandler (Optional)
    optionItem.OnClickHandler = optionItem.OnClickHandler or optionItem[6]

  elseif (controlType == "EndListViewItem") then
    -- EndListViewItem: N/A

  elseif (controlType == "Label") then
    -- Label: [6] Size (Optional), [7] WordWrap (Optional), [8] FontInfo (Optional), [9] TextColor (Optional), [10] IncludeUnderline (Optional), [11] UnderlineWidth (Optional)
    optionItem.Size = optionItem.Size or optionItem[6]
    optionItem.WordWrap = optionItem.WordWrap or optionItem[7]
    optionItem.FontInfo = optionItem.FontInfo or optionItem[8]
    optionItem.TextColor = optionItem.TextColor or optionItem[9]
    optionItem.IncludeUnderline = optionItem.IncludeUnderline or optionItem[10]
    optionItem.UnderlineWidth = optionItem.UnderlineWidth or optionItem[11]

  elseif (controlType == "Separator") then
    -- Separator: [6] Width (Optional)
    optionItem.Width = optionItem.Width or optionItem[6]

  elseif (controlType == "Button") or (controlType == "ReloadUIButton") then
    -- Button: [6] Size (Optional), [7] OnClickHandler
    optionItem.Size = optionItem.Size or optionItem[6]
    optionItem.OnClickHandler = optionItem.OnClickHandler or optionItem[7]

  elseif (controlType == "CheckBox") or (controlType == "StartCheckBoxGroup") then
    -- CheckBox: [6] DefaultValue, [7] FontInfo (Optional), [8] TextColor (Optional), [9] OnOptionValueChangedHandler (Optional), [10] OnClickHandler (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.FontInfo = optionItem.FontInfo or optionItem[7]
    optionItem.TextColor = optionItem.TextColor or optionItem[8]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[9]
    optionItem.OnClickHandler = optionItem.OnClickHandler or optionItem[10]

  elseif (controlType == "EndCheckBoxGroup") then
    -- EndCheckBoxGroup: N/A

  elseif (controlType == "EndAllCheckBoxGroups") then
    -- EndAllCheckBoxGroups: N/A

  elseif (controlType == "RadioButton") then
    -- RadioButton: [6] DefaultValue, [7] RadioButtonGroupName, [8] FontInfo (Optional), [9] TextColor (Optional), [10] OnOptionValueChangedHandler (Optional), [11] OnClickHandler (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.RadioButtonGroupName = optionItem.RadioButtonGroupName or optionItem[7]
    optionItem.FontInfo = optionItem.FontInfo or optionItem[8]
    optionItem.TextColor = optionItem.TextColor or optionItem[9]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[10]
    optionItem.OnClickHandler = optionItem.OnClickHandler or optionItem[11]

  elseif (controlType == "RadioButtonGroup") then
    -- RadioButtonGroup: [6] DefaultValue, [7] RadioButtonInfos, [8] AlignAsRow (Optional), [9] FontInfo (Optional), [10] TextColor (Optional), [11] OnOptionValueChangedHandler (Optional), [12] OnClickHandler (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.RadioButtonInfos = optionItem.RadioButtonInfos or optionItem[7]
    optionItem.AlignAsRow = optionItem.AlignAsRow or optionItem[8]
    optionItem.FontInfo = optionItem.FontInfo or optionItem[9]
    optionItem.TextColor = optionItem.TextColor or optionItem[10]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[11]
    optionItem.OnClickHandler = optionItem.OnClickHandler or optionItem[12]

  elseif (controlType == "DropDown") then
    -- DropDown: [6] DefaultValue, [7] DropDownItemInfos, [8] Width (Optional), [9] FontInfo (Optional), [10] TextColor (Optional), [11] OnOptionValueChangedHandler (Optional), [12] OnClickHandler (Optional), [13] OnValueChangedHandler (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.DropDownItemInfos = optionItem.DropDownItemInfos or optionItem[7]
    optionItem.Width = optionItem.Width or optionItem[8]
    optionItem.FontInfo = optionItem.FontInfo or optionItem[9]
    optionItem.TextColor = optionItem.TextColor or optionItem[10]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[11]
    optionItem.OnClickHandler = optionItem.OnClickHandler or optionItem[12]
    optionItem.OnValueChangedHandler = optionItem.OnValueChangedHandler or optionItem[13]
    
  elseif (controlType == "EditableDropDown") then
    -- EditableDropDown: [6] DefaultValue, [7] DropDownItemInfos, [8] Width (Optional), [9] ShowTextBox (Optional), [10] TextBoxWidth (Optional), [11] FontInfo (Optional), [12] TextColor (Optional), [13] TextBoxTooltipText (Optional), [14] TestButtonText (Optional), [15] TestButtonTooltipText (Optional), [16] OnTestHandler (Optional), [17] OnOptionValueChangedHandler (Optional), [18] OnClickHandler (Optional), [19] OnValueChangedHandler (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.DropDownItemInfos = optionItem.DropDownItemInfos or optionItem[7]
    optionItem.Width = optionItem.Width or optionItem[8]
    optionItem.ShowTextBox = optionItem.ShowTextBox or optionItem[9]
    optionItem.TextBoxWidth = optionItem.TextBoxWidth or optionItem[10]
    optionItem.FontInfo = optionItem.FontInfo or optionItem[11]
    optionItem.TextColor = optionItem.TextColor or optionItem[12]
    optionItem.TextBoxTooltipText = optionItem.TextBoxTooltipText or optionItem[13]
    optionItem.TestButtonText = optionItem.TestButtonText or optionItem[14]
    optionItem.TestButtonTooltipText = optionItem.TestButtonTooltipText or optionItem[15]
    optionItem.OnTestHandler = optionItem.OnTestHandler or optionItem[16]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[17]
    optionItem.OnClickHandler = optionItem.OnClickHandler or optionItem[18]
    optionItem.OnValueChangedHandler = optionItem.OnValueChangedHandler or optionItem[19]

  elseif (controlType == "TextBox") then
    -- TextBox: [6] DefaultValue, [7] Width (Optional), [8] LabelFontInfo (Optional), [9] LabelTextColor (Optional), [10] DelimiterInfo (Optional), [11] OnOptionValueChangedHandler (Optional), [12] OnTextAcceptedHandler (Optional), [13] OnTextChangedHandler (Optional)
    optionItem.DefaultValue =  optionItem.DefaultValue or optionItem[6]
    optionItem.Width = optionItem.Width or optionItem[7]
    optionItem.LabelFontInfo = optionItem.LabelFontInfo or optionItem[8]
    optionItem.LabelTextColor = optionItem.LabelTextColor or optionItem[9]
    optionItem.DelimiterInfo = optionItem.DelimiterInfo or optionItem[10]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[11]
    optionItem.OnTextAcceptedHandler = optionItem.OnTextAcceptedHandler or optionItem[12]
    optionItem.OnTextChangedHandler = optionItem.OnTextChangedHandler or optionItem[13]
    --
    optionItem.DefaultValue = EasyOptions:ConvertTextBoxValueToAppropriateNumberOfDimensions(optionItem.DefaultValue, controlType, optionItem.DelimiterInfo, true) -- IMPORTANT: We must call EasyOptions:ConvertTextBoxValueToAppropriateNumberOfDimensions after optionItem has been initialized.
    
  elseif (controlType == "ValueBox") then
    -- ValueBox: [6] DefaultValue, [7] MinValue (Optional), [8] MaxValue (Optional), [9] FormatString (Optional), [10] Width (Optional), [11] LabelFontInfo (Optional), [12] LabelTextColor (Optional), [13] OnOptionValueChangedHandler (Optional), [14] OnTextAcceptedHandler (Optional), [15] OnTextChangedHandler (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.MinValue = optionItem.MinValue or optionItem[7]
    optionItem.MaxValue = optionItem.MaxValue or optionItem[8]
    optionItem.FormatString = optionItem.FormatString or optionItem[9]
    optionItem.Width = optionItem.Width or optionItem[10]
    optionItem.LabelFontInfo = optionItem.LabelFontInfo or optionItem[11]
    optionItem.LabelTextColor = optionItem.LabelTextColor or optionItem[12]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[13]
    optionItem.OnTextAcceptedHandler = optionItem.OnTextAcceptedHandler or optionItem[14]
    optionItem.OnTextChangedHandler = optionItem.OnTextChangedHandler or optionItem[15]

  elseif (controlType == "MultiLineTextBox") then
    -- MultiLineTextBox: [6] DefaultValue, [7] Size (Optional), [8] LabelFontInfo (Optional), [9] LabelTextColor (Optional), [10] DelimiterInfo (Optional), [11] OnOptionValueChangedHandler (Optional), [12] OnTextAcceptedHandler (Optional), [13] OnTextChangedHandler (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.Size = optionItem.Size or optionItem[7]
    optionItem.LabelFontInfo = optionItem.LabelFontInfo or optionItem[8]
    optionItem.LabelTextColor = optionItem.LabelTextColor or optionItem[9]
    optionItem.DelimiterInfo = optionItem.DelimiterInfo or optionItem[10]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[11]
    optionItem.OnTextAcceptedHandler = optionItem.OnTextAcceptedHandler or optionItem[12]
    optionItem.OnTextChangedHandler = optionItem.OnTextChangedHandler or optionItem[13]
    --
    optionItem.DefaultValue = EasyOptions:ConvertTextBoxValueToAppropriateNumberOfDimensions(optionItem.DefaultValue, controlType, optionItem.DelimiterInfo, true) -- IMPORTANT: We must call EasyOptions:ConvertTextBoxValueToAppropriateNumberOfDimensions after optionItem has been initialized.

  elseif (controlType == "Slider") or (controlType == "FontSize") then
    -- Slider: [6] DefaultValue, [7] MinValue, [8] MaxValue, [9] ValueStep, [10] FormatString (Optional), [11] LabelPositionInfo (Optional), [12] Width (Optional), [13] ValueBoxWidth (Optional), [14] ValueBoxPositionInfo (Optional), [15] LabelFontInfo (Optional), [16] LabelTextColor (Optional), [17] OnOptionValueChangedHandler (Optional), [18] OnValueChangedHandler (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.MinValue = optionItem.MinValue or optionItem[7]
    optionItem.MaxValue = optionItem.MaxValue or optionItem[8]
    optionItem.ValueStep = optionItem.ValueStep or optionItem[9]
    optionItem.FormatString = optionItem.FormatString or optionItem[10]
    optionItem.LabelPositionInfo = optionItem.LabelPositionInfo or optionItem[11]
    optionItem.Width = optionItem.Width or optionItem[12]
    optionItem.ValueBoxWidth = optionItem.ValueBoxWidth or optionItem[13]
    optionItem.ValueBoxPositionInfo = optionItem.ValueBoxPositionInfo or optionItem[14]
    optionItem.LabelFontInfo = optionItem.LabelFontInfo or optionItem[15]
    optionItem.LabelTextColor = optionItem.LabelTextColor or optionItem[16]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[17]
    optionItem.OnValueChangedHandler = optionItem.OnValueChangedHandler or optionItem[18]
    --
    if (controlType == "FontSize") then
      optionItem.DefaultValue = optionItem.DefaultValue or EasyOptions.Fields.DefaultFontSize
    end

  elseif (controlType == "ColorPicker") then
    -- ColorPicker: [6] DefaultValue, [7] Size (Optional), [8] BorderWidth (Optional), [9] LabelPositionInfo (Optional), [10] FontInfo (Optional), [11] TextColor (Optional), [12] OnOptionValueChangedHandler (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.Size = optionItem.Size or optionItem[7]
    optionItem.BorderWidth = optionItem.BorderWidth or optionItem[8]
    optionItem.LabelPositionInfo = optionItem.LabelPositionInfo or optionItem[9]
    optionItem.FontInfo = optionItem.FontInfo or optionItem[10]
    optionItem.TextColor = optionItem.TextColor or optionItem[11]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[12]
    
  elseif (controlType == "SoundChannel") then
    -- SoundChannel: [6] DefaultValue
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6] or EasyOptions.Fields.DefaultSoundChannel
    
  elseif (controlType == "SoundFilePath") then
    -- SoundFilePath: [6] DefaultValue, [7] DropDownItemInfos (Optional), [8] IncludeDefaultItemInfos (Optional), [9] GetSoundChannel (Optional), [10] DropDownWidth (Optional), [11] ShowTextBox (Optional), [12] TextBoxWidth (Optional), [13] OnOptionValueChangedHandler (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.DropDownItemInfos = optionItem.DropDownItemInfos or optionItem[7]
    optionItem.IncludeDefaultItemInfos = optionItem.IncludeDefaultItemInfos or optionItem[8]
    optionItem.GetSoundChannel = optionItem.GetSoundChannel or optionItem[9]
    optionItem.DropDownWidth = optionItem.DropDownWidth or optionItem[10]
    optionItem.ShowTextBox = optionItem.ShowTextBox or optionItem[11]
    optionItem.TextBoxWidth = optionItem.TextBoxWidth or optionItem[12]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[13]

  elseif (controlType == "FontFilePath") then
    -- FontFilePath: [6] DefaultValue (Optional), [7] DropDownItemInfos (Optional), [8] IncludeDefaultItemInfos (Optional), [9] DropDownWidth (Optional), [10] ShowTextBox (Optional), [11] TextBoxWidth (Optional), [12] OnOptionValueChangedHandler (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6] or EasyOptions.Fields.DefaultFontFilePath
    optionItem.DropDownItemInfos = optionItem.DropDownItemInfos or optionItem[7]
    optionItem.IncludeDefaultItemInfos = optionItem.IncludeDefaultItemInfos or optionItem[8]
    optionItem.DropDownWidth = optionItem.DropDownWidth or optionItem[9]
    optionItem.ShowTextBox = optionItem.ShowTextBox or optionItem[10]
    optionItem.TextBoxWidth = optionItem.TextBoxWidth or optionItem[11]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[12]
    
  elseif (controlType == "ShowMinimapIcon") then
    -- ShowMinimapIcon: [6] DefaultValue, [7] MinimapIconFilePath, [8] MinimapIconText (Optional), [9] MinimapIconTooltipText (Optional), [10] OnMinimapIconClickHandler (Optional), [11] FontInfo (Optional), [12] TextColor (Optional), [13] OnOptionValueChangedHandler (Optional), [14] OnClickHandler (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.MinimapIconFilePath = optionItem.MinimapIconFilePath or optionItem[7]
    optionItem.MinimapIconText = optionItem.MinimapIconText or optionItem[8] or optionItem.AddonName
    optionItem.MinimapIconTooltipText = optionItem.MinimapIconTooltipText or optionItem[9]
    optionItem.OnMinimapIconClickHandler = optionItem.OnMinimapIconClickHandler or optionItem[10]
    optionItem.FontInfo = optionItem.FontInfo or optionItem[11]
    optionItem.TextColor = optionItem.TextColor or optionItem[12]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[13]
    optionItem.OnClickHandler = optionItem.OnClickHandler or optionItem[14]
    --
    if (optionItem.OnMinimapIconClickHandler == nil) then
      local optionItemLocal = optionItem
      optionItem.OnMinimapIconClickHandler = function(self, button)
        if (button == "LeftButton") then
          EasyOptions:ShowOptions(optionItemLocal.AddonName)
        elseif (button == "RightButton") then
          EasyOptions:ShowAddonManager(optionItemLocal.AddonName)
        end
      end
      optionItem.MinimapIconTooltipText = optionItem.MinimapIconTooltipText or EasyOptions:GetDefaultMinimapIconTooltipText(optionItemLocal.AddonName)
    end

  elseif (controlType == "FrameVisible") then
    -- FrameVisible: [6] DefaultValue, [7] Frame
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.Frame = EasyOptions:GetFrameOrPrintErrorMessageWithOptionItemInfoIfIsInvalidFrame("Frame", optionItem.Frame or optionItem[7], false)
    
  elseif (controlType == "FrameLocked") then
    -- FrameLocked: [6] DefaultValue, [7] Frame
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.Frame = EasyOptions:GetFrameOrPrintErrorMessageWithOptionItemInfoIfIsInvalidFrame("Frame", optionItem.Frame or optionItem[7], false)
    
  elseif (controlType == "FrameClampedToScreen") then
    -- FrameClampedToScreen: [6] DefaultValue, [7] Frame
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.Frame = EasyOptions:GetFrameOrPrintErrorMessageWithOptionItemInfoIfIsInvalidFrame("Frame", optionItem.Frame or optionItem[7], false)
    
  elseif (controlType == "FrameAnchoredToMouse") then
    -- FrameAnchoredToMouse: [6] DefaultValue, [7] Frame
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.Frame = EasyOptions:GetFrameOrPrintErrorMessageWithOptionItemInfoIfIsInvalidFrame("Frame", optionItem.Frame or optionItem[7], false)
    
  elseif (controlType == "FramePosition") then
    -- FramePosition: [6] DefaultValue (As FramePositionInfo), [7] Frame, [8] ParentFrame (Optional), [9] ShowParentAnchorPoint (Optional), [10] AlignOnSingleRow (Optional), [11] MinOffsetX (Optional), [12] MaxOffsetX (Optional), [13] MinOffsetY (Optional), [14] MaxOffsetY (Optional), [15] OnOptionValueChangedHandler (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.Frame = EasyOptions:GetFrameOrPrintErrorMessageWithOptionItemInfoIfIsInvalidFrame("Frame", optionItem.Frame or optionItem[7], false)
    optionItem.ParentFrame = EasyOptions:GetFrameOrPrintErrorMessageWithOptionItemInfoIfIsInvalidFrame("ParentFrame", optionItem.ParentFrame or optionItem[8], true)
    optionItem.ShowParentAnchorPoint = optionItem.ShowParentAnchorPoint or optionItem[9]
    optionItem.AlignOnSingleRow = optionItem.AlignOnSingleRow or optionItem[10]
    optionItem.MinOffsetX = optionItem.MinOffsetX or optionItem[11]
    optionItem.MaxOffsetX = optionItem.MaxOffsetX or optionItem[12]
    optionItem.MinOffsetY = optionItem.MinOffsetY or optionItem[13]
    optionItem.MaxOffsetY = optionItem.MaxOffsetY or optionItem[14]
    optionItem.OnOptionValueChangedHandler = optionItem.OnOptionValueChangedHandler or optionItem[15]

  elseif (controlType == "FrameSize") or (controlType == "FrameWidth") or (controlType == "FrameHeight") then
    -- FrameSize: [6] DefaultValue, [7] Frame, [8] MinValue (Optional), [9] MaxValue (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.Frame = EasyOptions:GetFrameOrPrintErrorMessageWithOptionItemInfoIfIsInvalidFrame("Frame", optionItem.Frame or optionItem[7], false)
    optionItem.MinValue = optionItem.MinValue or optionItem[8]
    optionItem.MaxValue = optionItem.MaxValue or optionItem[9]

  elseif (controlType == "FrameScale") then
    -- FrameScale: [6] DefaultValue, [7] Frame, [8] MinValue (Optional), [9] MaxValue (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.Frame = EasyOptions:GetFrameOrPrintErrorMessageWithOptionItemInfoIfIsInvalidFrame("Frame", optionItem.Frame or optionItem[7], false)
    optionItem.MinValue = optionItem.MinValue or optionItem[8]
    optionItem.MaxValue = optionItem.MaxValue or optionItem[9]

  elseif (controlType == "FrameStrata") then
    -- FrameStrata: [6] DefaultValue, [7] Frame
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6] or "MEDIUM"
    optionItem.Frame = EasyOptions:GetFrameOrPrintErrorMessageWithOptionItemInfoIfIsInvalidFrame("Frame", optionItem.Frame or optionItem[7], false)
    
  elseif (controlType == "FrameLevel") then
    -- FrameLevel: [6] DefaultValue, [7] Frame, [8] MinValue (Optional), [9] MaxValue (Optional)
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6] or 0
    optionItem.Frame = EasyOptions:GetFrameOrPrintErrorMessageWithOptionItemInfoIfIsInvalidFrame("Frame", optionItem.Frame or optionItem[7], false)
    optionItem.MinValue = optionItem.MinValue or optionItem[8]
    optionItem.MaxValue = optionItem.MaxValue or optionItem[9]
    
  elseif (controlType == "FrameBackgroundColor") then
    -- FrameBackgroundColor: [6] DefaultValue, [7] Frame
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.Frame = EasyOptions:GetFrameOrPrintErrorMessageWithOptionItemInfoIfIsInvalidFrame("Frame", optionItem.Frame or optionItem[7], false)
    
  elseif (controlType == "CustomControl") then
    -- CustomControl: [6] DefaultValue, [7] GetCustomControl
    optionItem.DefaultValue = optionItem.DefaultValue or optionItem[6]
    optionItem.GetCustomControl = optionItem.GetCustomControl or optionItem[7]
    
  elseif (controlType == "StartRow") then
    -- StartRow: [6] IndexOfItemToUseAsRowHeight (Optional), [7] Height (Optional)
    optionItem.IndexOfItemToUseAsRowHeight = optionItem.IndexOfItemToUseAsRowHeight or optionItem[6]
    optionItem.Height = optionItem.Height or optionItem[7]

  elseif (controlType == "EndRow") then
    -- EndRow: N/A

  elseif (controlType == "SetPosition") then
    -- SetPosition: N/A

  elseif (controlType == "MovePosition") then
    -- MovePosition: N/A
    
  elseif (controlType == "SavePosition") then
    -- SavePosition: [6] Key (String, Number, or nil)
    optionItem.Key = optionItem.Key or optionItem[6]

  elseif (controlType == "RestorePosition") then
    -- RestorePosition: [6] Key (String, Number, or nil)
    optionItem.Key = optionItem.Key or optionItem[6]

  elseif (controlType == "DisableAutoCheckBoxGrouping") then
    -- DisableAutoCheckBoxGrouping: N/A

  elseif (controlType == "EnableAutoCheckBoxGrouping") then
    -- EnableAutoCheckBoxGrouping: N/A

  else
    EasyOptions:PrintErrorMessageOnce(L("Unable to set properties of one or more of the option items because \"{1}\" is an unknown control type.", EasyOptions:FormatName(controlType)))
  end
end
function EasyOptions:SetOptionItemsProperties(optionItems, addonName, addonDB, includeChildren)
  if (optionItems == nil) then
    return
  end
  local length = EasyOptions:GetLength(optionItems)
  for i = 1, length do
    EasyOptions:SetOptionItemProperties(optionItems[i], optionItems, addonName, addonDB)
  end

  if (includeChildren) then
    local childrenOptionItems = optionItems.Children
    local childrenLength = EasyOptions:GetLength(childrenOptionItems)
    for i = 1, childrenLength do
      EasyOptions:SetOptionItemsProperties(childrenOptionItems[i], addonName, addonDB, includeChildren)
    end
  end
end

function EasyOptions:CreateOptionFrame(optionItems, parentPanel, optionPanelFrames)
  -- Validate optionItems.
  if (optionItems == nil) then
    return
  end

  -- Indicate that the Option frame is being built.
  EasyOptions.Current.IsBuildingOptionFrame = true

  -- Reset fields.
  EasyOptions.Current.OptionItemIndex = 0
  EasyOptions.Current.OptionItem = nil
  EasyOptions.Current.OptionItems = optionItems
  EasyOptions.Fields.RadioButtonDefaultGroupIndex = 0

  -- Determine parameters.
  local isParentFrame = (optionItems.Parent == nil)
  local childrenLength = EasyOptions:GetLength(optionItems.Children)
  local hasChildren = (childrenLength >= 1)
  --
  local marginX = EasyOptions:GetDefaultControlMarginX()
  local marginY = EasyOptions:GetDefaultControlMarginY()
  local paddingX = EasyOptions:GetDefaultControlPaddingX()
  local paddingY = EasyOptions:GetDefaultControlPaddingY()
  local optionPanelSize = EasyOptions:GetOptionPanelSize()

  -- Create the frame that stores all of the UI elements for the specified option category.
  local categoryName = optionItems.Name or "" -- or EasyOptions.Current.AddonName
  local frameName
  if (parentPanel == nil) or (parentPanel:GetName() == nil) or (parentPanel:GetName() == "") then
    frameName = EasyOptions.Current.AddonName.."Options" -- "Options_"..categoryName
  else
    frameName = parentPanel:GetName().."."..categoryName -- NOTE: parentPanel:GetName() ~= parentPanel.name.
  end

  local optionPanelFrame = EasyOptions:CreateContainerFrame(frameName, nil, false, false, true) -- EasyOptions:CreateContainerFrame(frameName, parentPanel, false, false, true)
  optionPanelFrame.name = categoryName
  optionPanelFrame.AddonName = EasyOptions.Current.AddonName
  optionPanelFrame.CategoryName = categoryName
  optionPanelFrame.OptionItems = optionItems
  if (parentPanel ~= nil) then
    optionPanelFrame.parent = parentPanel.name
  end
  --
  optionPanelFrame:Hide() -- Hide the frame initially to ensure that the frame's OnShow event will fire the first time that the frame is shown.
  optionPanelFrame:SetScript("OnShow", function(self)
    EasyOptions:OnOptionPanelFrameShowHandler(self)
  end)
  optionPanelFrame:SetScript("OnHide", function(self)
    EasyOptions:OnOptionPanelFrameHideHandler(self)
  end)
  --
  EasyOptions:CreateOptionFrame_AddToOptionPanelFrames(categoryName, optionPanelFrame, optionPanelFrames)
  if (EasyOptions:UseLegacyOptionsMode()) then
    InterfaceOptions_AddCategory(optionPanelFrame)
  else
    local parentCategory
    local category, layout
    if (isParentFrame) then
      parentCategory = nil
	    category, layout = Settings.RegisterCanvasLayoutCategory(optionPanelFrame, optionPanelFrame.name)
    else
      parentCategory = optionItems.Parent.OptionsCategory
	    category, layout = Settings.RegisterCanvasLayoutSubcategory(parentCategory, optionPanelFrame, optionPanelFrame.name)
    end
	  Settings.RegisterAddOnCategory(category)
	  -- Settings.RegisterCategory(category)
	  optionItems.OptionsParentCategory = parentCategory
	  optionItems.OptionsCategory = category
	  optionItems.OptionsCategoryID = category:GetID()
	  optionPanelFrame.OptionsParentCategory = optionItems.OptionsParentCategory
	  optionPanelFrame.OptionsCategory = optionItems.OptionsCategory
	  optionPanelFrame.OptionsCategoryID = optionItems.OptionsCategoryID
    -- EasyOptions:PrintItems({"CreateOptionFrame", EasyOptions.Current.AddonName, categoryName, optionPanelFrame:GetName(), optionPanelFrame.OptionsCategoryID})
  end

  -- Add the title label, version label, reset buttons, and manage button to the title region of optionPanelFrame.
  local titleLabel, subTitleLabel
  if (isParentFrame) then
    titleLabel, subTitleLabel = EasyOptions:AddOptionsCategoryTitleLabels(optionPanelFrame, nil, categoryName, EasyOptions:GetAddonVersion(EasyOptions.Current.AddonName, false))
  else
    titleLabel, subTitleLabel = EasyOptions:AddOptionsCategoryTitleLabels(optionPanelFrame, nil, categoryName, nil)
  end
  local firstOptionsButton = EasyOptions:AddOptionsButtons(optionPanelFrame, EasyOptions.Current.AddonName, optionItems, isParentFrame, hasChildren)
  EasyOptions:AddBuiltWithLabel(optionPanelFrame, EasyOptions.Current.AddonName, isParentFrame)
  if (titleLabel ~= nil) and (firstOptionsButton ~= nil) then
    titleLabel:SetPoint("RIGHT", firstOptionsButton, "LEFT", 0, 0) -- Anchor the right side of the title label to the left side of the first reset button so the title label's text cannot overlap the reset button.
  end

  -- Create the panel that implements a scrollbar and contains the UI elements for the option items.
  local scrollPanelName = EasyOptions:GetControlName("ScrollPanel", nil)
  local scrollPanel = EasyOptions:CreateContainerFrame(scrollPanelName, optionPanelFrame, true, true, true)
  local scrollPanelTop = EasyOptions:IIF(isParentFrame, -30, -8) - EasyOptions.Fields.OptionsFrameMarginTop - 5 -- - paddingY
  scrollPanel:SetPoint("TOPLEFT", optionPanelFrame, "TOPLEFT", EasyOptions.Fields.OptionsFrameMarginLeft - 16, scrollPanelTop + 0) -- "LEFT", "TOP", "RIGHT", "BOTTOM", "TOPLEFT", "TOPRIGHT", "BOTTOMLEFT", "BOTTOMRIGHT", "CENTER"
  scrollPanel:SetPoint("BOTTOMRIGHT", optionPanelFrame, "BOTTOMRIGHT", -EasyOptions.Fields.OptionsFrameMarginRight - 16, EasyOptions.Fields.OptionsFrameMarginBottom - 3) -- "LEFT", "TOP", "RIGHT", "BOTTOM", "TOPLEFT", "TOPRIGHT", "BOTTOMLEFT", "BOTTOMRIGHT", "CENTER"
  -- scrollPanel:SetSize(600 - paddingX, 528 - paddingY)
  --
  local scrollAreaPanel = CreateFrame("ScrollFrame", frameName..".ScrollArea", scrollPanel, "UIPanelScrollFrameTemplate")
  scrollAreaPanel:SetPoint("TOPLEFT", scrollPanel, "TOPLEFT", 0, -EasyOptions.Fields.ScrollBarVerticalMargin) -- paddingX
  scrollAreaPanel:SetPoint("BOTTOMRIGHT", scrollPanel, "BOTTOMRIGHT", -5, EasyOptions.Fields.ScrollBarVerticalMargin) -- -paddingX -- NOTE: The x-offset specifies how far the vertical scroll bar is from the right edge of the frame, which we want to be independent of paddingX.
  --
  scrollAreaPanel.Child = CreateFrame("Frame", frameName..".ScrollAreaChild", scrollAreaPanel, "BackdropTemplateMixin" and "BackdropTemplate")
  scrollAreaPanel:SetScrollChild(scrollAreaPanel.Child)
  scrollAreaPanel.Child:SetPoint("TOPLEFT", scrollAreaPanel, "TOPLEFT", 0, 0) -- "LEFT", "TOP", "RIGHT", "BOTTOM", "TOPLEFT", "TOPRIGHT", "BOTTOMLEFT", "BOTTOMRIGHT", "CENTER"
  scrollAreaPanel.Child:SetSize(optionPanelSize[1], optionPanelSize[2] - 2) -- IMPORTNAT: This size effectively determines the position of the right and bottom edges of the frame that contains the option item controls. The dimensions of this size must be > 0. We include an offset to the height to prevent a bug that causes the scrollbar to be able to scroll a small amount even if there are no controls near the bottom of the option frame. Also, this size determines the minimum range over which the scrollbar will scroll so if this size is larger than the size of scrollAreaPanel then the user will be able to scroll the scrollbar even if scrollAreaPanel is able to contain all of the controls in scrollAreaPanel.Child (i.e., the range of the scrollbar is math.max(scrollAreaPanel.Child:GetHeight(), [Maximum bottom position of all controls in scrollAreaPanel.Child])).
  
  -- Determine parameters that specify the active mode.
  local activeMode_Standard = "Standard"
  local activeMode_GroupBox = "GroupBox"
  local activeMode_TabControl = "TabControl"
  local activeMode_Tab = "Tab"
  local activeMode_ListView = "ListView"
  local activeMode_ListViewItem = "ListViewItem"
  local activeMode_Row = "Row"
  local activeModes_Container = {activeMode_GroupBox, activeMode_TabControl, activeMode_Tab, activeMode_ListView, activeMode_ListViewItem} -- Includes all of the active modes that correspond to a container control type.
  local activeModeOfControlType = {
    ["StartGroupBox"] = activeMode_GroupBox, 
    ["EndGroupBox"] = activeMode_GroupBox, 
    ["StartTabControl"] = activeMode_TabControl, 
    ["EndTabControl"] = activeMode_TabControl, 
    ["StartTab"] = activeMode_Tab, 
    ["EndTab"] = activeMode_Tab, 
    ["StartListView"] = activeMode_ListView, 
    ["EndListView"] = activeMode_ListView, 
    ["StartListViewItem"] = activeMode_ListViewItem, 
    ["EndListViewItem"] = activeMode_ListViewItem, 
    ["StartRow"] = activeMode_Row, 
    ["EndRow"] = activeMode_Row, 
    }
  local activeModeToStartControlType = {
    [activeMode_GroupBox] = "StartGroupBox", 
    [activeMode_TabControl] = "StartTabControl", 
    [activeMode_Tab] = "StartTab", 
    [activeMode_ListView] = "StartListView", 
    [activeMode_ListViewItem] = "StartListViewItem", 
    [activeMode_Row] = "StartRow", 
    }
  local activeModeToEndControlType = {
    [activeMode_GroupBox] = "EndGroupBox", 
    [activeMode_TabControl] = "EndTabControl", 
    [activeMode_Tab] = "EndTab", 
    [activeMode_ListView] = "EndListView", 
    [activeMode_ListViewItem] = "EndListViewItem", 
    [activeMode_Row] = "EndRow", 
    }
  local activeMode = activeMode_Standard -- init
  local iActiveMode = 0
  local activeModeInfo = {} -- [1] = Mode (i.e., "Standard", "GroupBox", "TabControl", "Tab", "ListView", "ListViewItem", or "Row")
  local ContainsContainerActiveMode = function(activeModeInfo)
    return EasyOptions:ContainsValue(activeModeInfo, activeModes_Container, true)
  end
  local ValidateControlTypeForActiveMode = function(controlType, activeMode)
    if (activeMode == activeMode_TabControl) then
      if (controlType ~= "EndTabControl") and (controlType ~= "StartTab") then
        EasyOptions:PrintErrorMessageWithOptionItemInfo(L("has a ControlType of {1} which is not a valid ControlType to specify while the active mode is set to {2}. Include a ControlType of \"{3}\" or \"{4}\" before this option item.", controlType, activeMode, "EndTabControl", "StartTab"))
      end
    end
    if (controlType == "StartTab") then
      if (activeMode ~= activeMode_TabControl) then
        EasyOptions:PrintErrorMessageWithOptionItemInfo(L("has a ControlType of {1}, but there is no active TabControl. Include a ControlType of \"{2}\" before this option item.", controlType, "StartTabControl"))
      end
    end
    if (activeMode == activeMode_ListView) then
      if (controlType ~= "EndListView") and (controlType ~= "StartListViewItem") then
        EasyOptions:PrintErrorMessageWithOptionItemInfo(L("has a ControlType of {1} which is not a valid ControlType to specify while the active mode is set to {2}. Include a ControlType of \"{3}\" or \"{4}\" before this option item.", controlType, activeMode, "EndListView", "StartListViewItem"))
      end
    end
    if (controlType == "StartListViewItem") then
      if (activeMode ~= activeMode_ListView) then
        EasyOptions:PrintErrorMessageWithOptionItemInfo(L("has a ControlType of {1}, but there is no active ListView. Include a ControlType of \"{2}\" before this option item.", controlType, "StartListView"))
      end
    end
  end

  -- Add the UI elements for the option items to scrollAreaPanel.Child.
  local length = EasyOptions:GetLength(optionItems)
  local optionItem
  local parentControlInitial = scrollAreaPanel.Child
  local parentControl = parentControlInitial -- Stores the control that acts as the parent of newly created controls.
  local positionInfoInitial = EasyOptions:GetPositionInfoOfColumn1Start()
  local positionInfoInitialLocal -- Stores a local initial position.
  local positionInfoCurrent = EasyOptions:CopyTable(positionInfoInitial)
  local positionInfo
  local indentCountCurrent = 0 -- Stores the current indent count.
  local useIndentCountCurrentIfIsBuildingRow = true
  local savedPositionInfos = {} -- [1] = PositionInfo, [2] = IndentCountCurrent
  local key
  local defaultKey = ""
  local shouldReplacePositionInfoCurrent
  local iContainerInfo = 0 -- Stores the index of the current item in containerInfo.
  local containerInfo = {} -- ContainerControl, ParentControlPrevious, IndentCountCurrentPrevious, PositionInfoCurrentPrevious, PositionInfoInitialWithinContainerControl, ControlRegionWidth, ControlRegionHeight, CustomSize, AutoFitWidth, AutoFitHeight, ControlsInContainerControl
  local isAddingControlsToContainer
  local iRowInfo = 0 -- Stores the index of the current item in rowInfo.
  local rowInfo = {} -- PositionInfoPrevious, ControlRegionWidth, ControlRegionHeight, IndexOfItemToUseAsRowHeight, CustomHeight, ControlsInRow
  local isBuildingRow
  local enableAutoCheckBoxGrouping = true
  local iCheckBoxGroupInfo = 0 -- Stores the index of the current item in checkBoxGroupInfo.
  local checkBoxGroupInfo = {} -- [1] CheckBox, [2] IndentCountOfCheckBox, [3] IsManualCheckBoxGroup, [4] GroupBoxCount, [5] RowCount
  local sign
  local positionIncrementOffsetX
  local positionIncrementOffsetY
  local shouldIncrementPositionX
  local shouldIncrementPositionY
  local control
  local controls = {}
  local controlType
  local controlRegionWidth -- Specifies the width of the region that is associated with the current control (not necessarily the actual width of the control).
  local controlRegionHeight -- Specifies the height of the region that is associated with the current control (not necessarily the actual height of the control).
  local hasWidth -- Not used.
  local hasHeight -- Not used.
  local shouldFillWidth -- Not used.
  local isBlankLine
  for i = 1, length do
    optionItem = optionItems[i] -- [1] Name, [2] Text, [3] TooltipText, [4] PositionInfo, [5] Type, [6+] [Type Specific]
    EasyOptions.Current.OptionItemIndex = i
    EasyOptions.Current.OptionItem = optionItem
    --
    controlType = optionItem.ControlType
    isBlankLine = optionItem.IsBlankLine
    isAddingControlsToContainer = ContainsContainerActiveMode(activeModeInfo)
    isBuildingRow = (activeMode == activeMode_Row)
    if (isBlankLine) then -- NOTE: If isBlankLine is true then we assume that we only want to increment y (i.e., positionInfoCurrent[4]) in order to increase (or possibly decrease) the space between the previous control and the next control.
      local scaleFactor_BlankLine = optionItem.BlankLineCount
      control = nil
      controlRegionWidth = scaleFactor_BlankLine * paddingX
      controlRegionHeight = scaleFactor_BlankLine * EasyOptions:GetBlankLineRegionHeight()
      shouldReplacePositionInfoCurrent = false
      positionIncrementOffsetX = 0
      positionIncrementOffsetY = 0
      shouldIncrementPositionX = isBuildingRow
      shouldIncrementPositionY = (not isBuildingRow)
    else
      positionInfo, shouldReplacePositionInfoCurrent, positionIncrementOffsetX, positionIncrementOffsetY, shouldIncrementPositionX, shouldIncrementPositionY = EasyOptions:GetPositionInfoFromOptionItem(positionInfoCurrent, optionItem, isBuildingRow)
      indentCountCurrent = EasyOptions:GetIndentCountFromOptionItem(indentCountCurrent, optionItem, isBuildingRow, useIndentCountCurrentIfIsBuildingRow)
      ValidateControlTypeForActiveMode(controlType, activeMode) -- Print an error message if controlType is incompatible with activeMode.
      if (controlType == "Hidden") then
        control, controlRegionWidth, controlRegionHeight = nil, 0, 0
      elseif (controlType == "GroupTitle") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionGroupTitle(parentControl, positionInfo, optionItem)
      elseif (controlType == "GroupBox") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionGroupBox(parentControl, positionInfo, optionItem)
      elseif (controlType == "Label") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionLabel(parentControl, positionInfo, optionItem)
      elseif (controlType == "Separator") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionSeparator(parentControl, positionInfo, optionItem)
      elseif (controlType == "Button") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionButton(parentControl, positionInfo, optionItem)
      elseif (controlType == "ReloadUIButton") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionReloadUIButton(parentControl, positionInfo, optionItem)
      elseif (controlType == "CheckBox") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionCheckBox(parentControl, positionInfo, optionItem)
      elseif (controlType == "StartCheckBoxGroup") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionCheckBox(parentControl, positionInfo, optionItem)
      elseif (controlType == "EndCheckBoxGroup") then
        control, controlRegionWidth, controlRegionHeight = nil, 0, 0
      elseif (controlType == "EndAllCheckBoxGroups") then
        control, controlRegionWidth, controlRegionHeight = nil, 0, 0
      elseif (controlType == "RadioButton") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionRadioButton(parentControl, positionInfo, optionItem)
      elseif (controlType == "RadioButtonGroup") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionRadioButtonGroup(parentControl, positionInfo, optionItem)
      elseif (controlType == "DropDown") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionDropDown(parentControl, positionInfo, optionItem)
      elseif (controlType == "EditableDropDown") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionEditableDropDown(parentControl, positionInfo, optionItem)
      elseif (controlType == "TextBox") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionTextBox(parentControl, positionInfo, optionItem)
      elseif (controlType == "ValueBox") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionValueBox(parentControl, positionInfo, optionItem)
      elseif (controlType == "MultiLineTextBox") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionMultiLineTextBox(parentControl, positionInfo, optionItem)
      elseif (controlType == "Slider") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionSlider(parentControl, positionInfo, optionItem)
      elseif (controlType == "ColorPicker") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionColorPicker(parentControl, positionInfo, optionItem)
      elseif (controlType == "SoundChannel") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionSoundChannel(parentControl, positionInfo, optionItem)
      elseif (controlType == "SoundFilePath") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionSoundFilePath(parentControl, positionInfo, optionItem)
      elseif (controlType == "FontFilePath") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFontFilePath(parentControl, positionInfo, optionItem)
      elseif (controlType == "ShowMinimapIcon") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionShowMinimapIcon(parentControl, positionInfo, optionItem)
      elseif (controlType == "FontSize") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFontSize(parentControl, positionInfo, optionItem)
      elseif (controlType == "FrameVisible") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFrameVisible(parentControl, positionInfo, optionItem)
      elseif (controlType == "FrameLocked") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFrameLocked(parentControl, positionInfo, optionItem)
      elseif (controlType == "FrameClampedToScreen") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFrameClampedToScreen(parentControl, positionInfo, optionItem)
      elseif (controlType == "FrameAnchoredToMouse") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFrameAnchoredToMouse(parentControl, positionInfo, optionItem)
      elseif (controlType == "FramePosition") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFramePosition(parentControl, positionInfo, optionItem)
      elseif (controlType == "FrameSize") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFrameSize(parentControl, positionInfo, optionItem)
      elseif (controlType == "FrameWidth") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFrameWidth(parentControl, positionInfo, optionItem)
      elseif (controlType == "FrameHeight") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFrameHeight(parentControl, positionInfo, optionItem)
      elseif (controlType == "FrameScale") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFrameScale(parentControl, positionInfo, optionItem)
      elseif (controlType == "FrameStrata") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFrameStrata(parentControl, positionInfo, optionItem)
      elseif (controlType == "FrameLevel") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFrameLevel(parentControl, positionInfo, optionItem)
      elseif (controlType == "FrameBackgroundColor") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionFrameBackgroundColor(parentControl, positionInfo, optionItem)
      elseif (controlType == "CustomControl") then
        control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionCustomControl(parentControl, positionInfo, optionItem)
      elseif (controlType == "StartGroupBox") or (controlType == "StartTabControl") or (controlType == "StartTab") or (controlType == "StartListView") or (controlType == "StartListViewItem") then
        if (activeMode == activeModeOfControlType[controlType]) then
          -- NOTE: This case is valid (i.e., a GroupBox within a GroupBox) so no error message is shown if it occurs. Note that validating the controlType for the current activeMode is handled elsewhere in this method. See also ValidateControlTypeForActiveMode.
          -- EasyOptions:PrintErrorMessageWithOptionCategoryInfo(L("has a \"{1}\" item at index {2}, but is missing an \"{3}\" item after the previous \"{1}\" item.", controlType, EasyOptions:Format(EasyOptions.Current.OptionItemIndex), EasyOptions:GetEndContainerControlTypeFromStartContainerControlType(controlType)))
        end
        iActiveMode = iActiveMode + 1
        activeModeInfo[iActiveMode] = activeModeOfControlType[controlType]
        activeMode = activeModeInfo[iActiveMode] -- Update activeMode.
        isAddingControlsToContainer = ContainsContainerActiveMode(activeModeInfo)
        isBuildingRow = (activeMode == activeMode_Row)
        iContainerInfo = iContainerInfo + 1 -- Increment the index.
        if (controlType == "StartGroupBox") then
          positionInfoInitialLocal = EasyOptions:GetPositionInfoOfChildControlInContainerStart()
          control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionGroupBoxGroup(parentControl, positionInfo, optionItem)
        elseif (controlType == "StartTabControl") then
          positionInfoInitialLocal = {"TOPLEFT", "TOPLEFT", 0, 0}
          control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionTabControl(parentControl, positionInfo, optionItem)
        elseif (controlType == "StartTab") then
          positionInfoInitialLocal = EasyOptions:GetPositionInfoOfChildControlInContainerStart()
          control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionTabButton(parentControl, positionInfo, optionItem)
        elseif (controlType == "StartListView") then
          positionInfoInitialLocal = {"TOPLEFT", "TOPLEFT", 0, 0}
          control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionListView(parentControl, positionInfo, optionItem)
        elseif (controlType == "StartListViewItem") then
          positionInfoInitialLocal = {"TOPLEFT", "TOPLEFT", EasyOptions:GetDefaultControlMarginX(), 0} -- EasyOptions:GetPositionInfoOfChildControlInContainerStart()
          control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionListViewItem(parentControl, positionInfo, optionItem)
        else
          EasyOptions:PrintErrorMessageOnce(L("The control type \"{1}\" is not a known container type.", controlType))
        end
        containerInfo[iContainerInfo] = {
          ContainerControl = control, 
          ParentControlPrevious = parentControl, 
          IndentCountCurrentPrevious = indentCountCurrent, 
          PositionInfoCurrentPrevious = {positionInfoCurrent[1], positionInfoCurrent[2], positionInfoCurrent[3], positionInfoCurrent[4]}, 
          PositionInfoInitialWithinContainerControl = positionInfoInitialLocal, 
          ControlRegionWidth = positionInfoInitialLocal[3], 
          ControlRegionHeight = -positionInfoInitialLocal[4], 
          CustomSize = optionItem.Size, 
          AutoFitWidth = optionItem.AutoFitWidth, 
          AutoFitHeight = optionItem.AutoFitHeight, 
          ControlsInContainerControl = {}} -- ContainerControl, ParentControlPrevious, IndentCountCurrentPrevious, PositionInfoCurrentPrevious, PositionInfoInitialWithinContainerControl, ControlRegionWidth, ControlRegionHeight, CustomSize, AutoFitWidth, AutoFitHeight, ControlsInContainerControl
        parentControl = control -- Set the container control as the parent of any child controls that are added to this container.
        positionInfo = {positionInfoInitialLocal[1], positionInfoInitialLocal[2], positionInfoInitialLocal[3], positionInfoInitialLocal[4]} -- Reset positionInfo to the default position within the container control.
        shouldReplacePositionInfoCurrent = true
        positionIncrementOffsetX = 0
        positionIncrementOffsetY = 0
        shouldIncrementPositionX = false
        shouldIncrementPositionY = false
        control, controlRegionWidth, controlRegionHeight = nil, 0, 0 -- Clear these parameters so they are not used outside of this case.
      elseif (controlType == "EndGroupBox") or (controlType == "EndTabControl") or (controlType == "EndTab") or (controlType == "EndListView") or (controlType == "EndListViewItem") then
        local isContainerItem = (activeModeOfControlType[controlType] == activeMode_Tab) or (activeModeOfControlType[controlType] == activeMode_ListViewItem)
        if (activeMode == activeMode_Standard) then
          EasyOptions:PrintErrorMessageWithOptionCategoryInfo(L("has an \"{1}\" item at index {2} with no matching \"{3}\" item.", controlType, EasyOptions:Format(EasyOptions.Current.OptionItemIndex), EasyOptions:GetStartContainerControlTypeFromEndContainerControlType(controlType)))
        elseif (activeMode ~= activeModeOfControlType[controlType]) then
          EasyOptions:PrintErrorMessageWithOptionCategoryInfo(L("has an \"{1}\" item at index {2}, but the previous \"{3}\" mode was not ended.", controlType, EasyOptions:Format(EasyOptions.Current.OptionItemIndex), activeMode))
        end
        if (activeMode == activeModeOfControlType[controlType]) and (iActiveMode > 0) then
          activeModeInfo[iActiveMode] = nil -- Clear the current item in the list.
          iActiveMode = iActiveMode - 1 -- Decrement the index.
        end
        activeMode = EasyOptions:IIF(iActiveMode >= 1, activeModeInfo[iActiveMode], activeMode_Standard) -- Update activeMode.
        isAddingControlsToContainer = ContainsContainerActiveMode(activeModeInfo)
        isBuildingRow = (activeMode == activeMode_Row)
        if (iContainerInfo >= 1) then
          control = containerInfo[iContainerInfo].ContainerControl -- Set the current control to the container control. -- ContainerControl, ParentControlPrevious, IndentCountCurrentPrevious, PositionInfoCurrentPrevious, PositionInfoInitialWithinContainerControl, ControlRegionWidth, ControlRegionHeight, CustomSize, AutoFitWidth, AutoFitHeight, ControlsInContainerControl
          EasyOptions:AddControlsToParent(containerInfo[iContainerInfo].ControlsInContainerControl, control) -- ContainerControl, ParentControlPrevious, IndentCountCurrentPrevious, PositionInfoCurrentPrevious, PositionInfoInitialWithinContainerControl, ControlRegionWidth, ControlRegionHeight, CustomSize, AutoFitWidth, AutoFitHeight, ControlsInContainerControl
          controlRegionWidth, controlRegionHeight = EasyOptions:ResizeContainerControlAfterAddChildControls(control, containerInfo[iContainerInfo])
          parentControl = containerInfo[iContainerInfo].ParentControlPrevious -- Restore the previous parentControl. -- ContainerControl, ParentControlPrevious, IndentCountCurrentPrevious, PositionInfoCurrentPrevious, PositionInfoInitialWithinContainerControl, ControlRegionWidth, ControlRegionHeight, CustomSize, AutoFitWidth, AutoFitHeight, ControlsInContainerControl
          optionItem = control.OptionItem
          positionInfoCurrent = containerInfo[iContainerInfo].PositionInfoCurrentPrevious -- Restore the previous value. -- ContainerControl, ParentControlPrevious, IndentCountCurrentPrevious, PositionInfoCurrentPrevious, PositionInfoInitialWithinContainerControl, ControlRegionWidth, ControlRegionHeight, CustomSize, AutoFitWidth, AutoFitHeight, ControlsInContainerControl
          positionInfo, shouldReplacePositionInfoCurrent, positionIncrementOffsetX, positionIncrementOffsetY, shouldIncrementPositionX, shouldIncrementPositionY = EasyOptions:GetPositionInfoFromOptionItem(positionInfoCurrent, optionItem, isBuildingRow)
          indentCountCurrent = containerInfo[iContainerInfo].IndentCountCurrentPrevious -- Restore the previous value. -- ContainerControl, ParentControlPrevious, IndentCountCurrentPrevious, PositionInfoCurrentPrevious, PositionInfoInitialWithinContainerControl, ControlRegionWidth, ControlRegionHeight, CustomSize, AutoFitWidth, AutoFitHeight, ControlsInContainerControl
          sign = EasyOptions:IIF(EasyOptions:IsRightAlignedPositionInfo(positionInfo), -1, 1)
          controlRegionWidth = controlRegionWidth + math.max(sign * (positionInfo[3] - positionInfoCurrent[3]), 0) -- Apply the offset in the x-position of the container control to controlRegionWidth.
          sign = EasyOptions:IIF(EasyOptions:IsBottomAlignedPositionInfo(positionInfo), -1, 1)
          controlRegionHeight = controlRegionHeight + math.max(sign * (-positionInfo[4] + positionInfoCurrent[4]), 0) -- Apply the offset in the y-position of the container control to controlRegionHeight.
          positionIncrementOffsetX = 0
          positionIncrementOffsetY = 0
          shouldIncrementPositionX = isBuildingRow and (not isContainerItem)
          shouldIncrementPositionY = (not isBuildingRow) and (not isContainerItem)
          if (iContainerInfo >= 1) then
            containerInfo[iContainerInfo] = nil -- Clear the item.
          end
        else
          positionInfo = nil
          control, controlRegionWidth, controlRegionHeight = nil, 0, 0
          shouldReplacePositionInfoCurrent = false
          positionIncrementOffsetX = 0
          positionIncrementOffsetY = 0
          shouldIncrementPositionX = false
          shouldIncrementPositionY = false
        end
        iContainerInfo = math.max(iContainerInfo - 1, 0) -- Decrement the index.
      elseif (controlType == "StartRow") then
        if (activeMode == activeMode_Row) then
          EasyOptions:PrintErrorMessageWithOptionCategoryInfo(L("has a \"StartRow\" item at index {1}, but is missing an \"EndRow\" item after the previous \"StartRow\" item.", EasyOptions:Format(EasyOptions.Current.OptionItemIndex)))
        end
        iActiveMode = iActiveMode + 1
        activeModeInfo[iActiveMode] = activeMode_Row
        activeMode = activeModeInfo[iActiveMode] -- Update activeMode.
        isAddingControlsToContainer = ContainsContainerActiveMode(activeModeInfo)
        isBuildingRow = (activeMode == activeMode_Row)
        iRowInfo = iRowInfo + 1 -- Increment the index.
        rowInfo[iRowInfo] = {
          PositionInfoPrevious = {positionInfoCurrent[1], positionInfoCurrent[2], positionInfoCurrent[3], positionInfoCurrent[4]}, 
          ControlRegionWidth = 0, 
          ControlRegionHeight = 0, 
          IndexOfItemToUseAsRowHeight = optionItem.IndexOfItemToUseAsRowHeight, 
          CustomHeight = optionItem.Height, 
          ControlsInRow = {}} -- PositionInfoPrevious, ControlRegionWidth, ControlRegionHeight, IndexOfItemToUseAsRowHeight, CustomHeight, ControlsInRow
        control, controlRegionWidth, controlRegionHeight = nil, 0, 0
        shouldReplacePositionInfoCurrent = true
        positionIncrementOffsetX = 0
        positionIncrementOffsetY = 0
        shouldIncrementPositionX = false
        shouldIncrementPositionY = false
      elseif (controlType == "EndRow") then
        if (activeMode == activeMode_Standard) then
          EasyOptions:PrintErrorMessageWithOptionCategoryInfo(L("has an \"EndRow\" item at index {1} with no matching \"StartRow\" item.", EasyOptions:Format(EasyOptions.Current.OptionItemIndex)))
        elseif (activeMode ~= activeMode_Row) then
          EasyOptions:PrintErrorMessageWithOptionCategoryInfo(L("has an \"EndRow\" item at index {1}, but the previous \"{2}\" mode was not ended.", EasyOptions:Format(EasyOptions.Current.OptionItemIndex), activeMode))
        end
        if (activeMode == activeMode_Row) and (iActiveMode > 0) then
          activeModeInfo[iActiveMode] = nil -- Clear the current item in the list.
          iActiveMode = iActiveMode - 1 -- Decrement the index.
        end
        activeMode = EasyOptions:IIF(iActiveMode >= 1, activeModeInfo[iActiveMode], activeMode_Standard) -- Update activeMode.
        isAddingControlsToContainer = ContainsContainerActiveMode(activeModeInfo)
        isBuildingRow = (activeMode == activeMode_Row)
        if (iRowInfo >= 1) then
          positionInfo = rowInfo[iRowInfo].PositionInfoPrevious -- PositionInfoPrevious, ControlRegionWidth, ControlRegionHeight, IndexOfItemToUseAsRowHeight, CustomHeight, ControlsInRow
          shouldReplacePositionInfoCurrent = true
          control = nil
          controlRegionWidth, controlRegionHeight = rowInfo[iRowInfo].ControlRegionWidth, rowInfo[iRowInfo].ControlRegionHeight -- PositionInfoPrevious, ControlRegionWidth, ControlRegionHeight, IndexOfItemToUseAsRowHeight, CustomHeight, ControlsInRow
          if (rowInfo[iRowInfo].CustomHeight ~= nil) then -- PositionInfoPrevious, ControlRegionWidth, ControlRegionHeight, IndexOfItemToUseAsRowHeight, CustomHeight, ControlsInRow
            controlRegionWidth, controlRegionHeight, hasWidth, hasHeight, shouldFillWidth = EasyOptions:GetSizeFromCustomSize(rowInfo[iRowInfo].CustomHeight, false, controlRegionWidth, controlRegionHeight, EasyOptions.Fields.ControlSizeMaxAllowed_Large, EasyOptions.Fields.ControlSizeMaxAllowed_Large, true, true)
          end
          positionIncrementOffsetX = 0
          positionIncrementOffsetY = 0
          shouldIncrementPositionX = (iRowInfo > 1)
          shouldIncrementPositionY = (iRowInfo <= 1)
          if (iRowInfo >= 1) then
            rowInfo[iRowInfo] = nil -- Clear the item.
          end
        else
          positionInfo = nil
          control, controlRegionWidth, controlRegionHeight = nil, 0, 0
          shouldReplacePositionInfoCurrent = false
          positionIncrementOffsetX = 0
          positionIncrementOffsetY = 0
          shouldIncrementPositionX = false
          shouldIncrementPositionY = false
        end
        iRowInfo = math.max(iRowInfo - 1, 0) -- Decrement the index.
      elseif (controlType == "SetPosition") then
        local positionInfoCurrent_SetPosition = {positionInfoInitial[1], positionInfoInitial[2], positionInfoInitial[3], positionInfoInitial[4]} -- Use the initial position as the reference position when determining the absolute position.
        positionInfo, shouldReplacePositionInfoCurrent, positionIncrementOffsetX, positionIncrementOffsetY, shouldIncrementPositionX, shouldIncrementPositionY = EasyOptions:GetPositionInfoFromOptionItem(positionInfoCurrent_SetPosition, optionItem, isBuildingRow)
        indentCountCurrent = EasyOptions:GetIndentCountFromOptionItem(indentCountCurrent, optionItem, isBuildingRow, useIndentCountCurrentIfIsBuildingRow)
        --
        control, controlRegionWidth, controlRegionHeight = nil, 0, 0
        shouldReplacePositionInfoCurrent = true
        positionIncrementOffsetX = 0
        positionIncrementOffsetY = 0
        shouldIncrementPositionX = false
        shouldIncrementPositionY = false
      elseif (controlType == "MovePosition") then
        local positionInfoCurrent_MovePosition = {positionInfoCurrent[1], positionInfoCurrent[2], 0, 0} -- We want to use a reference position of (0, 0) when determining how much to move the position.
        positionInfo, shouldReplacePositionInfoCurrent, positionIncrementOffsetX, positionIncrementOffsetY, shouldIncrementPositionX, shouldIncrementPositionY = EasyOptions:GetPositionInfoFromOptionItem(positionInfoCurrent_MovePosition, optionItem, isBuildingRow)
        indentCountCurrent = EasyOptions:GetIndentCountFromOptionItem(indentCountCurrent, optionItem, isBuildingRow, useIndentCountCurrentIfIsBuildingRow)
        --
        positionInfo = {positionInfo[1], positionInfo[2], positionInfo[3] + positionInfoCurrent[3], positionInfo[4] + positionInfoCurrent[4]}
        indentCountCurrent = EasyOptions:GetIndentCountFromOptionPositionInfo(indentCountCurrent, positionInfo, isBuildingRow, useIndentCountCurrentIfIsBuildingRow)
        control, controlRegionWidth, controlRegionHeight = nil, 0, 0
        shouldReplacePositionInfoCurrent = true
        positionIncrementOffsetX = 0
        positionIncrementOffsetY = 0
        shouldIncrementPositionX = false
        shouldIncrementPositionY = false
      elseif (controlType == "SavePosition") then
        key = EasyOptions:IIF(optionItem.Key ~= nil, optionItem.Key, defaultKey)
        savedPositionInfos[key] = {{positionInfoCurrent[1], positionInfoCurrent[2], positionInfoCurrent[3], positionInfoCurrent[4]}, indentCountCurrent} -- [1] = PositionInfo, [2] = IndentCountCurrent
        control, controlRegionWidth, controlRegionHeight = nil, 0, 0
        shouldReplacePositionInfoCurrent = false
        positionIncrementOffsetX = 0
        positionIncrementOffsetY = 0
        shouldIncrementPositionX = false
        shouldIncrementPositionY = false
      elseif (controlType == "RestorePosition") then
        key = EasyOptions:IIF(optionItem.Key ~= nil, optionItem.Key, defaultKey)
        if (savedPositionInfos[key] == nil) then -- [1] = PositionInfo, [2] = IndentCountCurrent
          if (key == defaultKey) then
            EasyOptions:PrintErrorMessageOnce(L("Unable to restore the OptionItem position because no position is associated with the default key."))
          elseif (type(key) == "string") then
            EasyOptions:PrintErrorMessageOnce(L("Unable to restore the OptionItem position because no position is associated with the key \"{1}\".", key))
          else
            EasyOptions:PrintErrorMessageOnce(L("Unable to restore the OptionItem position because no position is associated with the key {1}.", EasyOptions:Format(key)))
          end
        else
          positionInfo = savedPositionInfos[key][1] -- [1] = PositionInfo, [2] = IndentCountCurrent
          indentCountCurrent = savedPositionInfos[key][2] -- [1] = PositionInfo, [2] = IndentCountCurrent
        end
        control, controlRegionWidth, controlRegionHeight = nil, 0, 0
        shouldReplacePositionInfoCurrent = true
        positionIncrementOffsetX = 0
        positionIncrementOffsetY = 0
        shouldIncrementPositionX = false
        shouldIncrementPositionY = false
      elseif (controlType == "DisableAutoCheckBoxGrouping") then
        control, controlRegionWidth, controlRegionHeight = nil, 0, 0
        enableAutoCheckBoxGrouping = false
      elseif (controlType == "EnableAutoCheckBoxGrouping") then
        control, controlRegionWidth, controlRegionHeight = nil, 0, 0
        enableAutoCheckBoxGrouping = true
      else
        control, controlRegionWidth, controlRegionHeight = nil, 0, 0
        EasyOptions:PrintErrorMessageOnce(L("Unable to create one or more option items because \"{1}\" is an unknown control type.", EasyOptions:FormatName(controlType)))
      end
    end
    if (control ~= nil) then
      table.insert(controls, control)
    end
    iCheckBoxGroupInfo = EasyOptions:UpdateCheckBoxGroupInfo(control, controlType, iCheckBoxGroupInfo, checkBoxGroupInfo, indentCountCurrent, enableAutoCheckBoxGrouping, isAddingControlsToContainer, isBuildingRow)
    
    if (shouldReplacePositionInfoCurrent) then
      positionInfoCurrent = positionInfo
    end
    if (isAddingControlsToContainer) and (containerInfo[iContainerInfo] ~= nil) then
      if (control ~= nil) then
        table.insert(containerInfo[iContainerInfo].ControlsInContainerControl, control) -- ContainerControl, ParentControlPrevious, IndentCountCurrentPrevious, PositionInfoCurrentPrevious, PositionInfoInitialWithinContainerControl, ControlRegionWidth, ControlRegionHeight, CustomSize, AutoFitWidth, AutoFitHeight, ControlsInContainerControl
      end
      sign = EasyOptions:IIF(EasyOptions:IsRightAlignedPositionInfo(positionInfoCurrent), -1, 1)
      containerInfo[iContainerInfo].ControlRegionWidth = math.max(controlRegionWidth + sign * (positionInfoCurrent[3] + positionIncrementOffsetX), containerInfo[iContainerInfo].ControlRegionWidth) -- ContainerControl, ParentControlPrevious, IndentCountCurrentPrevious, PositionInfoCurrentPrevious, PositionInfoInitialWithinContainerControl, ControlRegionWidth, ControlRegionHeight, CustomSize, AutoFitWidth, AutoFitHeight, ControlsInContainerControl
      sign = EasyOptions:IIF(EasyOptions:IsBottomAlignedPositionInfo(positionInfoCurrent), -1, 1)
      containerInfo[iContainerInfo].ControlRegionHeight = math.max(controlRegionHeight - sign * (positionInfoCurrent[4] + positionIncrementOffsetY), containerInfo[iContainerInfo].ControlRegionHeight) -- ContainerControl, ParentControlPrevious, IndentCountCurrentPrevious, PositionInfoCurrentPrevious, PositionInfoInitialWithinContainerControl, ControlRegionWidth, ControlRegionHeight, CustomSize, AutoFitWidth, AutoFitHeight, ControlsInContainerControl
    end
    if (isBuildingRow) and (rowInfo[iRowInfo] ~= nil) then -- (activeMode == activeMode_Row)
      if (control ~= nil) then
        table.insert(rowInfo[iRowInfo].ControlsInRow, control) -- PositionInfoPrevious, ControlRegionWidth, ControlRegionHeight, IndexOfItemToUseAsRowHeight, CustomHeight, ControlsInRow
      end
      local rowControlCount = EasyOptions:GetLength(rowInfo[iRowInfo].ControlsInRow) -- PositionInfoPrevious, ControlRegionWidth, ControlRegionHeight, IndexOfItemToUseAsRowHeight, CustomHeight, ControlsInRow
      rowInfo[iRowInfo].ControlRegionWidth = controlRegionWidth + EasyOptions:IIF(rowInfo[iRowInfo].ControlRegionWidth == 0, 0, paddingX) + rowInfo[iRowInfo].ControlRegionWidth -- PositionInfoPrevious, ControlRegionWidth, ControlRegionHeight, IndexOfItemToUseAsRowHeight, CustomHeight, ControlsInRow
      if (rowInfo[iRowInfo].IndexOfItemToUseAsRowHeight ~= nil) and (rowInfo[iRowInfo].IndexOfItemToUseAsRowHeight == rowControlCount) then -- PositionInfoPrevious, ControlRegionWidth, ControlRegionHeight, IndexOfItemToUseAsRowHeight, CustomHeight, ControlsInRow
        rowInfo[iRowInfo].ControlRegionHeight = controlRegionHeight -- PositionInfoPrevious, ControlRegionWidth, ControlRegionHeight, IndexOfItemToUseAsRowHeight, CustomHeight, ControlsInRow
      elseif (rowInfo[iRowInfo].IndexOfItemToUseAsRowHeight == nil) or (rowInfo[iRowInfo].IndexOfItemToUseAsRowHeight <= 0) or (rowInfo[iRowInfo].IndexOfItemToUseAsRowHeight > rowControlCount) then -- Use the maximum height of the controls on the row if IndexOfItemToUseAsRowHeight is not specified, is invalid, or is greater than the current index. -- PositionInfoPrevious, ControlRegionWidth, ControlRegionHeight, IndexOfItemToUseAsRowHeight, CustomHeight, ControlsInRow
        rowInfo[iRowInfo].ControlRegionHeight = math.max(controlRegionHeight, rowInfo[iRowInfo].ControlRegionHeight) -- PositionInfoPrevious, ControlRegionWidth, ControlRegionHeight, IndexOfItemToUseAsRowHeight, CustomHeight, ControlsInRow
      end
    end
    if (shouldIncrementPositionX) then
      sign = EasyOptions:IIF(EasyOptions:IsRightAlignedPositionInfo(positionInfoCurrent), -1, 1)
      positionInfoCurrent[3] = positionInfoCurrent[3] + sign * (positionIncrementOffsetX + controlRegionWidth + EasyOptions:IIF(isBuildingRow, paddingX, 0))
    end
    if (shouldIncrementPositionY) then
      sign = EasyOptions:IIF(EasyOptions:IsBottomAlignedPositionInfo(positionInfoCurrent), -1, 1)
      positionInfoCurrent[4] = positionInfoCurrent[4] + sign * (positionIncrementOffsetY - controlRegionHeight - EasyOptions:IIF(isBuildingRow, 0, paddingY))
    end
  end
  --
  -- Add a blank label at the bottom of the option frame to act as padding so the bottom of the last control in the frame is slightly above the bottom of the frame.
  if (parentControl == parentControlInitial) and (EasyOptions:IsTopAlignedPositionInfo(positionInfoCurrent)) then -- Verify that parentControl is set to the expected value so we don't add the padding control to some other container control.
    local optionItem = {nil, " ", nil, 0, "Label"}
    EasyOptions:SetOptionItemProperties(optionItem, nil, nil, nil)
    positionInfo, shouldReplacePositionInfoCurrent, positionIncrementOffsetX, positionIncrementOffsetY, shouldIncrementPositionX, shouldIncrementPositionY = EasyOptions:GetPositionInfoFromOptionItem(positionInfoCurrent, optionItem, isBuildingRow)
    control, controlRegionWidth, controlRegionHeight = EasyOptions:CreateOptionLabel(parentControl, positionInfo, optionItem)
  end
  --
  -- Reset fields.
  EasyOptions.Current.OptionItemIndex = 0
  EasyOptions.Current.OptionItem = nil
  EasyOptions.Current.OptionItems = nil
  EasyOptions.Fields.RadioButtonDefaultGroupIndex = 0
  --
  -- Validate parameters.
  if (activeMode ~= activeMode_Standard) then
    EasyOptions:PrintErrorMessageWithOptionCategoryInfo(L("is missing an \"{1}\" item, or a configuration error prevented the \"{1}\" item from being processed.", activeModeToEndControlType[activeMode]))
  end

  -- Update the enabled state of controls.
  --  NOTE: This should be done before EasyOptions.Current.IsBuildingOptionFrame is restored to false for optimization.
  local includeChildControls = false -- The controls list should contain all controls that were created by this method so there is no need to update the child controls of the controls (i.e., the controls list should contain all parent and all child controls).
  EasyOptions:UpdateEnabledStateOfControlsFromEnabledStateOfOwners(controls, includeChildControls)
  --
  -- Indicate that the Option frame is done being built.
  EasyOptions.Current.IsBuildingOptionFrame = false

  -- Create an option frame for each of the child items.
  for i = 1, childrenLength do
    EasyOptions:CreateOptionFrame(optionItems.Children[i], optionPanelFrame, optionPanelFrames)
  end

  return optionPanelFrame
end
function EasyOptions:CreateOptionFrame_AddToOptionPanelFrames(categoryName, optionPanelFrame, optionPanelFrames)
  if (optionPanelFrames[categoryName] ~= nil) then
    EasyOptions:PrintErrorMessageOnce(L("An options category name of \"{1}\" is duplicated in {2}. Each category name should be unique.", EasyOptions:FormatName(categoryName), EasyOptions:FormatName(EasyOptions.Current.AddonName)))
    return
  end
  optionPanelFrames[categoryName] = optionPanelFrame
end
function EasyOptions:CreateOptionFrames(optionItems) -- mainOptionPanelFrame, optionPanelFrames
  local optionPanelFrames = {} -- [CategoryName] = Frame
  local mainOptionPanelFrame = EasyOptions:CreateOptionFrame(optionItems, nil, optionPanelFrames)
  return mainOptionPanelFrame, optionPanelFrames
end
function EasyOptions:OnOptionPanelFrameShowHandler(frame)
  local addonName = frame.AddonName
  local categoryName = frame.CategoryName
  local addonInfo = EasyOptions.Fields.AddonInfo[addonName] -- [AddonName] = {AddonName, AddonDB, OptionItems, OptionItemsDictionary, MainOptionPanelFrame, OptionPanelFrames, OptionControls, OptionControlCounts, OnOptionValueChangedHandler (Optional), OnOptionValuesChangingHandler (Optional), OnOptionValuesChangedHandler (Optional), OnOptionValuesChangedHandler (Optional), OnOptionPanelFrameShowHandler (Optional), IsValidSaveDataVersionProvider (Optional)} -- Stores all data for each addon.
  if (addonInfo.OnOptionPanelFrameShowHandler ~= nil) then
    -- EasyOptions:PrintItems({"OnOptionPanelFrameShowHandler", frame.name, frame:GetName()})
    addonInfo:OnOptionPanelFrameShowHandler(categoryName, frame)
  end
end
function EasyOptions:OnOptionPanelFrameHideHandler(frame)
  local addonName = frame.AddonName
  local categoryName = frame.CategoryName
  local addonInfo = EasyOptions.Fields.AddonInfo[addonName] -- [AddonName] = {AddonName, AddonDB, OptionItems, OptionItemsDictionary, MainOptionPanelFrame, OptionPanelFrames, OptionControls, OptionControlCounts, OnOptionValueChangedHandler (Optional), OnOptionValuesChangingHandler (Optional), OnOptionValuesChangedHandler (Optional), OnOptionValuesChangedHandler (Optional), OnOptionPanelFrameShowHandler (Optional), IsValidSaveDataVersionProvider (Optional)} -- Stores all data for each addon.
  if (addonInfo.OnOptionPanelFrameHideHandler ~= nil) then
    -- EasyOptions:PrintItems({"OnOptionPanelFrameHideHandler", frame.name, frame:GetName()})
    addonInfo:OnOptionPanelFrameHideHandler(categoryName, frame)
  end
end

function EasyOptions:AddOptionsCategoryTitleLabels(parent, customControlName, titleText, subTitleText)
  local controlName = EasyOptions:GetControlName("CategoryTitle", customControlName)
  local positionInfo_Title = EasyOptions:GetDefaultCategoryTitleLabelPositionInfo()
  local positionInfo_SubTitle = EasyOptions:GetDefaultCategorySubTitleLabelPositionInfo()

  local titleLabel = parent:CreateFontString(controlName, "ARTWORK", "GameFontNormalLarge")
  titleLabel.Name = controlName
  titleLabel:SetPoint(positionInfo_Title[1], parent, positionInfo_Title[2], positionInfo_Title[3], positionInfo_Title[4])
  titleLabel:SetJustifyH("LEFT") -- LEFT, CENTER, RIGHT
  titleLabel:SetJustifyV("MIDDLE") -- TOP, MIDDLE, BOTTOM
  titleLabel:SetText(titleText or "")
  titleLabel:SetWordWrap(false)
  titleLabel:SetNonSpaceWrap(false)
  
  local subTitleLabel
  if (subTitleText ~= nil) then
    local subTitleTextLocal = GAME_VERSION_LABEL..": "..EasyOptions:ConvertTextToColoredText(subTitleText, EasyOptions.Fields.DefaultTextColor) -- Example: "Version: 1.0.0"
    subTitleLabel = parent:CreateFontString(controlName..".SubTitle", "ARTWORK", "GameFontNormalSmall") -- "GameFontHighlightSmall"
    subTitleLabel:SetPoint(positionInfo_SubTitle[1], parent, positionInfo_SubTitle[2], positionInfo_SubTitle[3], positionInfo_SubTitle[4])
    subTitleLabel:SetJustifyH("LEFT") -- LEFT, CENTER, RIGHT
    subTitleLabel:SetJustifyV("MIDDLE") -- TOP, MIDDLE, BOTTOM
    subTitleLabel:SetText(subTitleTextLocal)
    -- subTitleLabel:SetFormattedText("%s: %s%s|r", GAME_VERSION_LABEL, HIGHLIGHT_FONT_COLOR_CODE, subTitleText)
    subTitleLabel:SetWordWrap(false)
    subTitleLabel:SetNonSpaceWrap(false)
  end
  
  return titleLabel, subTitleLabel
end
function EasyOptions:GetDefaultCategoryTitleLabelPositionInfo()
  return {"TOPLEFT", "TOPLEFT", EasyOptions.Fields.OptionsFrameMarginLeft - 12, -EasyOptions.Fields.OptionsFrameMarginTop + 8}
end
function EasyOptions:GetDefaultCategorySubTitleLabelPositionInfo()
  local positionInfo = EasyOptions:GetDefaultCategoryTitleLabelPositionInfo()
  positionInfo[4] = positionInfo[4] - 26
  return positionInfo
end

function EasyOptions:AddOptionsButtons(parent, addonName, optionItems, isParentFrame, hasChildren)
  local resetOptionCategoryButton = nil -- init
  local resetAllOptionsButton = nil -- init
  local manageOptionCategoryButton = nil -- init
  local firstOptionsButton = nil -- init
  local includeHiddenControlTypes_ResetButtons = false
  if (addonName ~= "EasyOptions") and (EasyOptions:HasEditableOptionItem(optionItems, true, includeHiddenControlTypes_ResetButtons)) then -- Only create the "Addon Manager" button if there are editable option items and the addon is not EasyOptions.
    manageOptionCategoryButton = EasyOptions:CreateManageOptionsButton(parent, addonName, optionItems, resetOptionCategoryButton or resetAllOptionsButton)
    firstOptionsButton = manageOptionCategoryButton or firstOptionsButton
  end
  if (isParentFrame) and (EasyOptions:HasEditableOptionItem(optionItems, true, includeHiddenControlTypes_ResetButtons)) then -- Only create the "Reset Addon" button if there are child option items and there are editable option items.
    resetAllOptionsButton = EasyOptions:CreateResetAllOptionsButton(parent, addonName, optionItems, resetOptionCategoryButton or manageOptionCategoryButton)
    firstOptionsButton = resetAllOptionsButton or firstOptionsButton
  end
  if (EasyOptions:HasEditableOptionItem(optionItems, false, includeHiddenControlTypes_ResetButtons)) and ((not isParentFrame) or (hasChildren)) then -- Only create the "Reset Category" button if there are editable option items and if the frame is a child frame or the OptionItems contain children (i.e., subcategories).
    resetOptionCategoryButton = EasyOptions:CreateResetOptionCategoryButton(parent, addonName, optionItems, resetAllOptionsButton or manageOptionCategoryButton)
    firstOptionsButton = resetOptionCategoryButton or firstOptionsButton
  end
  return firstOptionsButton
end
function EasyOptions:CreateResetOptionCategoryButton(parent, addonName, optionItems, anchorButton)
  local controlName = EasyOptions:GetControlName("ResetOptionCategoryButton", customControlName)
  local categoryName = optionItems.Name
  local text = L("Reset Category")
  local tooltipTitleText = text
  local tooltipText = L("Resets all options in the currently displayed category to their default values.\n\nThis operation cannot be undone.\n\n{1}", EasyOptions:GetTipTextForResetOptionsButton())
  local positionInfo = EasyOptions:GetDefaultOptionsButtonPositionInfo()
  local customSize = nil
  local onClickHandler = function(self, button)
    EasyOptions:ResetOptionValues(self.AddonName, self.OptionItems, false, true, true)
  end
  
  local control, controlRegionWidth, controlRegionHeight, shouldFillWidth = EasyOptions:CreateButton(parent, controlName, text, tooltipTitleText, tooltipText, positionInfo, customSize, onClickHandler)
  
  -- optionItems.Control = control
  control.AddonName = addonName
  control.OptionItems = optionItems
  local isStackedVertically = false -- init
  if (anchorButton ~= nil) then
    isStackedVertically = EasyOptions:SetAnchorForOptionsButton(control, anchorButton)
  end
  
  -- Set both controls to the same width (if required).
  if (anchorButton ~= nil) and (not anchorButton.IsManageOptionsButton) and (isStackedVertically) then
    local width = math.max(control:GetWidth(), anchorButton:GetWidth())
    local deltaWidth = width - control:GetWidth()
    control:SetWidth(width)
    anchorButton:SetWidth(width)
    controlRegionWidth = controlRegionWidth + deltaWidth
  end

  return control, controlRegionWidth, controlRegionHeight
end
function EasyOptions:CreateResetAllOptionsButton(parent, addonName, optionItems, anchorButton)
  local controlName = EasyOptions:GetControlName("ResetAllOptionsButton", customControlName)
  local text = L("Reset Addon")
  local tooltipTitleText = text
  local tooltipText = L("Resets all options in this addon to their default values.\n\nThis operation will reset all options of all categories of this addon and cannot be undone.\n\n{1}", EasyOptions:GetTipTextForResetOptionsButton())
  local positionInfo = EasyOptions:GetDefaultOptionsButtonPositionInfo() -- NOTE: This will only be used if anchorButton is nil (see below).
  local customSize = nil
  local onClickHandler = function(self, button)
    EasyOptions:ResetAllOptionValues(self.AddonName, self.OptionItems, true, true)
  end
  
  local control, controlRegionWidth, controlRegionHeight, shouldFillWidth = EasyOptions:CreateButton(parent, controlName, text, tooltipTitleText, tooltipText, positionInfo, customSize, onClickHandler)
  
  -- optionItems.Control = control
  control.AddonName = addonName
  control.OptionItems = optionItems
  local isStackedVertically = false -- init
  if (anchorButton ~= nil) then
    isStackedVertically = EasyOptions:SetAnchorForOptionsButton(control, anchorButton)
  end
  
  -- Set both controls to the same width (if required).
  if (anchorButton ~= nil) and (not anchorButton.IsManageOptionsButton) and (isStackedVertically) then
    local width = math.max(control:GetWidth(), anchorButton:GetWidth())
    local deltaWidth = width - control:GetWidth()
    control:SetWidth(width)
    anchorButton:SetWidth(width)
    controlRegionWidth = controlRegionWidth + deltaWidth
  end

  return control, controlRegionWidth, controlRegionHeight
end
function EasyOptions:CreateManageOptionsButton(parent, addonName, optionItems, anchorButton)
  local controlName = EasyOptions:GetControlName("ManageOptionsButton", customControlName)
  local text = nil -- L("Manage")
  local tooltipTitleText = L("Addon Manager")
  local tooltipText = L("Open the Addon Manager for this addon in EasyOptions.\n\n{1}", EasyOptions:GetTipTextForManageOptionsButton())
  local positionInfo = EasyOptions:GetDefaultOptionsButtonPositionInfo() -- NOTE: This will only be used if anchorButton is nil (see below).
  local customSize = EasyOptions:GetManageOptionsButtonWidth()
  local onClickHandler = function(self, button)
    local control = self
    EasyOptions:ShowAddonManager(control.AddonName)
  end
  
  local control, controlRegionWidth, controlRegionHeight, shouldFillWidth = EasyOptions:CreateButton(parent, controlName, text, tooltipTitleText, tooltipText, positionInfo, customSize, onClickHandler)
  EasyOptions:SetButtonIconTexture(control, 5, "Interface\\AddOns\\EasyOptions\\Textures\\Cog.png")
  
  -- optionItems.Control = control
  control.AddonName = addonName
  control.OptionItems = optionItems
  control.IsManageOptionsButton = true
  if (anchorButton ~= nil) then
    EasyOptions:SetAnchorForOptionsButton(control, anchorButton)
  end

  return control, controlRegionWidth, controlRegionHeight
end
function EasyOptions:GetTipTextForResetOptionsButton()
  local color = EasyOptionsDB["TooltipAlternateTextColor"]
  return EasyOptions:ConvertTextToColoredText(L("Tip: You can use the Addon Manager in EasyOptions to create a backup of this addon's options before you reset their values."), color)
end
function EasyOptions:GetTipTextForManageOptionsButton()
  local color = EasyOptionsDB["TooltipAlternateTextColor"]
  return EasyOptions:ConvertTextToColoredText(L("The Addon Manager allows you to save options for this addon which can be used as a backup or for different types of content (e.g., PvP, raiding, M+, tanking, gathering, etc.)."), color)
end
function EasyOptions:SetAnchorForOptionsButton(control, anchorButton)
  if (control == nil) then
    return
  end
  if (anchorButton == nil) then
    return
  end
  
  local isStackedVertically
  control:ClearAllPoints()
  if (control.IsManageOptionsButton) then
    control:SetPoint("TOPRIGHT", anchorButton, "TOPLEFT", -EasyOptions:GetOptionsButtonOffsetX(), 0)
    isStackedVertically = false
  elseif (anchorButton.IsManageOptionsButton) then
    control:SetPoint("TOPRIGHT", anchorButton, "TOPLEFT", -EasyOptions:GetOptionsButtonOffsetX(), 0)
    isStackedVertically = false
  else
    control:SetPoint("TOPRIGHT", anchorButton, "TOPLEFT", -EasyOptions:GetOptionsButtonOffsetX(), 0)
    isStackedVertically = false
    -- control:SetPoint("TOPLEFT", anchorButton, "BOTTOMLEFT", 0, 0)
    -- isStackedVertically = true
  end
  return isStackedVertically
end
function EasyOptions:GetOptionsButtonOffsetX()
  return -2
end
function EasyOptions:GetManageOptionsButtonWidth()
  return 26 -- 30
end
function EasyOptions:GetDefaultOptionsButtonPositionInfo()
  return {"TOPRIGHT", "TOPRIGHT", -EasyOptions.Fields.OptionsFrameMarginRight - 18, -EasyOptions.Fields.OptionsFrameMarginTop + 16}
end

function EasyOptions:AddBuiltWithLabel(parent, addonName, isParentFrame)
  if (not isParentFrame) then
    return
  end

  local customControlName = nil
  local controlName = EasyOptions:GetControlName("BuiltWithLabel", customControlName)
  local text = EasyOptions:IIF(addonName == "EasyOptions", L("The easy way to build a World of Warcraft addon!"), L("Built with EasyOptions"))
  local color = EasyOptions.Fields.DefaultTitleColor
  local positionInfo = EasyOptions:GetDefaultBuiltWithLabelPositionInfo()

  local label = parent:CreateFontString(controlName, "ARTWORK", "GameFontNormalSmall") -- "GameFontHighlightSmall"
  label:SetPoint(positionInfo[1], parent, positionInfo[2], positionInfo[3], positionInfo[4])
  label:SetJustifyH("RIGHT") -- LEFT, CENTER, RIGHT
  label:SetJustifyV("MIDDLE") -- TOP, MIDDLE, BOTTOM
  label:SetText(text)
  label:SetTextColor(color[1], color[2], color[3])
  label:SetWordWrap(false)
  label:SetNonSpaceWrap(false)
end
function EasyOptions:GetDefaultBuiltWithLabelPositionInfo()
  local positionInfo = EasyOptions:GetDefaultOptionsButtonPositionInfo()
  local positionInfo_SubTitle = EasyOptions:GetDefaultCategorySubTitleLabelPositionInfo()
  positionInfo[3] = positionInfo[3] - 2
  positionInfo[4] = positionInfo_SubTitle[4]
  return positionInfo
end

function EasyOptions:CanResetOptionValue(optionItem)
  local includeHiddenControlTypes = true
  if (not EasyOptions:IsEditableOptionItem(optionItem, includeHiddenControlTypes)) then -- IMPORTANT: We must validate EasyOptions:IsEditableOptionItem so this method is consistent with when the Reset buttons are shown in options frames.
    return false
  end
  return (not EasyOptions:IsOptionValueSetToDefaultValue(optionItem))
end
function EasyOptions:IsOptionValueSetToDefaultValue(optionItem)
  local defaultResult = true -- Return true by default.

  if (optionItem == nil) then -- [1] Name, [2] Text, [3] TooltipText, [4] PositionInfo, [5] Type, [6+] [Type Specific]
    return defaultResult
  end
  
  local addonDB = optionItem.AddonDB
  local optionName = optionItem.Name
  local defaultValue = optionItem.DefaultValue
  if (addonDB == nil) then
    return defaultResult
  end
  if (optionName == nil) then -- NOTE: We must require optionName to be ~= nil because we require optionName in order to determine the current value in Options so the user is not prompted if the control is currently set to its default value.
    return defaultResult
  end
  -- if (defaultValue == nil) then
  --   return defaultResult
  -- end
  
  local controlType = optionItem.ControlType
  if (controlType == "RadioButton") or (controlType == "RadioButtonGroup") then -- NOTE: Custom logic is required for RadioButtons because a RadioButton can be set to its default value even if one or more of the other RadioButtons are not set to their default value.
    return EasyOptions:IsRadioButtonGroupOfOptionItemSetToDefaultValue(optionItem)
  else
    return EasyOptions:AreEqual(addonDB[optionName], defaultValue, true)
  end
end
function EasyOptions:ResetOptionValue(optionItem, isSubOptionItemControl, promptUser) -- Sets the option value to its default value.
  -- NOTE: The following have similar functionality:
  --  EasyOptions:ResetOptionValue
  --  EasyOptions:SetOptionValueToDefaultValue
  --  EasyOptions:SetOptionValueToDefaultValueIfCurrentValueEquals

  if (not EasyOptions:CanResetOptionValue(optionItem)) then
    return
  end
  -- EasyOptions:Print(optionItem.Name..", Default Value = "..EasyOptions:Format(optionItem.DefaultValue)..", Control Text = "..EasyOptions:Format(optionItem.Control.Text:GetText()))
  if (not promptUser) then
    EasyOptions:ResetOptionValue_Internal(optionItem)
  else
    local promptText = L("Reset this item to its default value?")
    if (isSubOptionItemControl) then
      promptText = L("{1}\n\nThis will also reset the values of the other controls that are associated with this item.", promptText)
    end
    local uniqueID = "EasyOptions.ResetOptionValue"
    local onAcceptHandler = function(userText)
      EasyOptions:ResetOptionValue_Internal(optionItem)
    end
    local onCancelHandler = nil
    EasyOptions:ShowDialogOkCancel(promptText, uniqueID, onAcceptHandler, onCancelHandler)
  end
end
function EasyOptions:ResetOptionValue_Internal(optionItem) -- Sets the option value to its default value.
  if (not EasyOptions:CanResetOptionValue(optionItem)) then
    return
  end

  -- Set the option value to its default value.
  local addonDB = optionItem.AddonDB
  local addonName = optionItem.AddonName
  local optionName = optionItem.Name
  local defaultValue = optionItem.DefaultValue
  if (addonDB ~= nil) and (optionName ~= nil) and (not EasyOptions:AreEqual(addonDB[optionName], defaultValue, true)) then
    addonDB[optionName] = defaultValue
    EasyOptions:HandleOptionValueChanged(addonName, optionName)
    if (EasyOptionsDB["EnableOptionChangedMessages"]) and (optionName ~= nil) and (optionName ~= "") then
      EasyOptions:PrintMessage(L("{1} restored to {2}.", EasyOptions:FormatName(optionName), addonDB[optionName]))
    end
  end

  -- Update the value of the control.
  local control = optionItem.Control
  local controlType = optionItem.ControlType -- ControlType
  if (controlType == "Hidden") then
    -- Do nothing.
  elseif (controlType == "GroupTitle") then
    -- Do nothing.
  elseif (controlType == "GroupBox") or (controlType == "StartGroupBox") then
    -- Do nothing.
  elseif (controlType == "TabControl") or (controlType == "StartTabControl") then -- See also EasyOptions:IsEditableControlType.
    if (control:GetValue() ~= defaultValue) then
      control:SetValue(defaultValue)
    end
  elseif (controlType == "TabButton") or (controlType == "StartTab") then
    -- Do nothing.
  elseif (controlType == "ListView") or (controlType == "StartListView") then -- See also EasyOptions:IsEditableControlType.
    if (control:GetValue() ~= defaultValue) then
      control:SetValue(defaultValue)
    end
  elseif (controlType == "ListViewItem") or (controlType == "StartListViewItem") then
    -- Do nothing.
  elseif (controlType == "Label") then
    -- Do nothing.
  elseif (controlType == "Separator") then
    -- Do nothing.
  elseif (controlType == "Button") or (controlType == "ReloadUIButton") then
    -- Do nothing.
  elseif (controlType == "CheckBox") or (controlType == "StartCheckBoxGroup") or (controlType == "ShowMinimapIcon") or (controlType == "FrameVisible") or (controlType == "FrameLocked") or (controlType == "FrameClampedToScreen") or (controlType == "FrameAnchoredToMouse") then
    if (control:GetValue() ~= defaultValue) then
      control:SetValue(defaultValue)
    end
  elseif (controlType == "RadioButton") then
    -- if (control:GetValue() ~= defaultValue) then
      control:SetValue(defaultValue) -- IMPORTANT: We must always call control:SetValue if controlType == "RadioButton" (even if control:GetValue() == defaultValue) in case the user right-clicked a RadioButton that is set to its default value, but some other RadioButton is not set to its default value.
    -- end
  elseif (controlType == "RadioButtonGroup") then
    if (control:GetValue() ~= defaultValue) then
      control:SetValue(defaultValue)
    end
  elseif (controlType == "DropDown") or (controlType == "SoundChannel") or (controlType == "FrameStrata") then
    if (control:GetValue() ~= defaultValue) then
      control:SetValue(defaultValue)
    end
  elseif (controlType == "EditableDropDown") or (controlType == "SoundFilePath") or (controlType == "FontFilePath") then
    if (control:GetValue() ~= defaultValue) then
      control:SetValue(defaultValue)
    end
  elseif (controlType == "TextBox") or (controlType == "MultiLineTextBox") then
    if (control:GetText() ~= defaultValue) then
      control:SetValue(defaultValue)
    end
  elseif (controlType == "ValueBox") then
    if (control.Value ~= defaultValue) then -- control:GetNumber()
      control:SetValue(defaultValue)
    end
  elseif (controlType == "Slider") or (controlType == "FontSize") or (controlType == "FrameSize") or (controlType == "FrameWidth") or (controlType == "FrameHeight") or (controlType == "FrameScale") or (controlType == "FrameLevel") then
    if (control:GetValue() ~= defaultValue) then
      control:SetValue(defaultValue)
    end
  elseif (controlType == "ColorPicker") or (controlType == "FrameBackgroundColor") then
    control:SetValue(defaultValue)
  elseif (controlType == "FramePosition") then
    control:SetValue(defaultValue)
  elseif (controlType == "CustomControl") then
    if (control.SetValue ~= nil) then
      if (control.GetValue == nil) or (control:GetValue() ~= defaultValue) then
        control:SetValue(defaultValue)
      end
    end
  else
    EasyOptions:PrintErrorMessageOnce(L("Unable to update the value of one or more controls because \"{1}\" is an unknown control type.", EasyOptions:FormatName(controlType)))
  end
end
function EasyOptions:ResetOptionValues(addonName, optionItems, includeChildren, promptUser, showMessage) -- Sets the option values to their default value.
  if (not promptUser) then
    EasyOptions:ResetOptionValues_Internal(addonName, optionItems, includeChildren, showMessage)
  else
    local categoryName = optionItems.Name
    local promptText = L("Reset all options in the {1} category to their default values?\n\nThis operation cannot be undone.", EasyOptions:FormatName(optionItems.Name))
    local uniqueID = "EasyOptions.ResetOptionValues"
    local onAcceptHandler = function(userText)
      EasyOptions:ResetOptionValues_Internal(addonName, optionItems, includeChildren, showMessage)
    end
    local onCancelHandler = nil
    EasyOptions:ShowDialogOkCancel(promptText, uniqueID, onAcceptHandler, onCancelHandler)
  end
end
function EasyOptions:ResetOptionValues_Internal(addonName, optionItems, includeChildren, showMessage) -- Sets the option values to their default value.
  if (showMessage) and (EasyOptionsDB["EnableOptionChangedMessages"]) then
    EasyOptions:PrintMessage(L("Resetting all options in the {1} category...", EasyOptions:FormatName(optionItems.Name)))
  end

  local previousAreOptionValuesChanging
  if (EasyOptions:IsManagedAddon(addonName)) then
    previousAreOptionValuesChanging = EasyOptions.Fields.AreOptionValuesChanging[addonName] or false
    EasyOptions:SetAreOptionValuesChanging(addonName, true)
  end
  --
  local previousIsResettingOptionValues = EasyOptions.Fields.IsResettingOptionValues[addonName] or {false, nil} -- [AddonName] = [1] IsResetting, [2] OptionItems
  EasyOptions:SetIsResettingOptionValues(addonName, true, optionItems)

  local showMessageLocal = false
  local length = EasyOptions:GetLength(optionItems)
  for i = 1, length do
    EasyOptions:ResetOptionValue_Internal(optionItems[i])
  end
  if (includeChildren) then
    local childrenOptionItems = optionItems.Children
    local childrenLength = EasyOptions:GetLength(childrenOptionItems)
    for i = 1, childrenLength do
      EasyOptions:ResetOptionValues_Internal(addonName, childrenOptionItems[i], includeChildren, showMessageLocal)
    end
  end
  
  EasyOptions:SetIsResettingOptionValues(addonName, previousIsResettingOptionValues[1], previousIsResettingOptionValues[2])
  --
  if (EasyOptions:IsManagedAddon(addonName)) then
    EasyOptions:SetAreOptionValuesChanging(addonName, previousAreOptionValuesChanging)
  end

  if (showMessage) then
    EasyOptions:PrintMessage(L("Reset all options in the {1} category to their default values.", EasyOptions:FormatName(optionItems.Name)))
  end
end
function EasyOptions:ResetAllOptionValues(addonName, optionItems, promptUser, showMessage) -- Sets all option values to their default value.
  if (not promptUser) then
    EasyOptions:ResetAllOptionValues_Internal(addonName, optionItems, showMessage)
  else
    local promptText = L("Reset all options in the {1} addon to their default values?\n\nThis operation will reset all options of all categories of this addon and cannot be undone.", EasyOptions:FormatName(addonName))
    local uniqueID = "EasyOptions.ResetAllOptionValues"
    local onAcceptHandler = function(userText)
      EasyOptions:ResetAllOptionValues_Internal(addonName, optionItems, showMessage)
    end
    local onCancelHandler = nil
    EasyOptions:ShowDialogOkCancel(promptText, uniqueID, onAcceptHandler, onCancelHandler)
  end
end
function EasyOptions:ResetAllOptionValues_Internal(addonName, optionItems, showMessage) -- Sets all option values to their default value.
  if (showMessage) and (EasyOptionsDB["EnableOptionChangedMessages"]) then
    EasyOptions:PrintMessage(L("Resetting all options in the {1} addon...", EasyOptions:FormatName(addonName)))
  end
  --
  local includeChildrenLocal = true
  local showMessageLocal = false
  EasyOptions:ResetOptionValues_Internal(addonName, optionItems, includeChildrenLocal, showMessageLocal)
  --
  if (showMessage) then
    EasyOptions:PrintMessage(L("Reset all options in the {1} addon to their default values.", EasyOptions:FormatName(addonName)))
  end
end

function EasyOptions:HasEditableOptionItem(optionItems, includeChildren, includeHiddenControlTypes)
  local length = EasyOptions:GetLength(optionItems)
  for i = 1, length do
    if (EasyOptions:IsEditableOptionItem(optionItems[i], includeHiddenControlTypes)) then
      return true
    end
  end

  if (includeChildren) then
    local childrenLength = EasyOptions:GetLength(optionItems.Children)
    for i = 1, childrenLength do
      if (EasyOptions:HasEditableOptionItem(optionItems.Children[i], includeChildren, includeHiddenControlTypes)) then
        return true
      end
    end
  end

  return false
end
function EasyOptions:GetNumberOfEditableOptionItems(optionItems, includeChildren, includeHiddenControlTypes)
  local length = EasyOptions:GetLength(optionItems)
  local count = 0
  for i = 1, length do
    if (EasyOptions:IsEditableOptionItem(optionItems[i], includeHiddenControlTypes)) then
      count = count + 1
    end
  end

  if (includeChildren) then
    local childrenLength = EasyOptions:GetLength(optionItems.Children)
    for i = 1, childrenLength do
      count = count + EasyOptions:GetNumberOfEditableOptionItems(optionItems.Children[i], includeChildren, includeHiddenControlTypes)
    end
  end

  return count
end
function EasyOptions:IsEditableOptionItem(optionItem, includeHiddenControlTypes)
  if (optionItem == nil) then
    return false
  end
  if (optionItem.IsBlankLine) then
    return false
  end

  local controlType = optionItem.ControlType
  return EasyOptions:IsEditableControlType(controlType)
end
function EasyOptions:IsEditableControlType(controlType)
  if (controlType == nil) then -- This can be true if optionItem.IsBlankLine = true.
    return false
  end

  if (controlType == "Hidden") then
    return includeHiddenControlTypes

  elseif (controlType == "GroupTitle") then
    return false

  elseif (controlType == "GroupBox") then
    return false

  elseif (controlType == "StartGroupBox") then
    return false

  elseif (controlType == "EndGroupBox") then
    return false
    
  elseif (controlType == "StartTabControl") then
    return false -- NOTE: Returning true for this ControlType will cause the selected tab of a TabControl to be set to the first tab when the user resets the values of the option items in an option frame if the TabControl has an OptionItem that has a Name (i.e., tabControl.OptionItem ~= nil and tabControl.OptionItem.Name ~= nil).

  elseif (controlType == "EndTabControl") then
    return false

  elseif (controlType == "StartTab") then
    return false

  elseif (controlType == "EndTab") then
    return false
    
  elseif (controlType == "StartListView") then
    return false -- NOTE: Returning true for this ControlType will cause the selected ListViewItem of a ListView to be set to the first tab when the user resets the values of the option items in an option frame if the ListView has an OptionItem that has a Name (i.e., listView.OptionItem ~= nil and listView.OptionItem.Name ~= nil).

  elseif (controlType == "EndListView") then
    return false

  elseif (controlType == "StartListViewItem") then
    return false

  elseif (controlType == "EndListViewItem") then
    return false

  elseif (controlType == "Label") then
    return false

  elseif (controlType == "Separator") then
    return false

  elseif (controlType == "Button") then
    return false

  elseif (controlType == "ReloadUIButton") then
    return false
    
  elseif (controlType == "CheckBox") then
    return true

  elseif (controlType == "StartCheckBoxGroup") then
    return true

  elseif (controlType == "EndCheckBoxGroup") then
    return false

  elseif (controlType == "EndAllCheckBoxGroups") then
    return false

  elseif (controlType == "RadioButton") then
    return true

  elseif (controlType == "RadioButtonGroup") then
    return true
    
  elseif (controlType == "DropDown") then
    return true

  elseif (controlType == "EditableDropDown") then
    return true

  elseif (controlType == "TextBox") then
    return true

  elseif (controlType == "ValueBox") then
    return true

  elseif (controlType == "MultiLineTextBox") then
    return true

  elseif (controlType == "Slider") then
    return true

  elseif (controlType == "ColorPicker") then
    return true
    
  elseif (controlType == "SoundChannel") then
    return true
    
  elseif (controlType == "SoundFilePath") then
    return true
    
  elseif (controlType == "FontFilePath") then
    return true
    
  elseif (controlType == "FontSize") then
    return true
    
  elseif (controlType == "ShowMinimapIcon") then
    return true
    
  elseif (controlType == "FrameVisible") then
    return true

  elseif (controlType == "FrameLocked") then
    return true
    
  elseif (controlType == "FrameClampedToScreen") then
    return true
    
  elseif (controlType == "FrameAnchoredToMouse") then
    return true
    
  elseif (controlType == "FramePosition") then
    return true
    
  elseif (controlType == "FrameSize") then
    return true
    
  elseif (controlType == "FrameWidth") then
    return true
    
  elseif (controlType == "FrameHeight") then
    return true
    
  elseif (controlType == "FrameScale") then
    return true
    
  elseif (controlType == "FrameStrata") then
    return true

  elseif (controlType == "FrameLevel") then
    return true
    
  elseif (controlType == "FrameBackgroundColor") then
    return true
    
  elseif (controlType == "CustomControl") then
    return true
    
  elseif (controlType == "StartRow") then
    return false

  elseif (controlType == "EndRow") then
    return false

  elseif (controlType == "SetPosition") then
    return false

  elseif (controlType == "MovePosition") then
    return false
    
  elseif (controlType == "SavePosition") then
    return false

  elseif (controlType == "RestorePosition") then
    return false

  elseif (controlType == "DisableAutoCheckBoxGrouping") then
    return false

  elseif (controlType == "EnableAutoCheckBoxGrouping") then
    return false

  else
    EasyOptions:PrintErrorMessageOnce(L("\"{1}\" is an unknown control type.", EasyOptions:FormatName(controlType)))
    return false
  end
end
