
---@type detailsframework
local detailsFramework = _G["DetailsFramework"]
if (not detailsFramework or not DetailsFrameworkCanLoad) then
	return
end

local unpack = unpack
local C_Timer = C_Timer
local InCombatLockdown = InCombatLockdown
local CreateFrame = CreateFrame
local PixelUtil = PixelUtil
local _

---@class df_menu : frame
---@field RefreshOptions fun()
---@field widget_list table
---@field widget_list_by_type table
---@field widget_to_disable_check table
---@field widgetids table
---@field GetWidgetById fun(optionsFrame: df_menu, id: string): table this should return a widget from the widgetids table

---@class df_menu_table : table
---@field type string the type of widget to create
---@field text_template table
---@field id string an unique string or number to identify the button, from parent.widgetids[id], parent is the first argument of BuildMenu and BuildMenuVolatile
---@field namePhraseId string the phrase id (from language localization) to use on the button
---@field hasLabel any
---@field hidden boolean?
---@field inline boolean?
---@field widget table?
---@field disableif function? a function that returns true or nil, if true the widget get :Disable(), :Enabled() otherwise
---@field tags string[] optional tags that help the search bar to find the option

---@class df_menu_label : df_menu_table
---@field get function
---@field color table
---@field font string
---@field size number
---@field text string

---@class df_menu_dropdown : df_menu_table
---@field type string
---@field set function
---@field get function
---@field values table
---@field name string
---@field desc string
---@field descPhraseId string
---@field hooks table
---@field include_default boolean

---@class df_menu_toggle : df_menu_table
---@field set function
---@field get function
---@field name string
---@field desc string
---@field descPhraseId string
---@field hooks table
---@field width number
---@field height number
---@field boxfirst boolean

---@class df_menu_range : df_menu_table
---@field set function
---@field get function
---@field min number
---@field max number
---@field step number
---@field name string
---@field desc string
---@field descPhraseId string
---@field hooks table
---@field thumbscale number
---@field usedecimals boolean if true allow fraction values

---@class df_menu_color : df_menu_table
---@field set function
---@field get function
---@field name string
---@field desc string
---@field descPhraseId string
---@field hooks table
---@field boxfirst boolean

---@class df_menu_button : df_menu_table
---@field func function the function to execute when the button is pressed
---@field param1 any the first parameter to pass to the function
---@field param2 any the second parameter to pass to the function
---@field name string text to show on the button
---@field desc string text to show on the tooltip
---@field descPhraseId string the phrase id (from language localization) to use on the tooltip
---@field hooks table a table with hooks to add to the button
---@field width number
---@field height number
---@field inline boolean
---@field icontexture any
---@field icontexcoords table

---@class df_menu_textentry : df_menu_table
---@field func function the function to execute when enter key is pressed
---@field set function same as above 'func'
---@field get function
---@field name string text to show on the button
---@field desc string text to show on the tooltip
---@field descPhraseId string the phrase id (from language localization) to use on the tooltip
---@field hooks table a table with hooks to add to the button
---@field inline boolean if true, the widget is placed in the rigt side of the previous one
---@field align string "left", "center" or "right"
---@field nocombat boolean can't edit when in combat
---@field spacement boolean gives a little of more space from the next widget

---@class df_menu_texture : df_menu_table
---@field texture any
---@field texture_width number
---@field texture_height number

---@class df_menu_group : df_menu_table
---@field type string "group"
---@field name string the name identifier for this group
---@field color table {red, green, blue, alpha} background color
---@field UseBackdrop table? optional backdrop properties table, if set the frame uses SetBackdrop instead of a plain texture
---@field BackgroundColor table? {r, g, b, a} used with UseBackdrop for the backdrop background color
---@field BackdropBorderColor table? {r, g, b, a} used with UseBackdrop for the backdrop border color
---@field width number? optional fixed width for the group frame
---@field height number? optional fixed height for the group frame
---@field padding number? optional padding to add around the group frame, this is used to give more space between the group border and the widgets inside it

detailsFramework.OptionsFrameMixin = {

}

detailsFramework.ValidBuildMenuWidgetTypes = {
    ["label"] = true,
    ["select"] = true,
    ["toggle"] = true,
    ["range"] = true,
    ["color"] = true,
    ["execute"] = true,
    ["textentry"] = true,
    ["image"] = true,
    ["space"] = true,
    ["blank"] = true,
    ["fontdropdown"] = true,
    ["texturedropdown"] = true,
    ["colordropdown"] = true,
    ["outlinedropdown"] = true,
    ["anchordropdown"] = true,
    ["audiodropdown"] = true,
    ["dropdown"] = true,
    ["switch"] = true,
    ["slider"] = true,
    ["button"] = true,
    ["selectfont"] = true,
    ["selectstatusbartexture"] = true,
    ["selectcolor"] = true,
    ["selectoutline"] = true,
    ["selectanchor"] = true,
    ["selectaudio"] = true,
    ["selectframestrata"] = true,
    ["backgrounddropdown"] = true,
    ["selectbackgroundtexture"] = true,
    ["borderdropdown"] = true,
    ["selectbordertexture"] = true,
    ["group"] = true
}

function detailsFramework:IsValidWidgetForBuildMenu(widgetType)
    return detailsFramework.ValidBuildMenuWidgetTypes[widgetType] or false
end

local onWidgetSetInUse = function(widget, widgetTable)
    if (widgetTable.childrenids) then
        widget.childrenids = widgetTable.childrenids
    end
    widget.children_follow_enabled = widgetTable.children_follow_enabled

    if (widgetTable.disabled) then
        widget:Disable()
    else
        if (widget.IsEnabled and not widget:IsEnabled()) then
            widget:Enable()
        end
    end
end

local setWidgetId = function(parent, widgetTable, widgetObject)
    if (widgetTable.id) then
        parent.widgetids[widgetTable.id] = widgetObject
    end
    widgetTable.widget = widgetObject
end

local onEnterHighlight = function(self)
    self.highlightTexture:Show()
    if (self.parent:GetScript("OnEnter")) then
        self.parent:GetScript("OnEnter")(self.parent)
    end
end

local onLeaveHighlight = function(self)
    self.highlightTexture:Hide()
    if (self.parent:GetScript("OnLeave")) then
        self.parent:GetScript("OnLeave")(self.parent)
    end
end

local parseTemplates = function(textTemplate, dropdownTemplate, switchTemplate, sliderTemplate, buttonTemplate, switchIsCheckbox)
    if not textTemplate then
        textTemplate = detailsFramework:GetTemplate("font", "OPTIONS_FONT_TEMPLATE")
    end
    if not dropdownTemplate then
        dropdownTemplate = detailsFramework:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE")
    end
    if not switchTemplate then
        switchTemplate = detailsFramework:GetTemplate("switch", "OPTIONS_CHECKBOX_TEMPLATE")
        switchIsCheckbox = true
    end
    if not sliderTemplate then
        sliderTemplate = detailsFramework:GetTemplate("slider", "OPTIONS_SLIDER_TEMPLATE")
    end
    if not buttonTemplate then
        buttonTemplate = detailsFramework:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE")
    end

    return textTemplate, dropdownTemplate, switchTemplate, sliderTemplate, buttonTemplate, switchIsCheckbox
end

local processTexture = function(widget, widgetTable)
    widget = widget.widget or widget

    if (widgetTable.texture) then
        local texture = widget.IconTexture
        if (not texture) then
            texture = widget:CreateTexture(nil, "overlay")
            texture:SetPoint("left", widget, "right", 2, 0)
            widget.IconTexture = texture
        end

        local textureToSet = widgetTable.texture
        if (type(textureToSet) == "string" or type(textureToSet) == "number") then
            texture:SetTexture(textureToSet)
            texture:SetSize(widgetTable.texture_width or (widget:GetHeight()-2), widgetTable.texture_height or (widget:GetHeight()-2))
        end

        if (widget.hasLabel and widget.hasLabel.widget) then
            widget.hasLabel.widget.originalPoint = widget.hasLabel.widget.originalPoint or {widget.hasLabel.widget:GetPoint(1)}
            widget.hasLabel.widget:ClearAllPoints()
            widget.hasLabel.widget:SetPoint("left", texture, "right", 2, 0)
        end
    else
        if (widget.IconTexture and widget.IconTexture:IsShown()) then
            widget.IconTexture:Hide()

            if (widget.hasLabel and widget.hasLabel.widget and widget.hasLabel.widget.originalPoint) then
                widget.hasLabel.widget:ClearAllPoints()
                widget.hasLabel.widget:SetPoint(unpack(widget.hasLabel.widget.originalPoint))
            end
        end
    end
end

local getNamePhraseID = function(widgetTable, languageAddonId, languageTable, bIgnoreEmbed)
    if (widgetTable.namePhraseId) then
        return widgetTable.namePhraseId
    end

    if (not languageTable) then
        return
    end

    local keyName = widgetTable.name

    if (widgetTable.type == "label" and widgetTable.get) then
        local key = widgetTable.get()
        if (key and type(key) == "string") then
            keyName = key
        end
    end

    --embed key is when the phraseId is inside a string surounded by @
    local embedPhraseId = keyName:match("@(.-)@")

    local hasValue = detailsFramework.Language.DoesPhraseIDExistsInDefaultLanguage(languageAddonId, embedPhraseId or keyName)
    if (not hasValue) then
        return
    end

    if (embedPhraseId and not bIgnoreEmbed) then
        return embedPhraseId, true
    else
        return keyName
    end
end

local formatOptionNameWithColon = function(text, useColon)
    if (text) then
        if (useColon) then
            text = text .. ":"
            return text
        else
            return text
        end
    end
end

local getNamePhraseText = function(languageTable, widgetTable, useColon, languageAddonId)
    local namePhraseId, bWasEmbed = getNamePhraseID(widgetTable, languageAddonId, languageTable)
    local namePhrase = languageTable and (languageTable[namePhraseId] or languageTable[widgetTable.namePhraseId] or languageTable[widgetTable.name])

    if (bWasEmbed and widgetTable.name) then
        namePhrase = widgetTable.name:gsub("@" .. namePhraseId .. "@", namePhrase)
    end

    return namePhrase or formatOptionNameWithColon(widgetTable.name, useColon) or widgetTable.namePhraseId or widgetTable.name or "-?-"
end

---if the widgetTable has a key named 'icontexture' with a valid number or string, it add the texture in the left side of the label using escape sequence.
local processLabelIcon = function(label, widgetTable, languageTable, textTemplate, useColon, languageAddonId)
    --icontexture: texture
    --iconcoords: table with 4 numbers, left, right, top and bottom coords of the icontexture
    --iconsize: table with 2 numbers, width and height of the icon to show, if not set, it will be the same size as the font height of the label
    --iconfilesize: table with 2 numbers, width and height of the icontexture, this is used to calculate the correct coords if the file has a different size than the default 64x64
    --its size will be the same as the font size of the label
    if textTemplate then
        label:SetTemplate(textTemplate)
    end

    local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon, languageAddonId)

    local iconString = ""
    if widgetTable.icontexture then
        local tc = widgetTable.iconcoords or {.1, .9, .1, .9}
        local fileSize = widgetTable.iconfilesize or {64, 64}
        local font, height, flags = label:GetFont()
        local iconSize = widgetTable.iconsize or {height, height}

        local bAddSpace = true
        local bAddAfterText = false

        namePhrase, iconString = detailsFramework:AddTextureToText(namePhrase, detailsFramework:CreateTextureInfo(widgetTable.icontexture, iconSize[1], iconSize[2], tc[1], tc[2], tc[3], tc[4], fileSize[1], fileSize[2]), bAddSpace, bAddAfterText)
    end

    label.__iconString = iconString
    label.__iconStringBefore = true
    if label.widget then
        label.widget.__iconString = iconString
        label.widget.__iconStringBefore = true
    end

    label:SetText(namePhrase)
end

--control the highlight color, if true, use color one, if false, use color two
--color one: .2, .2, .2, 0.5
--color two: .3, .3, .3, 0.5
local bHighlightColorOne = true

---create a button and a texture to highlight the button when the mouse is over it
---the button has the dimentions of the label and the widget
---@param frame frame
---@param label fontstring
---@param widgetWidth number
---@return unknown
local createOptionHighlightFrame = function(frame, label, widgetWidth)
    frame = frame.widget or frame
    label = label.widget or label

    local highlightFrame = CreateFrame("button", nil, frame)
    highlightFrame:EnableMouse(true)
    highlightFrame:SetFrameLevel(frame:GetFrameLevel()-1)

    PixelUtil.SetSize(highlightFrame, widgetWidth, frame:GetHeight() + 1)
    PixelUtil.SetPoint(highlightFrame, "topleft", label, "topleft", -2, 5)

    highlightFrame:SetScript("OnEnter", onEnterHighlight)
    highlightFrame:SetScript("OnLeave", onLeaveHighlight)

    local highlightTexture = highlightFrame:CreateTexture(nil, "overlay")
    highlightTexture:SetColorTexture(1, 1, 1, 0.1)

    PixelUtil.SetPoint(highlightTexture, "topleft", highlightFrame, "topleft", 0, 0)
    PixelUtil.SetPoint(highlightTexture, "bottomright", highlightFrame, "bottomright", 0, 0)
    highlightTexture:Hide()

    local backgroundTexture = highlightFrame:CreateTexture(nil, "artwork")
    backgroundTexture:SetColorTexture(1, 1, 1, 0.5)
    backgroundTexture:SetVertexColor(.25, .25, .25, 0.5)

    if (bHighlightColorOne) then
        backgroundTexture:SetVertexColor(.2, .2, .2, 0.5)
    else
        backgroundTexture:SetVertexColor(.25, .25, .25, 0.5)
    end
    bHighlightColorOne = not bHighlightColorOne

    PixelUtil.SetPoint(backgroundTexture, "topleft", highlightFrame, "topleft", 0, 0)
    PixelUtil.SetPoint(backgroundTexture, "bottomright", highlightFrame, "bottomright", 0, 0)

    highlightFrame.highlightTexture = highlightTexture
    highlightFrame.parent = frame

    return highlightFrame
end

local setLabelProperties = function(parent, widget, widgetTable, currentXOffset, currentYOffset, template)
    widget._get = widgetTable.get
    widget.widget_type = "label"
    widget:SetPoint(currentXOffset, currentYOffset)

    if (widgetTable.text_template or template) then
        widget:SetTemplate(widgetTable.text_template or template)
    else
        widget.fontsize = widgetTable.size or 10
    end

    if (widgetTable.font) then
        widget.fontface = widgetTable.font
    end

    setWidgetId(parent, widgetTable, widget)
    onWidgetSetInUse(widget, widgetTable)
end

local setDropdownProperties = function(parent, widget, widgetTable, currentXOffset, currentYOffset, template, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth)
    widget._get = widgetTable.get
    widget.widget_type = "select"

    widget:Refresh()
    widget:Select(widgetTable.get())
    widget:SetTemplate(template)

    if (widgetWidth) then
        widget:SetWidth(widgetWidth)
    end
    if (widgetHeight) then
        widget:SetHeight(widgetHeight)
    end

    setWidgetId(parent, widgetTable, widget)

    local label = widget.hasLabel.widget

    widget:ClearAllPoints()
    label:ClearAllPoints()

    if (bAlignAsPairs) then --regular
        PixelUtil.SetPoint(label, "topleft", widget:GetParent(), "topleft", currentXOffset, currentYOffset)
        PixelUtil.SetPoint(widget.widget, "left", label, "left", nAlignAsPairsLength, 0)

        if (not widget.highlightFrame) then
            local highlightFrame = createOptionHighlightFrame(widget, label, (widgetWidth or 140) + nAlignAsPairsLength + 5)
            widget.highlightFrame = highlightFrame
        end
    else
        widget:SetPoint("left", label, "right", 2, 0)
        label:SetPoint("topleft", parent, "topleft", currentXOffset, currentYOffset)
    end

    --global callback
    if (valueChangeHook) then
        widget:SetHook("OnOptionSelected", valueChangeHook)
    end

    --hook list
    if (widgetTable.hooks) then
        for hookName, hookFunc in pairs(widgetTable.hooks) do
            widget:SetHook(hookName, hookFunc)
        end
    end

    local widgetTotalSize = label:GetStringWidth() + 144
    if (widgetTotalSize > maxColumnWidth) then
        maxColumnWidth = widgetTotalSize
    end

    if (widget:GetWidth() > maxWidgetWidth) then
        maxWidgetWidth = widget:GetWidth()
    end

    onWidgetSetInUse(widget, widgetTable)

    return maxColumnWidth, maxWidgetWidth
end

local highlightFrameOnClickToggle = function(highlightFrame, mouseButton)
    local parent = highlightFrame:GetParent()
    local widget = parent.MyObject

    local bNewState = not widget._get()
    widget.OnSwitch(widget, nil, bNewState) --widget.OnSwitch = widgetTable.set

    if (bNewState) then
        widget:SetValue(true)
    else
        widget:SetValue(false)
    end

    if (widget._valueChangeHook) then
        widget._valueChangeHook()
    end
end

local setToggleProperties = function(parent, widget, widgetTable, currentXOffset, currentYOffset, template, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, switchIsCheckbox, bUseBoxFirstOnAllWidgets, menuOptions, index, maxWidgetWidth)
    widget._get = widgetTable.get
    widget._set = widgetTable.set
    widget.widget_type = "toggle"
    widget.OnSwitch = widgetTable.set

    if (switchIsCheckbox) then
        widget:SetAsCheckBox()
    end

    if (widgetTable.children_follow_enabled) then
        --widget.SetValueOriginal = widget.SetValue --perhaps widgetTable.set()  --perhaps setscrip OnClick
        widget.SetValueOriginal = widget.SetValueOriginal or widget.SetValue
        widget._name = widgetTable.name

        local newSetFunc = function(thisWidget, value)
            --look for children ids
            local childrenids = widgetTable.childrenids
            --print(childrenids, type(childrenids))
            if (type(childrenids) == "table") then
                for i, childId in ipairs(childrenids) do
                    local childWidget = parent:GetWidgetById(childId)
                    --print("childWidget", childWidget)
                    if (childWidget) then
                        --if the children_follow_reverse is true, then the children will be enabled when the toogle is disabeld
                        --this is used when the main toggle is a kind of "Do This Automatically", if is not doing it automatically
                        --then the children should be enabled to set the options
                        if (widgetTable.children_follow_reverse) then
                            if (value) then
                                childWidget:Disable()
                            else
                                childWidget:Enable()
                            end
                        else
                            if (value) then
                                childWidget:Enable()
                            else
                                childWidget:Disable()
                            end
                        end
                    end
                end
            end

            thisWidget.SetValueOriginal(thisWidget, value)
            return value
        end

        widget:SetValue(widgetTable.get())
        rawset(widget, "SetValue", newSetFunc)
    else
        if (widget.SetValueOriginal) then
            rawset(widget, "SetValue", widget.SetValueOriginal)
            rawset(widget, "SetValueOriginal", nil)
        end
        widget:SetValue(widgetTable.get())
    end

    if (widgetWidth) then
        PixelUtil.SetWidth(widget.widget, widgetWidth)
    end
    if (widgetHeight) then
        PixelUtil.SetHeight(widget.widget, widgetHeight)
    end

    widget:SetTemplate(template)

    setWidgetId(parent, widgetTable, widget)

    local label = widget.hasLabel.widget

    widget:ClearAllPoints()
    label:ClearAllPoints()

    local extraPaddingY = 0

    if (bAlignAsPairs) then
        if (not widget.highlightFrame) then
            local highlightFrame = createOptionHighlightFrame(widget, label, (widgetWidth or 140) + nAlignAsPairsLength + 5)
            widget.highlightFrame = highlightFrame
        end

        widget._valueChangeHook = valueChangeHook
        widget.highlightFrame:SetScript("OnClick", highlightFrameOnClickToggle)
        PixelUtil.SetPoint(label, "topleft", widget:GetParent(), "topleft", currentXOffset, currentYOffset)
        PixelUtil.SetPoint(widget.widget, "right", widget.highlightFrame, "right", -3, 0)
    else
        if (widgetTable.boxfirst or bUseBoxFirstOnAllWidgets) then
            label:SetPoint("left", widget.widget or widget, "right", 2, 0)
            widget:SetPoint("topleft", parent, "topleft", currentXOffset, currentYOffset)

            local nextWidgetTable = menuOptions[index+1]
            if (nextWidgetTable) then
                if (nextWidgetTable.type ~= "blank" and nextWidgetTable.type ~= "breakline" and nextWidgetTable.type ~= "toggle" and nextWidgetTable.type ~= "color") then
                    extraPaddingY = 4
                end
            end
        else
            widget:SetPoint("left", label, "right", 2, 0)
            label:SetPoint("topleft", parent, "topleft", currentXOffset, currentYOffset)
        end
    end

    --global callback
    if (valueChangeHook) then
        widget:SetHook("OnSwitch", valueChangeHook)
    end

    --hook list
    if (widgetTable.hooks) then
        for hookName, hookFunc in pairs(widgetTable.hooks) do
            widget:SetHook(hookName, hookFunc)
        end
    end

    local widgetTotalSize = label:GetStringWidth() + 32
    if (widgetTotalSize > maxColumnWidth) then
        maxColumnWidth = widgetTotalSize
    end

    if (widget:GetWidth() > maxWidgetWidth) then
        maxWidgetWidth = widget:GetWidth()
    end

    processTexture(widget, widgetTable)

    onWidgetSetInUse(widget, widgetTable)

    return maxColumnWidth, maxWidgetWidth, extraPaddingY
end

local setRangeProperties = function(parent, widget, widgetTable, currentXOffset, currentYOffset, template, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth, bIsDecimals, bAttachSliderButtonsToLeft)
    widget._get = widgetTable.get
    widget.widget_type = "range"
    widget:SetTemplate(template)

    widget.bAttachButtonsToLeft = bAttachSliderButtonsToLeft

    local currentValue = widgetTable.get()

    if (bIsDecimals) then
        widget.slider:SetValueStep(0.01)
    else
        widget.slider:SetValueStep(widgetTable.step or 1)
        currentValue = math.floor(currentValue)
    end
    widget.useDecimals = bIsDecimals

    widget.slider:SetMinMaxValues(widgetTable.min, widgetTable.max)
    widget.slider:SetValue(currentValue or 0)
    widget.ivalue = widget.slider:GetValue()

    if (widgetWidth) then
        widget:SetWidth(widgetWidth)
    end
    if (widgetHeight) then
        widget:SetHeight(widgetHeight)
    end

    widget:SetHook("OnValueChange", widgetTable.set)

    if (valueChangeHook) then
        widget:SetHook("OnValueChange", valueChangeHook)
    end

    if (widgetTable.thumbscale) then
        widget:SetThumbSize(widget.thumb.originalWidth * widgetTable.thumbscale, nil)
    else
        widget:SetThumbSize(widget.thumb.originalWidth * 1.3, nil)
    end

    --hook list
    if (widgetTable.hooks) then
        for hookName, hookFunc in pairs(widgetTable.hooks) do
            widget:SetHook(hookName, hookFunc)
        end
    end

    setWidgetId(parent, widgetTable, widget)

    local label = widget.hasLabel.widget

    widget:ClearAllPoints()
    label:ClearAllPoints()

    if (bAlignAsPairs) then
        PixelUtil.SetPoint(label, "topleft", widget:GetParent(), "topleft", currentXOffset, currentYOffset)
        PixelUtil.SetPoint(widget.widget, "left", label, "left", nAlignAsPairsLength, 0)

        if (not widget.highlightFrame) then
            local highlightFrame = createOptionHighlightFrame(widget, label, (widgetWidth or 140) + nAlignAsPairsLength + 5)
            widget.highlightFrame = highlightFrame
        end

        widget.bAttachButtonsToLeft = true
    else
        widget:SetPoint("left", label, "right", 2, 0)
        label:SetPoint("topleft", parent, "topleft", currentXOffset, currentYOffset)
    end

    local widgetTotalSize = label:GetStringWidth() + 146
    if (widgetTotalSize > maxColumnWidth) then
        maxColumnWidth = widgetTotalSize
    end

    if (widget:GetWidth() > maxWidgetWidth) then
        maxWidgetWidth = widget:GetWidth()
    end

    onWidgetSetInUse(widget, widgetTable)

    return maxColumnWidth, maxWidgetWidth
end

local setColorProperties = function(parent, widget, widgetTable, currentXOffset, currentYOffset, template, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth, bUseBoxFirstOnAllWidgets, extraPaddingY)
    widget._get = widgetTable.get
    widget.widget_type = "color"

    local r, g, b, a = detailsFramework:ParseColors(widgetTable.get())
    widget:SetColor(r, g, b, a)

    widget.color_callback = widgetTable.set --callback

    --[=[
        if (widgetWidth) then
            widget:SetWidth(widgetWidth)
        else
            widget:SetWidth(18)
        end

        if (widgetHeight) then
            widget:SetHeight(widgetHeight)
        else
            widget:SetHeight(18)
        end
    --]=]

    widget:SetTemplate(template)
    widget:SetWidth(18)
    widget:SetHeight(18)

    widget:SetHook("OnColorChanged", widgetTable.set)

    if (valueChangeHook) then
        widget:SetHook("OnColorChanged", valueChangeHook)
    end

    --hook list
    if (widgetTable.hooks) then
        for hookName, hookFunc in pairs(widgetTable.hooks) do
            widget:SetHook(hookName, hookFunc)
        end
    end

    setWidgetId(parent, widgetTable, widget)

    local label = widget.hasLabel.widget

    widget:ClearAllPoints()
    label:ClearAllPoints()

    if (bAlignAsPairs) then
        if (not widget.highlightFrame) then
            local highlightFrame = createOptionHighlightFrame(widget, label, (widgetWidth or 140) + nAlignAsPairsLength + 5)
            widget.highlightFrame = highlightFrame
        end

        ----
        widget._valueChangeHook = valueChangeHook
        --widget.highlightFrame:SetScript("OnClick", highlightFrameOnClickToggle) --todo make this function for color picker color pick start
        PixelUtil.SetPoint(label, "topleft", widget:GetParent(), "topleft", currentXOffset, currentYOffset)
        PixelUtil.SetPoint(widget.widget, "right", widget.highlightFrame, "right", -3, 0)
    else
        if (widgetTable.boxfirst or bUseBoxFirstOnAllWidgets) then
            label:SetPoint("left", widget.widget, "right", 2, 0)
            widget:SetPoint(currentXOffset, currentYOffset)
            extraPaddingY = 1
        else
            widget:SetPoint("left", label, "right", 2, 0)
            label:SetPoint("topleft", parent, "topleft", currentXOffset, currentYOffset)
        end
    end

    local widgetTotalSize = label:GetStringWidth() + 32
    if (widgetTotalSize > maxColumnWidth) then
        maxColumnWidth = widgetTotalSize
    end

    if (widget:GetWidth() > maxWidgetWidth) then
        maxWidgetWidth = widget:GetWidth()
    end

    onWidgetSetInUse(widget, widgetTable)

    return maxColumnWidth, maxWidgetWidth, extraPaddingY
end

local setExecuteProperties = function(parent, widget, widgetTable, currentXOffset, currentYOffset, template, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth, textTemplate, latestInlineWidget)
    ---@cast widget df_button
    widget._get = widgetTable.get
    widget.widget_type = "execute"
    widget:SetTemplate(template)
    widget:SetWidth(widgetWidth or widgetTable.width or 120, widgetHeight or widgetTable.height or 18)

    widget:SetClickFunction(widgetTable.func, widgetTable.param1, widgetTable.param2)

    --button icon
    if (widgetTable.icontexture) then
        widget:SetIcon(widgetTable.icontexture, widget:GetHeight()-2, widget:GetHeight()-2, nil, widgetTable.icontexcoords, nil, nil, 2)
    end

    textTemplate = widgetTable.text_template or textTemplate or detailsFramework.font_templates["ORANGE_FONT_TEMPLATE"]
    widget.textcolor = textTemplate.color
    widget.textfont = textTemplate.font
    widget.textsize = textTemplate.size

    --hook list
    if (widgetTable.hooks) then
        for hookName, hookFunc in pairs(widgetTable.hooks) do
            widget:SetHook(hookName, hookFunc)
        end
    end

    setWidgetId(parent, widgetTable, widget)

    local label = widget.hasLabel.widget

    widget:ClearAllPoints()
    label:ClearAllPoints()

    if (bAlignAsPairs) then
        PixelUtil.SetPoint(label, "topleft", widget:GetParent(), "topleft", currentXOffset, currentYOffset)
        PixelUtil.SetPoint(widget.widget, "left", label, "left", nAlignAsPairsLength, 0)
        label:SetText(">")
        if (not widget.highlightFrame) then
            local highlightFrame = createOptionHighlightFrame(widget, label, (widgetWidth or 140) + nAlignAsPairsLength + 5)
            widget.highlightFrame = highlightFrame
        end
    else
        if (widgetTable.inline) then
            if (latestInlineWidget) then
                widget:SetPoint("left", latestInlineWidget, "right", 2, 0)
                latestInlineWidget = widget
            else
                widget:SetPoint(currentXOffset, currentYOffset)
                latestInlineWidget = widget
            end
        else
            widget:SetPoint(currentXOffset, currentYOffset)
        end
    end

    local widgetTotalSize = widget:GetWidth() + 4
    if (widgetTotalSize > maxColumnWidth) then
        maxColumnWidth = widgetTotalSize
    end

    if (widget:GetWidth() > maxWidgetWidth) then
        maxWidgetWidth = widget:GetWidth()
    end

    onWidgetSetInUse(widget, widgetTable)

    return maxColumnWidth, maxWidgetWidth, latestInlineWidget
end

local setImageProperties = function(parent, widget, widgetTable, currentXOffset, currentYOffset)
    --.texture .width .height .filterType .texcoord
    if (type(widgetTable.texture) == "table") then
        local r, g, b, a = detailsFramework:ParseColors(widgetTable.texture)
        widget:SetColorTexture(r, g, b, a)
    else
        widget:SetTexture(widgetTable.texture, "CLAMP", "CLAMP", widgetTable.filterType)
    end

    widget:SetSize(widgetTable.width, widgetTable.height)

    local left, right, top, bottom = 0, 1, 0, 1
    if (widgetTable.texcoord) then
        left, right, top, bottom = unpack(widgetTable.texcoord)
    end
    widget:SetTexCoord(left, right, top, bottom)

    if (widgetTable.vertexcolor) then
        local r, g, b, a = detailsFramework:ParseColors(widgetTable.vertexcolor)
        widget:SetVertexColor(r, g, b, a)
    else
        widget:SetVertexColor(1, 1, 1, 1)
    end

    setWidgetId(parent, widgetTable, widget)

    widget:ClearAllPoints()
    widget:SetPoint("topleft", parent, "topleft", currentXOffset, currentYOffset)
end

local setTextEntryProperties = function(parent, widget, widgetTable, currentXOffset, currentYOffset, template, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth, textTemplate, latestInlineWidget)
    widget._get = widgetTable.get
    widget.text = widgetTable.get()
    widget.widget_type = "textentry"
    widget:SetTemplate(widgetTable.template or widgetTable.button_template or template)
    widget:SetWidth(widgetWidth or widgetTable.width or 120, widgetHeight or widgetTable.height or 18)
    widget:SetCommitFunction(widgetTable.func or widgetTable.set)

    widget:SetHook("OnEnterPressed", function(...)
        local upFunc = widgetTable.func or widgetTable.set
        upFunc(...)
        if (valueChangeHook) then
            valueChangeHook()
        end
    end)

    widget:SetHook("OnEditFocusLost", function(...)
        local upFunc = widgetTable.func or widgetTable.set
        upFunc(...)
        if (valueChangeHook) then
            valueChangeHook()
        end
    end)

    textTemplate = widgetTable.text_template or textTemplate or detailsFramework.font_templates["ORANGE_FONT_TEMPLATE"]
    widget.textcolor = textTemplate.color
    widget.textfont = textTemplate.font
    widget.textsize = textTemplate.size

    --hook list
    if (widgetTable.hooks) then
        for hookName, hookFunc in pairs(widgetTable.hooks) do
            widget:SetHook(hookName, hookFunc)
        end
    end

    setWidgetId(parent, widgetTable, widget)

    local label = widget.hasLabel.widget

    widget:ClearAllPoints()
    label:ClearAllPoints()

    if (bAlignAsPairs) then
        PixelUtil.SetPoint(label, "topleft", widget:GetParent(), "topleft", currentXOffset, currentYOffset)
        PixelUtil.SetPoint(widget.widget, "left", label, "left", nAlignAsPairsLength, 0)

        if (not widget.highlightFrame) then
            local highlightFrame = createOptionHighlightFrame(widget, label, (widgetWidth or 140) + nAlignAsPairsLength + 5)
            widget.highlightFrame = highlightFrame
        end
    else
        widget:SetPoint("left", label, "right", 2, 0)
        label:SetPoint("topleft", parent, "topleft", currentXOffset, currentYOffset)
    end

    local widgetTotalSize = label:GetStringWidth() + 64 --need review, might not be correct
    if (widgetTotalSize > maxColumnWidth) then
        maxColumnWidth = widgetTotalSize
    end

    if (widget:GetWidth() > maxWidgetWidth) then
        maxWidgetWidth = widget:GetWidth()
    end

    onWidgetSetInUse(widget, widgetTable)

    return maxColumnWidth, maxWidgetWidth
end

---disableif is a key which widgetTable can have, if the function assigned to this key returns true, the widget will be disabled, if it returns false, the widget will be enabled, this is checked on menu build and on options refresh
local checkForDisableIF = function(parent)
    for _, widgetTable in ipairs(parent.widget_to_disable_check) do
        if widgetTable.disableif then
            if widgetTable.disableif() == true and widgetTable.widget.Disable then
                widgetTable.widget:Disable()
            else
                if rawget(widgetTable.widget, "lockdown") or (widgetTable.widget.IsEnabled and not widgetTable.widget:IsEnabled()) then
                    widgetTable.widget:Enable()
                end
            end
        end
    end
end

local onMenuBuilt = function(parent)
    --refresh the options to find children to disable or enable
    if (parent.build_menu_options) then
        for index, widgetTable in ipairs(parent.build_menu_options) do
            if (widgetTable.children_follow_enabled) then --not found, bug
                local widget = widgetTable.widget
                local childrenids = widgetTable.childrenids
                if (type(childrenids) == "table") then
                    for i, childId in ipairs(childrenids) do
                        local childWidget = parent:GetWidgetById(childId)
                        if (childWidget) then
                            local value = widget:GetValue()
                            if (widgetTable.children_follow_reverse) then
                                if (value) then
                                    childWidget:Disable()
                                else
                                    childWidget:Enable()
                                end
                            else
                                if (value) then
                                    childWidget:Enable()
                                else
                                    childWidget:Disable()
                                end
                            end
                        end
                    end
                end
            end
        end
    end

    checkForDisableIF(parent)
end

local refreshOptions = function(self) --~refresh
    for _, widget in ipairs(self.widget_list) do
        if (widget._get) then
            if (widget.widget_type == "label") then
                if (widget._get() and not widget.languageAddonId) then
                    widget:SetText(widget._get())
                end

            elseif (widget.widget_type == "select") then
                widget:Select(widget._get())

            elseif (widget.widget_type == "toggle" or widget.widget_type == "range") then
                widget:SetValue(widget._get())

            elseif (widget.widget_type == "textentry") then
                widget:SetText(widget._get())

            elseif (widget.widget_type == "color") then
                local default_value, g, b, a = widget._get()
                if (type(default_value) == "table") then
                    widget:SetColor (unpack(default_value))

                else
                    widget:SetColor (default_value, g, b, a)
                end
            end
        end
    end

    checkForDisableIF(self)

    onMenuBuilt(self)
end

detailsFramework.internalFunctions.RefreshOptionsPanel = refreshOptions

local parseOptionsTypes = function(menuOptions)
    --normalize format types
    for index, widgetTable in ipairs(menuOptions) do
        if (widgetTable.type == "space") then
            widgetTable.type = "blank"

        elseif (widgetTable.type == "fontdropdown") then
            widgetTable.type = "selectfont"
        elseif (widgetTable.type == "texturedropdown") then
            widgetTable.type = "selectstatusbartexture"
        elseif (widgetTable.type == "backgrounddropdown") then
            widgetTable.type = "selectbackgroundtexture"
        elseif (widgetTable.type == "borderdropdown") then
            widgetTable.type = "selectbordertexture"
        elseif (widgetTable.type == "colordropdown") then
            widgetTable.type = "selectcolor"
        elseif (widgetTable.type == "outlinedropdown") then
            widgetTable.type = "selectoutline"
        elseif (widgetTable.type == "anchordropdown") then
            widgetTable.type = "selectanchor"
        elseif (widgetTable.type == "audiodropdown") then
            widgetTable.type = "selectaudio"
        elseif (widgetTable.type == "dropdown") then
            widgetTable.type = "select"

        elseif (widgetTable.type == "switch") then
            widgetTable.type = "toggle"

        elseif (widgetTable.type == "slider") then
            widgetTable.type = "range"

        elseif (widgetTable.type == "button") then
            widgetTable.type = "execute"
        end
    end
end

local parseOptionsTable = function(menuOptions)
    local bUseBoxFirstOnAllWidgets = menuOptions.always_boxfirst
    local widgetWidth = menuOptions.widget_width --a width to be used on all widgets
    local widgetHeight = menuOptions.widget_height --a height to be used on all widgets
    local bAlignAsPairs = menuOptions.align_as_pairs
    local nAlignAsPairsLength = menuOptions.align_as_pairs_string_space or 160
    local nAlignAsPairsSpacing = menuOptions.align_as_pairs_spacing or 20
    local bAttachSliderButtonsToLeft = menuOptions.slider_buttons_to_left

    --if a scrollbox is passed, the height can be ignored
    --the scrollBox child will be used as the parent, and the height of the child will be resized to fit the widgets
    local bUseScrollFrame = menuOptions.use_scrollframe
    local languageAddonId = menuOptions.language_addonId
    return bUseBoxFirstOnAllWidgets, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, nAlignAsPairsSpacing, bUseScrollFrame, languageAddonId, bAttachSliderButtonsToLeft
end

local parseParent = function(bUseScrollFrame, parent, height, yOffset)
    if (bUseScrollFrame) then
        local width, height = parent:GetSize()
        parent = parent:GetScrollChild()
        parent:SetSize(width, height)
    else
        if (height and type(height) == "number") then
            height = math.abs((height or parent:GetHeight()) - math.abs(yOffset) + 20)
            height = height * -1
        else
            height = parent:GetHeight()
        end
    end

    return parent, height
end

local parseLanguageTable = function(languageAddonId)
    local languageTable
    if (languageAddonId) then
        languageTable = DetailsFramework.Language.GetLanguageTable(languageAddonId)
    end
    return languageTable
end

local getFrameById = function(self, id)
    return self.widgetids[id]
end

function detailsFramework:ClearOptionsPanel(frame)
    for i = 1, #frame.widget_list do
        frame.widget_list[i]:Hide()
        if (frame.widget_list[i].hasLabel) then
            frame.widget_list[i].hasLabel:SetText("")
        end
    end
    --hide group frames
    if frame.widget_list_by_type and frame.widget_list_by_type["group"] then
        for i = 1, #frame.widget_list_by_type["group"] do
            frame.widget_list_by_type["group"][i]:Hide()
        end
    end
    table.wipe(frame.widgetids)
end

function detailsFramework:SetAsOptionsPanel(frame)
    --print("refresh_options", refresh_options)
    frame.RefreshOptions = refreshOptions
    frame.widget_list = {}
    frame.widget_list_by_type = {
        ["dropdown"] = {}, --"select"
        ["switch"] = {}, --"toggle"
        ["slider"] = {}, --"range"
        ["color"] = {}, --
        ["button"] = {}, --"execute"
        ["textentry"] = {}, --
        ["label"] = {}, --"text"
        ["image"] = {},
        ["group"] = {},
    }
    frame.widgetids = {}
    --store widgets which has a disable function (widgetTable.disableif)
    frame.widget_to_disable_check = {}
    frame.GetWidgetById = getFrameById
end

local widgetsToDisableOnCombat = {}

---get or create a group frame from the pool for volatile menus, or create a new one for static menus
---@param parent frame the parent frame for the group frame
---@param groupName string the group identifier
---@param widgetTable df_menu_group the widget table with group settings
---@param isVolatile boolean if true, use the pool from parent.widget_list_by_type.group
---@param indexTable table? the volatile index table with pool counters
---@return frame groupFrame
local getOrCreateGroupFrame = function(parent, groupName, widgetTable, isVolatile, indexTable)
    local groupFrame

    if isVolatile then
        local poolIndex = indexTable["group"]
        groupFrame = parent.widget_list_by_type["group"][poolIndex]
        if not groupFrame then
            groupFrame = CreateFrame("frame", nil, parent, "BackdropTemplate")
            groupFrame.backgroundTexture = groupFrame:CreateTexture(nil, "background")
            groupFrame.backgroundTexture:SetAllPoints()
            table.insert(parent.widget_list_by_type["group"], groupFrame)
        end
        indexTable["group"] = poolIndex + 1
    else
        groupFrame = CreateFrame("frame", nil, parent, "BackdropTemplate")
        groupFrame.backgroundTexture = groupFrame:CreateTexture(nil, "background")
        groupFrame.backgroundTexture:SetAllPoints()
        table.insert(parent.widget_list_by_type["group"], groupFrame)
    end

    groupFrame.groupName = groupName
    groupFrame:SetFrameLevel(parent:GetFrameLevel())
    groupFrame:ClearAllPoints()
    groupFrame:Hide()

    local color = widgetTable.color or {0, 0, 0, 0.3}

    if widgetTable.UseBackdrop then
        groupFrame:SetBackdrop(widgetTable.UseBackdrop)
        groupFrame:Show()
        groupFrame.backgroundTexture:Hide()

        local bgColor = widgetTable.BackgroundColor or color
        groupFrame:SetBackdropColor(bgColor[1] or 0, bgColor[2] or 0, bgColor[3] or 0, bgColor[4] or 1)

        local borderColor = widgetTable.BackdropBorderColor or {0.3, 0.3, 0.3, 0.8}
        groupFrame:SetBackdropBorderColor(borderColor[1] or 0.3, borderColor[2] or 0.3, borderColor[3] or 0.3, borderColor[4] or 0.8)
    else
        groupFrame:SetBackdrop(nil)
        groupFrame.backgroundTexture:SetColorTexture(color[1] or 0, color[2] or 0, color[3] or 0, color[4] or 0.3)
        groupFrame.backgroundTexture:Show()
    end

    return groupFrame
end

---after the build loop, compute group frame positions and reparent widgets
---@param parent frame
---@param menuOptions table
local applyGroupFrames = function(parent, menuOptions)
    --search for groups definitions and group members
    ---@type table<string, frame>
    local groupFrames = {}
    ---@type table<string, df_menu_group>
    local groupSettings = {}
    ---@type table<string, table[]>
    local groupWidgets = {}

    for _, widgetTable in ipairs(menuOptions) do
        if widgetTable.type == "group" and widgetTable.widget then
            groupFrames[widgetTable.name] = widgetTable.widget
            groupSettings[widgetTable.name] = widgetTable
        end
    end

    --no groups found
    if not next(groupFrames) then
        return
    end

    --collect widgets that belong to each group
    for _, widgetTable in ipairs(menuOptions) do
        if widgetTable.group and widgetTable.type ~= "group" and widgetTable.widget then
            if not groupWidgets[widgetTable.group] then
                groupWidgets[widgetTable.group] = {}
            end
            table.insert(groupWidgets[widgetTable.group], widgetTable)
        end
    end

    ---get the bounding box of a region, returns left, right, top, bottom or nil
    ---for fontstrings (labels), uses GetStringWidth/GetStringHeight + anchor to compute bounds
    ---@param region any
    ---@return number|nil, number|nil, number|nil, number|nil
    local getRegionBounds = function(region)
        local left = region:GetLeft()
        local right = region:GetRight()
        local top = region:GetTop()
        local bottom = region:GetBottom()

        --fontstrings may not have valid bounds yet, attempt to compute from anchor and string size
        if not left or not right or not top or not bottom then
            if region.GetStringWidth then
                local strWidth = region:GetStringWidth()
                local strHeight = region:GetStringHeight()
                if strWidth and strHeight and strWidth > 0 and strHeight > 0 then
                    local numPoints = region:GetNumPoints()
                    if numPoints and numPoints > 0 then
                        local point, relativeTo, relativePoint, xOfs, yOfs = region:GetPoint(1)
                        if relativeTo then
                            local refLeft = relativeTo:GetLeft()
                            local refTop = relativeTo:GetTop()
                            if refLeft and refTop then
                                left = refLeft + (xOfs or 0)
                                top = refTop + (yOfs or 0)
                                right = left + strWidth
                                bottom = top - strHeight
                            end
                        end
                    end
                end
            end
        end

        return left, right, top, bottom
    end

    --for each group, compute where the topleft and bottomright is
    for groupName, groupFrame in pairs(groupFrames) do
        local members = groupWidgets[groupName]
        if not members or #members == 0 then
            --no widgets reference this group, hide it
            groupFrame:Hide()
        else
            local settings = groupSettings[groupName]

            --find bounding box by examining all widget frames
            local topY = -math.huge --highest Y (least negative in WoW coords)
            local bottomY = math.huge --lowest Y (most negative)
            local leftX = math.huge --leftmost X
            local rightX = -math.huge --rightmost X

            for _, memberTable in ipairs(members) do
                local widget = memberTable.widget
                local frame = widget.widget or widget

                --get the widget frame's bounding box
                local widgetLeft, widgetRight, widgetTop, widgetBottom = getRegionBounds(frame)

                if widgetLeft and widgetRight and widgetTop and widgetBottom then
                    --also check the label if it exists (non-label widgets like toggles, sliders, etc.)
                    local labelLeft, labelRight, labelTop, labelBottom
                    if widget.hasLabel then
                        local labelFrame = widget.hasLabel.widget or widget.hasLabel
                        labelLeft, labelRight, labelTop, labelBottom = getRegionBounds(labelFrame)
                    end

                    local effectiveLeft = widgetLeft
                    local effectiveRight = widgetRight
                    local effectiveTop = widgetTop
                    local effectiveBottom = widgetBottom

                    if labelLeft and labelLeft < effectiveLeft then
                        effectiveLeft = labelLeft
                    end
                    if labelRight and labelRight > effectiveRight then
                        effectiveRight = labelRight
                    end
                    if labelTop and labelTop > effectiveTop then
                        effectiveTop = labelTop
                    end
                    if labelBottom and labelBottom < effectiveBottom then
                        effectiveBottom = labelBottom
                    end

                    if effectiveLeft < leftX then
                        leftX = effectiveLeft
                    end
                    if effectiveRight > rightX then
                        rightX = effectiveRight
                    end
                    if effectiveTop > topY then
                        topY = effectiveTop
                    end
                    if effectiveBottom < bottomY then
                        bottomY = effectiveBottom
                    end
                end
            end

            if topY ~= -math.huge and bottomY ~= math.huge then
                --get parent absolute position for relative anchoring
                local parentLeft = parent:GetLeft() or 0
                local parentTop = parent:GetTop() or 0

                local relLeft = leftX - parentLeft - 4
                local relTop = -(topY - parentTop) - 4

                --compute dimensions: use fixed width/height from settings if provided
                local computedWidth = (rightX - leftX) + 8
                local computedHeight = (topY - bottomY) + 8

                local finalWidth = settings.width or computedWidth
                local finalHeight = settings.height or computedHeight
                local padding = settings.padding or 0

                groupFrame:SetPoint("topleft", parent, "topleft", relLeft - (padding), -relTop + (padding))
                groupFrame:SetSize(finalWidth + (padding * 2), finalHeight + (padding * 2))
                groupFrame:Show()

                --reparent member widgets to the group frame so they render on top of the background
                for _, memberTable in ipairs(members) do
                    local widget = memberTable.widget
                    local frame = widget.widget or widget
                    frame:SetParent(groupFrame)

                    --only set frame level on actual frames, not fontstrings
                    if frame.SetFrameLevel then
                        frame:SetFrameLevel(groupFrame:GetFrameLevel() + 2)
                    end

                    if widget.hasLabel then
                        local labelFrame = widget.hasLabel.widget or widget.hasLabel
                        labelFrame:SetParent(groupFrame)
                    end
                end
            end
        end
    end
end

local getMenuWidgetVolative = function(parent, widgetType, indexTable)
    local widgetObject

    if (widgetType == "label") then
        widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]]
        if (not widgetObject) then
            widgetObject = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType], "overlay")
            table.insert(parent.widget_list, widgetObject)
            table.insert(parent.widget_list_by_type[widgetType], widgetObject)
        end
        indexTable[widgetType] = indexTable[widgetType] + 1

    elseif (widgetType == "dropdown") then
        widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]]
        if (not widgetObject) then
            widgetObject = detailsFramework:CreateDropDown(parent, function() return {} end, nil, 120, 18, nil, "$parentWidget" .. widgetType .. indexTable[widgetType])
            widgetObject.hasLabel = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay")
            table.insert(parent.widget_list, widgetObject)
            table.insert(parent.widget_list_by_type[widgetType], widgetObject)

        else
            widgetObject:ClearHooks()
            widgetObject.hasLabel.text = ""
        end
        indexTable[widgetType] = indexTable[widgetType] + 1

    elseif (widgetType == "switch") then
        widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]]
        if (not widgetObject) then
            widgetObject = detailsFramework:CreateSwitch(parent, nil, true, 20, 20, nil, nil, nil, "$parentWidget" .. widgetType .. indexTable[widgetType])
            widgetObject.hasLabel = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay")

            table.insert(parent.widget_list, widgetObject)
            table.insert(parent.widget_list_by_type[widgetType], widgetObject)
        else
            widgetObject:ClearHooks()
        end
        indexTable[widgetType] = indexTable[widgetType] + 1

    elseif (widgetType == "slider") then
        widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]]
        if (not widgetObject) then
            widgetObject = detailsFramework:CreateSlider(parent, 120, 20, 1, 2, 1, 1, false, nil, "$parentWidget" .. widgetType .. indexTable[widgetType])
            widgetObject.hasLabel = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay")

            table.insert(parent.widget_list, widgetObject)
            table.insert(parent.widget_list_by_type[widgetType], widgetObject)
        else
            widgetObject:ClearHooks()
        end
        indexTable[widgetType] = indexTable[widgetType] + 1

    elseif (widgetType == "color") then
        widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]]
        if (not widgetObject) then
            widgetObject = detailsFramework:CreateColorPickButton(parent, "$parentWidget" .. widgetType .. indexTable[widgetType], nil, function()end, 1)
            widgetObject.hasLabel = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay")

            table.insert(parent.widget_list, widgetObject)
            table.insert(parent.widget_list_by_type[widgetType], widgetObject)
        else
            widgetObject:ClearHooks()
        end
        indexTable[widgetType] = indexTable[widgetType] + 1

    elseif (widgetType == "button") then
        widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]]
        if (not widgetObject) then
            widgetObject = detailsFramework:CreateButton(parent, function()end, 120, 18, "", nil, nil, nil, nil, "$parentWidget" .. widgetType .. indexTable[widgetType])
            widgetObject.hasLabel = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay")

            table.insert(parent.widget_list, widgetObject)
            table.insert(parent.widget_list_by_type[widgetType], widgetObject)
        else
            widgetObject:ClearHooks()
        end
        indexTable[widgetType] = indexTable[widgetType] + 1

    elseif (widgetType == "textentry") then
        widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]]
        if (not widgetObject) then
            widgetObject = detailsFramework:CreateTextEntry(parent, function()end, 120, 18, nil, "$parentWidget" .. widgetType .. indexTable[widgetType])
            widgetObject.hasLabel = detailsFramework:CreateLabel(parent, "", 10, "white", "", nil, "$parentWidget" .. widgetType .. indexTable[widgetType] .. "label", "overlay")

            table.insert(parent.widget_list, widgetObject)
            table.insert(parent.widget_list_by_type[widgetType], widgetObject)
        else
            widgetObject:ClearHooks()
        end
        indexTable[widgetType] = indexTable[widgetType] + 1

    elseif (widgetType == "image") then
        widgetObject = parent.widget_list_by_type[widgetType][indexTable[widgetType]]
        if (not widgetObject) then
            widgetObject = parent:CreateTexture("$parentWidget" .. widgetType .. indexTable[widgetType], "overlay")
            table.insert(parent.widget_list, widgetObject)
            table.insert(parent.widget_list_by_type[widgetType], widgetObject)
        end
        indexTable[widgetType] = indexTable[widgetType] + 1
    end

    --if the widget is inside the no combat table, remove it
    for i = 1, #widgetsToDisableOnCombat do
        if (widgetsToDisableOnCombat[i] == widgetObject) then
            table.remove(widgetsToDisableOnCombat, i)
            break
        end
    end

    --clean children ids, children ids are used to disable or enable other widgets when a widget is disabled or enabled
    if (widgetObject.childrenids) then
        table.wipe(widgetObject.childrenids)
    end
    widgetObject.children_follow_enabled = nil

    return widgetObject
end

--get the description phrase from the language table or use the .desc or .deschraseid
local getDescPhraseText = function(languageTable, widgetTable)
    local descPhraseId = languageTable and (languageTable[widgetTable.descPhraseId] or languageTable[widgetTable.desc])
    return descPhraseId or widgetTable.descPhraseId or widgetTable.desc or widgetTable.name or "-?-"
end

--volatile menu can be called several times, each time all settings are reset and a new menu is built reusing the widgets
function detailsFramework:BuildMenuVolatile(parent, menuOptions, xOffset, yOffset, height, useColon, textTemplate, dropdownTemplate, switchTemplate, switchIsCheckbox, sliderTemplate, buttonTemplate, valueChangeHook)
    bHighlightColorOne = true

    local amountLineWidgetAdded = 0
    local biggestColumnHeight = 0 --used to resize the scrollbox child when a scrollbox is passed
    local latestInlineWidget
    local currentXOffset = xOffset or 0
    local currentYOffset = yOffset or 0
    local maxColumnWidth = 0 --biggest width of widget + text size on the current column loop pass
    local maxWidgetWidth = 0 --biggest widget width on the current column loop pass
    local canvasFrame = parent

    --which is the next widget to get from the pool
    local widgetIndexes = {
        label = 1,
        dropdown = 1,
        switch = 1,
        slider = 1,
        color = 1,
        button = 1,
        textentry = 1,
        group = 1,
    }

    parseOptionsTypes(menuOptions)

    local bUseBoxFirstOnAllWidgets, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, nAlignAsPairsSpacing, bUseScrollFrame, languageAddonId, bAttachSliderButtonsToLeft = parseOptionsTable(menuOptions)
    parent, height = parseParent(bUseScrollFrame, parent, height, yOffset)
    local languageTable = parseLanguageTable(languageAddonId)

    if (not parent.widget_list) then
        detailsFramework:SetAsOptionsPanel(parent)
    end
    table.wipe(parent.widget_to_disable_check)

    detailsFramework:ClearOptionsPanel(parent)

    local userValueChangeHook = valueChangeHook
    local refreshTimer
    valueChangeHook = function()
        if userValueChangeHook then
            userValueChangeHook()
        end

        if menuOptions.no_refresh_on_change then
            return
        end

        if refreshTimer then
            return
        else
            refreshTimer = C_Timer.NewTimer(0.05, function()
                refreshTimer = nil
                parent:RefreshOptions()
            end)
        end
    end

    parent.build_menu_options = menuOptions

    for index, widgetTable in ipairs(menuOptions) do
        if (not widgetTable.hidden) then
            local widgetCreated
            if (latestInlineWidget) then
                if (not widgetTable.inline) then
                    latestInlineWidget = nil
                    currentYOffset = currentYOffset - 20
                end
            end

            local extraPaddingY = 0
            local jumpToNextLine = true --if true the Y offset is increased

            if (not widgetTable.novolatile) then
                --step a line
                if (widgetTable.type == "blank" or widgetTable.type == "space") then
                    --do nothing

                elseif (widgetTable.type == "label" or widgetTable.type == "text") then
                    ---@cast widgetTable df_menu_label
                    local label = getMenuWidgetVolative(parent, "label", widgetIndexes)
                    label:ClearAllPoints()
                    widgetCreated = label

                    setLabelProperties(parent, label, widgetTable, currentXOffset, currentYOffset, textTemplate)

                    local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable)
                    local namePhrase = (languageTable and (languageTable[namePhraseId] or languageTable[widgetTable.namePhraseId] or languageTable[widgetTable.name])) or (widgetTable.get and widgetTable.get()) or widgetTable.text or (widgetTable.namePhraseId) or ""
                    label.text = namePhrase
                    label.color = widgetTable.color

                    amountLineWidgetAdded = amountLineWidgetAdded + 1

                --dropdowns
                elseif (widgetTable.type:find("select")) then
                    ---@cast widgetTable df_menu_dropdown
                    assert(widgetTable.get, "DetailsFramework:BuildMenu: .get() not found in the widget table for 'select'")
                    local dropdown = getMenuWidgetVolative(parent, "dropdown", widgetIndexes)
                    dropdown:SetTemplate(dropdownTemplate)
                    widgetCreated = dropdown
                    local defaultHeight = 18

                    do
                        if (widgetTable.type == "selectfont") then
                            local func = detailsFramework:CreateFontListGenerator(widgetTable.set, widgetTable.include_default)
                            dropdown:SetFunction(func)

                        elseif (widgetTable.type == "selectstatusbartexture") then
                            local func = detailsFramework:CreateStatusbarTextureListGenerator(widgetTable.set)
                            dropdown:SetFunction(func)

                        elseif (widgetTable.type == "selectbackgroundtexture") then
                            local func = detailsFramework:CreateBackgroundListGenerator(widgetTable.set, widgetTable.include_default)
                            dropdown:SetFunction(func)

                        --frame strata
                        elseif (widgetTable.type == "selectframestrata") then
                            local func = detailsFramework:CreateFrameStrataListGenerator(widgetTable.set)
                            dropdown:SetFunction(func)

                        elseif (widgetTable.type == "selectcolor") then
                            local func = detailsFramework:CreateColorListGenerator(widgetTable.set)
                            dropdown:SetFunction(func)

                        elseif (widgetTable.type == "selectanchor") then
                            local func = detailsFramework:CreateAnchorPointListGenerator(widgetTable.set)
                            dropdown:SetFunction(func)

                        elseif (widgetTable.type == "selectoutline") then
                            local func = detailsFramework:CreateOutlineListGenerator(widgetTable.set)
                            dropdown:SetFunction(func)

                        elseif (widgetTable.type == "selectaudio") then
                            local func = detailsFramework:CreateAudioListGenerator(widgetTable.set)
                            dropdown:SetFunction(func)
                        else
                            dropdown:SetFunction(widgetTable.values)
                        end
                    end

                    local descPhrase = getDescPhraseText(languageTable, widgetTable)
                    dropdown:SetTooltip(descPhrase)

                    processLabelIcon(dropdown.hasLabel, widgetTable, languageTable, widgetTable.text_template or textTemplate, useColon, languageAddonId)

                    maxColumnWidth, maxWidgetWidth = setDropdownProperties(parent, dropdown, widgetTable, currentXOffset, currentYOffset, textTemplate, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth)
                    amountLineWidgetAdded = amountLineWidgetAdded + 1

                --switchs
                elseif (widgetTable.type == "toggle" or widgetTable.type == "switch") then
                    ---@cast widgetTable df_menu_toggle
                    local switch = getMenuWidgetVolative(parent, "switch", widgetIndexes)
                    widgetCreated = switch

                    local descPhrase = getDescPhraseText(languageTable, widgetTable)
                    switch:SetTooltip(descPhrase)

                    processLabelIcon(switch.hasLabel, widgetTable, languageTable, widgetTable.text_template or textTemplate, useColon, languageAddonId)

                    maxColumnWidth, maxWidgetWidth, extraPaddingY = setToggleProperties(parent, switch, widgetTable, currentXOffset, currentYOffset, switchTemplate, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, true,             bUseBoxFirstOnAllWidgets, menuOptions, index, maxWidgetWidth)
                    amountLineWidgetAdded = amountLineWidgetAdded + 1

                --slider
                elseif (widgetTable.type == "range") then
                    ---@cast widgetTable df_menu_range
                    local slider = getMenuWidgetVolative(parent, "slider", widgetIndexes)
                    widgetCreated = slider

                    local descPhrase = getDescPhraseText(languageTable, widgetTable)
                    slider:SetTooltip(descPhrase)

                    processLabelIcon(slider.hasLabel, widgetTable, languageTable, widgetTable.text_template or textTemplate, useColon, languageAddonId)

                    maxColumnWidth, maxWidgetWidth = setRangeProperties(parent, slider, widgetTable, currentXOffset, currentYOffset, sliderTemplate, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth, widgetTable.usedecimals, bAttachSliderButtonsToLeft)
                    slider:SetTemplate(sliderTemplate)
                    amountLineWidgetAdded = amountLineWidgetAdded + 1

                --color
                elseif (widgetTable.type == "color") then
                    ---@cast widgetTable df_menu_color
                    local colorpick = getMenuWidgetVolative(parent, "color", widgetIndexes)
                    widgetCreated = colorpick

                    local descPhrase = getDescPhraseText(languageTable, widgetTable)
                    colorpick:SetTooltip(descPhrase)

                    processLabelIcon(colorpick.hasLabel, widgetTable, languageTable, widgetTable.text_template or textTemplate, useColon, languageAddonId)

                    maxColumnWidth, maxWidgetWidth, extraPaddingY = setColorProperties(parent, colorpick, widgetTable, currentXOffset, currentYOffset, switchTemplate, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth, bUseBoxFirstOnAllWidgets, extraPaddingY)
                    amountLineWidgetAdded = amountLineWidgetAdded + 1

                --button
                elseif (widgetTable.type == "execute" or widgetTable.type == "button") then
                    ---@cast widgetTable df_menu_button
                    local button = getMenuWidgetVolative(parent, "button", widgetIndexes)
                    button.widget_type = "execute"
                    widgetCreated = button

                    maxColumnWidth, maxWidgetWidth, latestInlineWidget = setExecuteProperties(parent, button, widgetTable, currentXOffset, currentYOffset, buttonTemplate, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth, textTemplate, latestInlineWidget)

                    local namePhrase = getNamePhraseText(languageTable, widgetTable, useColon, languageAddonId)
                    button.text = namePhrase

                    local descPhrase = getDescPhraseText(languageTable, widgetTable)
                    button:SetTooltip(descPhrase)
                    amountLineWidgetAdded = amountLineWidgetAdded + 1

                --textentry
                elseif (widgetTable.type == "textentry") then
                    ---@cast widgetTable df_menu_textentry
                    local textentry = getMenuWidgetVolative(parent, "textentry", widgetIndexes)
                    widgetCreated = textentry

                    local descPhrase = getDescPhraseText(languageTable, widgetTable)
                    textentry:SetTooltip(descPhrase)

                    processLabelIcon(textentry.hasLabel, widgetTable, languageTable, widgetTable.text_template or textTemplate, useColon, languageAddonId)

                    maxColumnWidth, maxWidgetWidth = setTextEntryProperties(parent, textentry, widgetTable, currentXOffset, currentYOffset, buttonTemplate, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth, textTemplate)
                    amountLineWidgetAdded = amountLineWidgetAdded + 1

                --image
                elseif (widgetTable.type == "image") then
                    ---@cast widgetTable df_menu_texture
                    local image = getMenuWidgetVolative(parent, "image", widgetIndexes)
                    widgetCreated = image

                    setImageProperties(parent, image, widgetTable, currentXOffset, currentYOffset)

                    amountLineWidgetAdded = amountLineWidgetAdded + 1
                end --end loop

                --group type: create/reuse the group frame, it does not advance the Y offset
                if (widgetTable.type == "group") then
                    ---@cast widgetTable df_menu_group
                    local groupFrame = getOrCreateGroupFrame(parent, widgetTable.name, widgetTable, true, widgetIndexes)
                    widgetCreated = groupFrame
                    setWidgetId(parent, widgetTable, groupFrame)
                    jumpToNextLine = false
                end

                if languageAddonId and widgetCreated then
                    widgetCreated.__languageAddonId = languageAddonId
                end

                if (widgetTable.nocombat) then
                    table.insert(widgetsToDisableOnCombat, widgetCreated)
                end

                if jumpToNextLine then
                    if (not widgetTable.inline) then
                        if (widgetTable.spacement) then
                            currentYOffset = currentYOffset - 30
                        else
                            currentYOffset = currentYOffset - 20
                        end
                    end

                    if (extraPaddingY > 0) then
                        currentYOffset = currentYOffset - extraPaddingY
                    end

                    widgetTable.widget = widgetCreated
                    if widgetTable.disableif then
                        table.insert(parent.widget_to_disable_check, widgetTable)
                    end

                    if (bUseScrollFrame) then
                        if (widgetTable.type == "breakline") then
                            biggestColumnHeight = math.min(currentYOffset, biggestColumnHeight)
                            currentYOffset = yOffset

                            if (bAlignAsPairs) then
                                currentXOffset = currentXOffset + nAlignAsPairsLength + (widgetWidth or maxWidgetWidth) + nAlignAsPairsSpacing
                            else
                                currentXOffset = currentXOffset + maxColumnWidth + 20
                            end

                            amountLineWidgetAdded = 0
                            maxColumnWidth = 0
                            maxWidgetWidth = 0
                        end
                    else
                        if (widgetTable.type == "breakline" or currentYOffset < height) then
                            currentYOffset = yOffset
                            currentXOffset = currentXOffset + maxColumnWidth + 20
                            amountLineWidgetAdded = 0
                            maxColumnWidth = 0
                        end
                    end
                end

                if widgetCreated then
                    widgetCreated:Show()
                end
            end
        end
    end

    if (bUseScrollFrame) then
        canvasFrame:GetParent().RefreshOptions = function()
            parent:RefreshOptions()
        end
    end

    detailsFramework.RefreshUnsafeOptionsWidgets()
    onMenuBuilt(parent)

    parent:RefreshOptions()

    applyGroupFrames(parent, menuOptions)
end

local getDescripttionPhraseID = function(widgetTable, languageAddonId, languageTable)
    if (widgetTable.descPhraseId) then
        return widgetTable.descPhraseId
    end

    if (not languageTable) then
        return
    end

    local hasValue = detailsFramework.Language.DoesPhraseIDExistsInDefaultLanguage(languageAddonId, widgetTable.desc)
    if (not hasValue) then
        return
    end

    return widgetTable.desc
end




---classes used by the menu builder on the menuOptions table on both functions BuildMenu and BuildMenuVolatile
---the menuOptions consists of a table with several tables inside in array, each table is a widget to be created
---class df_menu_label is used when the sub table of menuOptions has a key named "type" with the value "label" or "text"

--[=[
function detailsFramework:BuildMenu(parent, menuOptions, xOffset, yOffset, height, useColon, textTemplate, dropdownTemplate, switchTemplate, switchIsCheckbox, sliderTemplate, buttonTemplate, valueChangeHook)
    local tNow = debugprofilestop()
    detailsFramework:BuildMenu22(parent, menuOptions, xOffset, yOffset, height, useColon, textTemplate, dropdownTemplate, switchTemplate, switchIsCheckbox, sliderTemplate, buttonTemplate, valueChangeHook)
    local tEnd = debugprofilestop()
    print("BuildMenu for", (menuOptions.Name or "--"), floor(tEnd - tNow), "ms")
end
--]=]

function detailsFramework:BuildMenu(parent, menuOptions, xOffset, yOffset, height, useColon, textTemplate, dropdownTemplate, switchTemplate, switchIsCheckbox, sliderTemplate, buttonTemplate, valueChangeHook)
    --how many widgets has been created on this line loop pass
    local amountLineWidgetAdded = 0
    local biggestColumnHeight = 0 --used to resize the scrollbox child when a scrollbox is passed
    local latestInlineWidget
    local currentXOffset = xOffset or 0
    local currentYOffset = yOffset or 0
    local maxColumnWidth = 0 --biggest width of widget + text size on the current column loop pass
    local maxWidgetWidth = 0 --biggest widget width on the current column loop pass
    local canvasFrame = parent

    textTemplate, dropdownTemplate, switchTemplate, sliderTemplate, buttonTemplate, switchIsCheckbox = parseTemplates(textTemplate, dropdownTemplate, switchTemplate, sliderTemplate, buttonTemplate, switchIsCheckbox)

    bHighlightColorOne = true

    --parse settings and the options table
    parseOptionsTypes(menuOptions)

    local bUseBoxFirstOnAllWidgets, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, nAlignAsPairsSpacing, bUseScrollFrame, languageAddonId, bAttachSliderButtonsToLeft = parseOptionsTable(menuOptions)
    parent, height = parseParent(bUseScrollFrame, parent, height, yOffset)
    local languageTable = parseLanguageTable(languageAddonId)

    parent.build_menu_options = menuOptions

    if (not parent.widget_list) then
        detailsFramework:SetAsOptionsPanel(parent)
    end

    local userValueChangeHook = valueChangeHook
    local refreshTimer
    valueChangeHook = function()
        if userValueChangeHook then
            userValueChangeHook()
        end

        if menuOptions.no_refresh_on_change then
            return
        end

        if refreshTimer then
            return
        else
            refreshTimer = C_Timer.NewTimer(0.1, function()
                refreshTimer = nil
                parent:RefreshOptions()
            end)
        end
    end

    for index, widgetTable in ipairs(menuOptions) do
        if (not widgetTable.hidden) then
            local widgetCreated
            if (latestInlineWidget) then
                if (not widgetTable.inline) then
                    latestInlineWidget = nil
                    currentYOffset = currentYOffset - 28
                end
            end

            local extraPaddingY = 0
            local jumpToNextLine = true

            if (widgetTable.type == "blank") then
                --do nothing

            elseif (widgetTable.type == "label" or widgetTable.type == "text") then
                ---@cast widgetTable df_menu_label

                local label = detailsFramework:CreateLabel(parent, "", widgetTable.text_template or textTemplate or widgetTable.size, widgetTable.color, widgetTable.font, nil, "$parentWidget" .. index, "overlay")
                setLabelProperties(parent, label, widgetTable, currentXOffset, currentYOffset)

                local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable)
                if (namePhraseId) then
                    DetailsFramework.Language.RegisterObject(languageAddonId, label.widget, namePhraseId)
                    label.languageAddonId = languageAddonId
                else
                    local textToSet = (widgetTable.get and widgetTable.get()) or widgetTable.text or ""
                    label:SetText(textToSet)
                end

                --store the widget created into the overall table and the widget by type
                table.insert(parent.widget_list, label)
                table.insert(parent.widget_list_by_type.label, label)

                widgetCreated = label
                amountLineWidgetAdded = amountLineWidgetAdded + 1

            elseif (widgetTable.type:find("select")) then
                ---@cast widgetTable df_menu_dropdown
                assert(widgetTable.get, "DetailsFramework:BuildMenu: .get not found in the widget table for 'select'")
                local defaultHeight = 18

                local dropdown
                do
                    if (widgetTable.type == "selectfont") then
                        dropdown = detailsFramework:CreateFontDropDown(parent, widgetTable.set, widgetTable.get(), widgetWidth or 140, widgetHeight or defaultHeight, nil, "$parentWidget" .. index, dropdownTemplate, widgetTable.include_default)

                    elseif (widgetTable.type == "selectstatusbartexture") then
                        dropdown = detailsFramework:CreateStatusbarTextureDropDown(parent, widgetTable.set, widgetTable.get(), widgetWidth or 140, widgetHeight or defaultHeight, nil, "$parentWidget" .. index, dropdownTemplate)

                    elseif (widgetTable.type == "selectbackgroundtexture") then
                        dropdown = detailsFramework:CreateBackgroundDropDown(parent, widgetTable.set, widgetTable.get(), widgetWidth or 140, widgetHeight or defaultHeight, nil, "$parentWidget" .. index, dropdownTemplate, widgetTable.include_default)

                    elseif (widgetTable.type == "selectframestrata") then
                        dropdown = detailsFramework:CreateFrameStrataDropDown(parent, widgetTable.set, widgetTable.get(), widgetWidth or 140, widgetHeight or defaultHeight, nil, "$parentWidget" .. index, dropdownTemplate)

                    elseif (widgetTable.type == "selectcolor") then
                        dropdown = detailsFramework:CreateColorDropDown(parent, widgetTable.set, widgetTable.get(), widgetWidth or 140, widgetHeight or defaultHeight, nil, "$parentWidget" .. index, dropdownTemplate)

                    elseif (widgetTable.type == "selectanchor") then
                        dropdown = detailsFramework:CreateAnchorPointDropDown(parent, widgetTable.set, widgetTable.get(), widgetWidth or 140, widgetHeight or defaultHeight, nil, "$parentWidget" .. index, dropdownTemplate)

                    elseif (widgetTable.type == "selectoutline") then
                        dropdown = detailsFramework:CreateOutlineDropDown(parent, widgetTable.set, widgetTable.get(), widgetWidth or 140, widgetHeight or defaultHeight, nil, "$parentWidget" .. index, dropdownTemplate)

                    elseif (widgetTable.type == "selectaudio") then
                        dropdown = detailsFramework:CreateAudioDropDown(parent, widgetTable.set, widgetTable.get(), widgetWidth or 140, widgetHeight or defaultHeight, nil, "$parentWidget" .. index, dropdownTemplate)
                    else
                        dropdown = detailsFramework:NewDropDown(parent, nil, "$parentWidget" .. index, nil, widgetWidth or 140, widgetHeight or defaultHeight, widgetTable.values, widgetTable.get(), dropdownTemplate)
                    end
                end

                local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable)
                DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, dropdown, "have_tooltip", descPhraseId, widgetTable.desc)
                local label = detailsFramework:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12)
                dropdown.hasLabel = label

                local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable, true)
                DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon))
                dropdown.addonId = languageAddonId

                processLabelIcon(dropdown.hasLabel, widgetTable, languageTable, widgetTable.text_template or textTemplate, useColon, languageAddonId)

                if (languageAddonId) then
                    detailsFramework.Language.RegisterCallback(languageAddonId, function(addonId, languageId, ...) dropdown:Select(dropdown:GetValue()) end)
                    C_Timer.After(0.1, function() dropdown:Select(dropdown:GetValue()) end)
                end

                maxColumnWidth, maxWidgetWidth = setDropdownProperties(parent, dropdown, widgetTable, currentXOffset, currentYOffset, textTemplate, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth)

                --store the widget created into the overall table and the widget by type
                table.insert(parent.widget_list, dropdown)
                table.insert(parent.widget_list_by_type.dropdown, dropdown)

                widgetCreated = dropdown
                amountLineWidgetAdded = amountLineWidgetAdded + 1

            elseif (widgetTable.type == "toggle") then
                ---@cast widgetTable df_menu_toggle

                local switch = detailsFramework:NewSwitch(parent, nil, "$parentWidget" .. index, nil, 60, 20, nil, nil, widgetTable.get(), nil, nil, nil, nil, switchTemplate)

                local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable)
                DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, switch, "have_tooltip", descPhraseId, widgetTable.desc)

                local label = detailsFramework:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12)
                switch.hasLabel = label

                local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable, true)
                DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon))

                processLabelIcon(switch.hasLabel, widgetTable, languageTable, widgetTable.text_template or textTemplate, useColon, languageAddonId)

                maxColumnWidth, maxWidgetWidth, extraPaddingY = setToggleProperties(parent, switch, widgetTable, currentXOffset, currentYOffset, switchTemplate, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, switchIsCheckbox, bUseBoxFirstOnAllWidgets, menuOptions, index, maxWidgetWidth)

                --store the widget created into the overall table and the widget by type
                table.insert(parent.widget_list, switch)
                table.insert(parent.widget_list_by_type.switch, switch)

                widgetCreated = switch
                amountLineWidgetAdded = amountLineWidgetAdded + 1

            elseif (widgetTable.type == "range") then
                ---@cast widgetTable df_menu_range

                assert(widgetTable.get, "DetailsFramework:BuildMenu: .get not found in the widget table for 'range'")
                local bIsDecimals = widgetTable.usedecimals
                local slider = detailsFramework:NewSlider(parent, nil, "$parentWidget" .. index, nil, widgetWidth or 140, widgetHeight or 18, widgetTable.min, widgetTable.max, widgetTable.step, widgetTable.get(),  bIsDecimals, nil, nil, sliderTemplate)

                local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable)
                DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, slider, "have_tooltip", descPhraseId, widgetTable.desc)

                local label = detailsFramework:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12)
                slider.hasLabel = label

                local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable, true)
                DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon))

                processLabelIcon(slider.hasLabel, widgetTable, languageTable, widgetTable.text_template or textTemplate, useColon, languageAddonId)

                maxColumnWidth, maxWidgetWidth = setRangeProperties(parent, slider, widgetTable, currentXOffset, currentYOffset, sliderTemplate, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth, bIsDecimals, bAttachSliderButtonsToLeft)
                --store the widget created into the overall table and the widget by type
                table.insert(parent.widget_list, slider)
                table.insert(parent.widget_list_by_type.slider, slider)

                widgetCreated = slider
                amountLineWidgetAdded = amountLineWidgetAdded + 1

            elseif (widgetTable.type == "color") then
                ---@cast widgetTable df_menu_color
                assert(widgetTable.get, "DetailsFramework:BuildMenu: .get not found in the widget table for 'color'")
                local colorpick = detailsFramework:NewColorPickButton(parent, "$parentWidget" .. index, nil, widgetTable.set, nil, buttonTemplate)

                local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable)
                DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, colorpick, "have_tooltip", descPhraseId, widgetTable.desc)

                local label = detailsFramework:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12)
                colorpick.hasLabel = label

                local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable, true)
                DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon))

                processLabelIcon(colorpick.hasLabel, widgetTable, languageTable, widgetTable.text_template or textTemplate, useColon, languageAddonId)

                maxColumnWidth, maxWidgetWidth, extraPaddingY = setColorProperties(parent, colorpick, widgetTable, currentXOffset, currentYOffset, buttonTemplate, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth, bUseBoxFirstOnAllWidgets, extraPaddingY)

                --store the widget created into the overall table and the widget by type
                table.insert(parent.widget_list, colorpick)
                table.insert(parent.widget_list_by_type.color, colorpick)

                widgetCreated = colorpick
                amountLineWidgetAdded = amountLineWidgetAdded + 1

            elseif (widgetTable.type == "execute") then
                ---@cast widgetTable df_menu_button
                local button = detailsFramework:NewButton(parent, nil, "$parentWidget" .. index, nil, widgetWidth or 120, widgetHeight or 18, widgetTable.func, widgetTable.param1, widgetTable.param2, nil, "", nil, buttonTemplate, textTemplate)
                button.widget_type = "execute"

                local label = detailsFramework:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12)
                button.hasLabel = label

                local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable, true)
                DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, button.widget, namePhraseId, widgetTable.name)

                local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable)
                DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, button, "have_tooltip", descPhraseId, widgetTable.desc)

                maxColumnWidth, maxWidgetWidth, latestInlineWidget = setExecuteProperties(parent, button, widgetTable, currentXOffset, currentYOffset, buttonTemplate, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth, textTemplate, latestInlineWidget)

                --store the widget created into the overall table and the widget by type
                table.insert(parent.widget_list, button)
                table.insert(parent.widget_list_by_type.button, button)

                widgetCreated = button
                amountLineWidgetAdded = amountLineWidgetAdded + 1

            elseif (widgetTable.type == "textentry") then
                ---@cast widgetTable df_menu_textentry
                local textentry = detailsFramework:CreateTextEntry(parent, widgetTable.func or widgetTable.set, widgetWidth or 120, widgetHeight or 18, nil, "$parentWidget" .. index, nil, buttonTemplate)
                local label = detailsFramework:NewLabel(parent, nil, "$parentLabel" .. index, nil, "", "GameFontNormal", widgetTable.text_template or textTemplate or 12)
                textentry.hasLabel = label
                textentry.align = widgetTable.align or "left"

                local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable)
                DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, textentry, "have_tooltip", descPhraseId, widgetTable.desc)

                local namePhraseId = getNamePhraseID(widgetTable, languageAddonId, languageTable, true)
                DetailsFramework.Language.RegisterObjectWithDefault(languageAddonId, label.widget, namePhraseId, formatOptionNameWithColon(widgetTable.name, useColon))

                processLabelIcon(textentry.hasLabel, widgetTable, languageTable, widgetTable.text_template or textTemplate, useColon, languageAddonId)

                maxColumnWidth, maxWidgetWidth = setTextEntryProperties(parent, textentry, widgetTable, currentXOffset, currentYOffset, buttonTemplate, widgetWidth, widgetHeight, bAlignAsPairs, nAlignAsPairsLength, valueChangeHook, maxColumnWidth, maxWidgetWidth, textTemplate)

                --store the widget created into the overall table and the widget by type
                table.insert(parent.widget_list, textentry)
                table.insert(parent.widget_list_by_type.textentry, textentry)

                widgetCreated = textentry
                amountLineWidgetAdded = amountLineWidgetAdded + 1

            elseif (widgetTable.type == "image") then
                local image = parent:CreateTexture("$parentMenuImage" .. index, "overlay")

                setImageProperties(parent, image, widgetTable, currentXOffset, currentYOffset)

                currentYOffset = currentYOffset - widgetTable.height + 10

                --store the widget created into the overall table and the widget by type
                table.insert(parent.widget_list, image)
                table.insert(parent.widget_list_by_type.image, image)

                widgetCreated = image
                amountLineWidgetAdded = amountLineWidgetAdded + 1
            end

            --group type: create the group frame, it does not advance the Y offset
            if (widgetTable.type == "group") then
                ---@cast widgetTable df_menu_group
                local groupFrame = getOrCreateGroupFrame(parent, widgetTable.name, widgetTable, false, nil)
                widgetCreated = groupFrame
                setWidgetId(parent, widgetTable, groupFrame)
                jumpToNextLine = false
            end

            if languageAddonId and widgetCreated then
                widgetCreated.__languageAddonId = languageAddonId
            end

            if (widgetTable.nocombat) then
                table.insert(widgetsToDisableOnCombat, widgetCreated)
            end

            widgetTable.widget = widgetCreated
            if widgetTable.disableif then
                table.insert(parent.widget_to_disable_check, widgetTable)
            end

            if jumpToNextLine then
                if (not widgetTable.inline) then
                    if (widgetTable.spacement) then
                        currentYOffset = currentYOffset - 30
                    else
                        currentYOffset = currentYOffset - 20
                    end
                end

                if (extraPaddingY > 0) then
                    currentYOffset = currentYOffset - extraPaddingY
                end

                if (bUseScrollFrame) then
                    if (widgetTable.type == "breakline") then
                        biggestColumnHeight = math.min(currentYOffset, biggestColumnHeight)
                        currentYOffset = yOffset

                        if (bAlignAsPairs) then
                            currentXOffset = currentXOffset + nAlignAsPairsLength + (widgetWidth or maxWidgetWidth) + nAlignAsPairsSpacing
                        else
                            currentXOffset = currentXOffset + maxColumnWidth + 20
                        end

                        amountLineWidgetAdded = 0
                        maxColumnWidth = 0
                        maxWidgetWidth = 0
                    end
                else
                    if (widgetTable.type == "breakline" or currentYOffset < height) then
                        currentYOffset = yOffset
                        currentXOffset = currentXOffset + maxColumnWidth + 20
                        amountLineWidgetAdded = 0
                        maxColumnWidth = 0
                    end
                end
            end
        end --no widget.hidden
    end --end loop

    if (bUseScrollFrame) then
        if (biggestColumnHeight == 0) then
            biggestColumnHeight = currentYOffset
        end

        parent:SetHeight(biggestColumnHeight * -1)
        canvasFrame:GetParent().RefreshOptions = function()
            parent:RefreshOptions()
        end
    end

    detailsFramework.RefreshUnsafeOptionsWidgets()
    onMenuBuilt(parent)

    parent:RefreshOptions()

    applyGroupFrames(parent, menuOptions)
end


local lockNotSafeWidgetsForCombat = function()
    for _, widget in ipairs(widgetsToDisableOnCombat) do
        widget:Disable()
    end
end

local unlockNotSafeWidgetsForCombat = function()
    for _, widget in ipairs(widgetsToDisableOnCombat) do
        widget:Enable()
    end
end

function detailsFramework.RefreshUnsafeOptionsWidgets()
    if (detailsFramework.PlayerHasCombatFlag) then
        lockNotSafeWidgetsForCombat()
    else
        unlockNotSafeWidgetsForCombat()
    end
end

detailsFramework.PlayerHasCombatFlag = false
local ProtectCombatFrame = CreateFrame("frame")
ProtectCombatFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
ProtectCombatFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
ProtectCombatFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
ProtectCombatFrame:SetScript("OnEvent", function(self, event)
    if (event == "PLAYER_ENTERING_WORLD") then
        if (InCombatLockdown()) then
            detailsFramework.PlayerHasCombatFlag = true
        else
            detailsFramework.PlayerHasCombatFlag = false
        end
        detailsFramework.RefreshUnsafeOptionsWidgets()

    elseif (event == "PLAYER_REGEN_ENABLED") then
        detailsFramework.PlayerHasCombatFlag = false
        detailsFramework.RefreshUnsafeOptionsWidgets()

    elseif (event == "PLAYER_REGEN_DISABLED") then
        detailsFramework.PlayerHasCombatFlag = true
        detailsFramework.RefreshUnsafeOptionsWidgets()
    end
end)

function detailsFramework:CreateInCombatTexture(frame)
    if (detailsFramework.debug and not frame) then
        error("Details! Framework: CreateInCombatTexture invalid frame on parameter 1.")
    end

    local inCombatBackgroundTexture = detailsFramework:CreateImage(frame)
    inCombatBackgroundTexture:SetColorTexture(.6, 0, 0, .1)
    inCombatBackgroundTexture:Hide()

    local inCombatLabel = detailsFramework:CreateLabel(frame, "you are in combat", 24, "silver")
    inCombatLabel:SetPoint("right", inCombatBackgroundTexture, "right", -10, 0)
    inCombatLabel:Hide()

    frame:RegisterEvent("PLAYER_REGEN_DISABLED")
    frame:RegisterEvent("PLAYER_REGEN_ENABLED")

    frame:SetScript("OnEvent", function(self, event)
        if (event == "PLAYER_REGEN_DISABLED") then
            inCombatBackgroundTexture:Show()
            inCombatLabel:Show()

        elseif (event == "PLAYER_REGEN_ENABLED") then
            inCombatBackgroundTexture:Hide()
            inCombatLabel:Hide()
        end
    end)

    return inCombatBackgroundTexture
end
