local _, addon = ...
local L = addon.L;
local API = addon.API;
local Clamp = API.Clamp;
local ThemeUtil = addon.ThemeUtil;
local InCombatLockdown = InCombatLockdown;
local CreateFrame = CreateFrame;
local type = type;

local GetDBValue = addon.GetDBValue;
local SetDBValue = addon.SetDBValue;

local INPUT_DEVICE_GAME_PAD = false;
local INPUT_DEVICE_ID = 1;
local GAME_PAD_ACTIVE = false;
local BUTTON_PADDING_LARGE = 12;
local FONT_HEIGHT_LARGE = 12;
local FONT_HEIGHT_NORMAL = 12;
local FRAME_PADDING = 14;

local OPTIONBUTTON_HEIGHT = 36;                         --Adaptive
local OPTIONBUTTON_WIDTH = OPTIONBUTTON_HEIGHT*12;      --Adaptive
local OPTIONBUTTON_FROM_Y = 4;
local OPTIONBUTTON_LABEL_OFFSET = 14;
local OPTION_WIDGET_SIZE = 24;
local NUM_VISIBLE_OPTIONS = 10.5;
local ARROWOPTION_VALUETEXT_OFFSET_Y = -2;
local ARROWOTPION_WIDTH_RATIO = 7;                      --Adaptive
local ARROWOTPION_BAR_HEIGHT = 5;                       --Adaptive
local ARROWOTPION_CYCLING = false;
local HOTKEYFRAME_VALUETEXT_GAP = 4;
local TAB_BUTTON_GAP = 4;

local DISABLED_TEXTURE_ALPHA = 0.2;

local PREVIEW_PATH = "Interface/AddOns/DialogueUI/Art/PreviewPicture/";

local MainFrame;


local function Reposition_OnDragStart(self)
    if self.OnMouseUp then
        self:OnMouseUp();
    end

    MainFrame:StartMoving();
end

local function Reposition_OnDragStop(self)
    MainFrame:StopMovingOrSizing();
    MainFrame:UpdateScrollFrameBound();
end

local function SetupRepositionObject(obj)
    obj:SetScript("OnDragStart", Reposition_OnDragStart);
    obj:SetScript("OnDragStop", Reposition_OnDragStop);
    obj:RegisterForDrag("LeftButton");
end

DUIDialogSettingsMixin = {};

function DUIDialogSettingsMixin:OnLoad()
    MainFrame = self;
    addon.SettingsUI = self;
    addon.PixelUtil:AddPixelPerfectObject(self);

    SetupRepositionObject(self.Header);
end

function DUIDialogSettingsMixin:OnShow_First()
    if self.Init then
        self:Init();
    end
    self:Layout();
    self:UpdatePixel();
    self:SetScript("OnShow", self.OnShow);
    self.tabID = 1;
    self:OnShow();
end

function DUIDialogSettingsMixin:OnShow()
    self:UpdateCurrentTab();
    self:EnableGamePadButton(true);
    self:RegisterEvent("GAME_PAD_ACTIVE_CHANGED");
    self:RegisterEvent("GAME_PAD_DISCONNECTED");
    self:MoveToBestPosition();
    addon.CallbackRegistry:Trigger("SettingsUI.Show");
end

function DUIDialogSettingsMixin:MoveToBestPosition()
    if addon.DialogueUI:IsShown() then
        local viewportWidth, viewportHeight = WorldFrame:GetSize(); --height unaffected by screen resolution
        viewportWidth = math.min(viewportWidth, viewportHeight * 16/9);
        local frameWidth = self:GetWidth();
        self:ClearAllPoints();
        self:SetPoint("CENTER", nil, "CENTER", 0.5*frameWidth -0.5 * viewportWidth + 16, 0);
    else
        self:ClearAllPoints();
        self:SetPoint("CENTER", nil, "CENTER", 0, 0);
    end
    self:UpdateScrollFrameBound();
end

function DUIDialogSettingsMixin:UpdateScrollFrameBound()
    self.scrollFrameTop = self.ScrollFrame:GetTop();
    self.scrollFrameBottom = self.ScrollFrame:GetBottom();
end

function DUIDialogSettingsMixin:OnKeyDown(key)
    local valid = false;

    if key == "ESCAPE" or key == "F1" then
        self:Hide();
        valid = true;
    end

    if not InCombatLockdown() then
        self:SetPropagateKeyboardInput(not valid);
    end
end

function DUIDialogSettingsMixin:OnGamePadButtonDown(button)
    --print(button)

    GAME_PAD_ACTIVE = true;

    local valid = false;

    if button == "PADFORWARD" or button == "PADBACK" or button == "PAD2" then
        self:Hide();
        valid = true;
    elseif button == "PADLSHOULDER" then
        self:SelectTabByDelta(-1);
        valid = true;
    elseif button == "PADRSHOULDER" then
        self:SelectTabByDelta(1);
        valid = true;
    elseif button == "PADDUP" then
        self:FocusObjectByDelta(1);
        valid = true;
    elseif button == "PADDDOWN" then
        self:FocusObjectByDelta(-1);
        valid = true;
    elseif button == "PADDLEFT" or button == "PADDRIGHT" or button == "PAD1" then
        self:ClickFocusedObject(button);
        valid = true;
    end

    if not InCombatLockdown() then
        self:SetPropagateKeyboardInput(not valid);
    end
end

function DUIDialogSettingsMixin:OnMouseWheel(delta)

end

function DUIDialogSettingsMixin:LoadTheme()
    if self.Init then return end;

    local filePath = ThemeUtil:GetTexturePath();

    self.Background:SetTexture(filePath.."Settings-Background.png");
    self.BackgroundShadow:SetTexture(filePath.."Settings-BackgroundShadow.png");
    self.HeaderDivider:SetTexture(filePath.."Settings-Divider-H.png");
    self.VerticalDivider:SetTexture(filePath.."Settings-Divider-V.png");
    self.PreviewBorder:SetTexture(filePath.."Settings-PreviewBorder.png");
    self.Header.Selection:SetTexture(filePath.."Settings-TabButton-Selection.png");
    self.ButtonHighlight.BackTexture:SetTexture(filePath.."Settings-ButtonHighlight.png");

    local file1 = filePath.."Settings-CloseButton.png";
    self.Header.CloseButton.Background:SetTexture(file1);
    self.Header.CloseButton.Highlight:SetTexture(file1);
    self.Header.CloseButton.Icon:SetTexture(file1);

    local file2 = filePath.."Settings-Checkbox.png";
    self.checkboxPool:ProcessAllObjects(function(widget)
        widget:SetTexture(file2);
    end);

    local file3 = filePath.."Settings-ArrowOption.png";
    local file4 = filePath.."Settings-ArrowOption.png";

    self.arrowOptionPool:ProcessAllObjects(function(widget)
        widget:SetTexture(file3);
        widget.barPool:ProcessAllObjects(function(bar)
            bar:SetTexture(file4);
        end);
    end);

    if self.hotkeyFramePool then
        self.hotkeyFramePool:CallAllObjects("LoadTheme");
    end

    self.BackgroundDecor:SetTexture(filePath.."Settings-BackgroundDecor.png");

    local themeID;

    if ThemeUtil:IsDarkMode() then
        themeID = 2;
        self.BackgroundDecor:SetBlendMode("ADD");
    else
        themeID = 1;
        self.BackgroundDecor:SetBlendMode("BLEND");
    end

    self.ScrollBar:SetTheme(themeID);

    for i, button in ipairs(self.tabButtons) do
        local isSelected = button.isSelected;
        button.isSelected = true;
        button:SetSelected(isSelected);
    end
end

function DUIDialogSettingsMixin:UpdatePixel(scale)
    if not scale then
        scale = self:GetEffectiveScale();
    end

    local pixelOffset = 10.0;
    local offset = API.GetPixelForScale(scale, pixelOffset);
    self.BackgroundShadow:ClearAllPoints();
    self.BackgroundShadow:SetPoint("TOPLEFT", self, "TOPLEFT", -offset, offset);
    self.BackgroundShadow:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", offset, -offset);


    --API.UpdateTextureSliceScale(self.Background);
    --API.UpdateTextureSliceScale(self.BackgroundShadow);
end

function DUIDialogSettingsMixin:HighlightButton(button)
    self.ButtonHighlight:Hide();
    self.ButtonHighlight:ClearAllPoints();

    if button and button:IsEnabled() then
        self.ButtonHighlight:SetParent(button);
        self.ButtonHighlight:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0);
        self.ButtonHighlight:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 0, 0);
        self.ButtonHighlight:Show();
    end
end

function DUIDialogSettingsMixin:OnHide()
    self.focusedObject = nil;
    self.focusedObjectOffsetY = nil;
    self.scrollFrameTop = nil;
    self.scrollFrameBottom = nil;
    self:EnableGamePadButton(false);
    self:UnregisterEvent("GAME_PAD_ACTIVE_CHANGED");
    self:UnregisterEvent("GAME_PAD_DISCONNECTED");
    addon.CallbackRegistry:Trigger("SettingsUI.Hide");
end

function DUIDialogSettingsMixin:OnEvent(event, ...)
    if event == "GAME_PAD_ACTIVE_CHANGED" then
        local isActive = ...
        GAME_PAD_ACTIVE = isActive;
    elseif event == "GAME_PAD_DISCONNECTED" then
        GAME_PAD_ACTIVE = false;
    end
end

function DUIDialogSettingsMixin:SetPreviewTexture(optionData)
    if optionData.preview and (type(optionData.preview) == "string") then
        self.Preview:SetTexture(PREVIEW_PATH..optionData.preview);
    end

    if optionData.ratio == 1 then
        self.Preview:SetWidth(self.previewHeight);
    elseif optionData.ratio == 2 then
        self.Preview:SetWidth(2 * self.previewHeight);
    else

    end
end


function DUIDialogSettingsMixin:DisplayOptionInfo(optionData, choiceTooltip)
    local hasPreview;

    if optionData.preview then
        hasPreview = true;
        self:SetPreviewTexture(optionData);
    else
        hasPreview = false;
    end

    self.Preview:SetShown(hasPreview);
    self.PreviewBorder:SetShown(hasPreview);

    if hasPreview then
        if not self.hasPreview then
            self.hasPreview = true;
            self.Description:ClearAllPoints();
            self.Description:SetPoint("TOP", self.Preview, "BOTTOM", 0, -FRAME_PADDING);
        end
    else
        if self.hasPreview then
            self.hasPreview = nil;
            self.Description:ClearAllPoints();
            self.Description:SetPoint("TOP", self.Preview, "TOP", 0, -8);
        end
    end

    if choiceTooltip and optionData.description then
        self.Description:SetText(optionData.description.."\n\n"..choiceTooltip);
    else
        self.Description:SetText(optionData.description);
    end
end


local function ValueTextFormatter_PrimaryControlKey(arrowOptionButton, dbValue)
    local f = arrowOptionButton.HotkeyFrame;

    if not f then
        f = MainFrame.hotkeyFramePool:Acquire();
        arrowOptionButton.HotkeyFrame = f;
    end

    f:SetBaseHeight(20);

    local key, keyDesc, errorText;

    if dbValue == 1 then
        key = "SPACE";
        keyDesc = L["Key Space"];
    elseif dbValue == 2 then
        key, errorText = API.GetBestInteractKey();
        keyDesc = L["Key Interact"];
    elseif dbValue == 0 then
        key = "DISABLED";
        keyDesc = L["Key Disabled"];
    end

    local fontString = arrowOptionButton.ValueText;
    f:SetKey(key or "ERROR");
    fontString:SetText(keyDesc);

    local widgetWidth = f:GetWidth() + HOTKEYFRAME_VALUETEXT_GAP;
    fontString:ClearAllPoints();
    fontString:SetPoint("TOP", arrowOptionButton, "TOP", widgetWidth*0.5, ARROWOPTION_VALUETEXT_OFFSET_Y);
    f:ClearAllPoints();
    f:SetPoint("RIGHT", fontString, "LEFT", -HOTKEYFRAME_VALUETEXT_GAP, 0);
end

local function PrimaryControlKey_Interact_Tooltip()
    local key, errorText = API.GetBestInteractKey();
    if errorText then
        local additionalTooltip = errorText.."\n\n"..L["Use Default Control Key Alert"];
        return additionalTooltip
    end
end

local function TTSHotkey_Tooltip()
    local device;

    if INPUT_DEVICE_ID == 1 then
        device = "PC";
    elseif INPUT_DEVICE_ID == 2 then
        device = "Xbox";
    elseif INPUT_DEVICE_ID == 3 then
        device = "PlayStation";
    else
        device = "PC";
    end

    return L["TTS Use Hotkey Tooltip "..device]
end

local function QuestItemDisplay_Move_OnClick()
    addon.QuestItemDisplay:EnterEditMode();
end

local function QuestItemDisplay_Reset_OnClick()
    addon.QuestItemDisplay:ResetPosition();
end

local function QuestItemDisplayPosition_Validation()
    local f = addon.QuestItemDisplay;
    return f and f:IsUsingCustomPosition()
end

local function RPAddOn_Validation()
    return addon.GetInstalledRPAddOnName() ~= nil
end

local function RPAddOn_ReplaceName_Tooltip()
    local addOnName = addon.GetInstalledRPAddOnName();
    if addOnName then
        return L["Format Functionality Handled By"]:format(addOnName);
    end
end

local Schematic = {
    {
        tabName = L["UI"],
        options = {
            {type = "ArrowOption", name = L["Theme"], description = L["Theme Desc"], dbKey = "Theme", preview = "Theme", ratio = 2,
                choices = {
                    {dbValue = 1, valueText = L["Theme Brown"]},
                    {dbValue = 2, valueText = L["Theme Dark"]},
                },
            },
            {type = "ArrowOption", name = L["Frame Size"], description = L["Frame Size Desc"], dbKey = "FrameSize",
                choices = {
                    {dbValue = 0, valueText = L["Size Extra Small"]},
                    {dbValue = 1, valueText = L["Size Small"]},
                    {dbValue = 2, valueText = L["Size Medium"]},
                    {dbValue = 3, valueText = L["Size Large"]},
                },
            },
            {type = "ArrowOption", name = L["Font Size"], description = L["Font Size Desc"], dbKey = "FontSizeBase", realignAfterClicks = true,
                choices = {
                    {dbValue = 0, valueText = "10"},
                    {dbValue = 1, valueText = "12"},
                    {dbValue = 2, valueText = "14"},
                    {dbValue = 3, valueText = "16"},
                },
            },
            {type = "Checkbox", name = L["Hide UI"], description = L["Hide UI Desc"], dbKey = "HideUI"},
            {type = "Checkbox", name = L["Hide Unit Names"], description = L["Hide Unit Names Desc"], dbKey = "HideUnitNames", parentKey = "HideUI", requiredParentValue = true},
            {type = "Checkbox", name = L["Show Copy Text Button"], description = L["Show Copy Text Button Desc"], preview = "CopyTextButton", ratio = 1, dbKey = "ShowCopyTextButton"},
            {type = "Checkbox", name = L["Show NPC Name On Page"], description = L["Show NPC Name On Page Desc"], dbKey = "ShowNPCNameOnPage"},

            {type = "Subheader", name = L["Quest"]},
            {type = "Checkbox", name = L["Mark Highest Sell Price"], description = L["Mark Highest Sell Price Desc"], dbKey = "MarkHighestSellPrice", preview = "MarkHighestSellPrice", ratio = 1},
            {type = "Checkbox", name = L["Show Quest Type Text"], description = L["Show Quest Type Text Desc"], dbKey = "QuestTypeText", preview = "QuestTypeText", ratio = 1},
            {type = "Checkbox", name = L["Simplify Currency Rewards"], description = L["Simplify Currency Rewards Desc"], dbKey = "SimplifyCurrencyReward", preview = "SimplifyCurrencyReward", ratio = 2},

            {type = "Subheader", name = L["Roleplaying"], validationFunc = RPAddOn_Validation},
            {type = "Checkbox", name = L["Use RP Name In Dialogues"], description = L["Use RP Name In Dialogues Desc"], tooltip = RPAddOn_ReplaceName_Tooltip, dbKey = "UseRoleplayName", validationFunc = RPAddOn_Validation},
        },
    },

    {
        tabName = L["Camera"],
        options = {
            {type = "ArrowOption", name = L["Camera Movement"], dbKey="CameraMovement",
                choices = {
                    {dbValue = 0, valueText = L["Camera Movement Off"]},
                    {dbValue = 1, valueText = L["Camera Movement Zoom In"]},
                    {dbValue = 2, valueText = L["Camera Movement Horizontal"]},
                },
            },
            {type = "Checkbox", name = L["Change FOV"], description = L["Change FOV Desc"], dbKey = "CameraChangeFov", parentKey = "CameraMovement", requiredParentValue = 1, preview = "CameraChangeFov", ratio = 2},
            {type = "Checkbox", name = L["Maintain Camera Position"], description = L["Maintain Camera Position Desc"], dbKey = "CameraMovement1MaintainPosition", parentKey = "CameraMovement", requiredParentValue = 1},
            --{type = "Checkbox", name = L["Maintain Camera Position"], description = L["Maintain Camera Position Desc"], dbKey = "CameraMovement2MaintainPosition", parentKey = "CameraMovement", requiredParentValue = 2, },
            {type = "Checkbox", name = L["Maintain Offset While Mounted"], description = L["Maintain Offset While Mounted Desc"], dbKey = "CameraMovementMountedCamera", parentKey = "CameraMovement", requiredParentValue = {1, 2}},
            {type = "Checkbox", name = L["Disable Camera Movement Instance"], description = L["Disable Camera Movement Instance Desc"], dbKey = "CameraMovementDisableInstance", parentKey = "CameraMovement", requiredParentValue = {1, 2}},
        },
    },

    {
        tabName = L["Control"],
        options = {
            {type = "ArrowOption", name = L["Input Device"], dbKey = "InputDevice", description = L["Input Device Desc"],
                choices = {
                    {dbValue = 1, valueText = L["Input Device KBM"]},
                    {dbValue = 2, valueText = L["Input Device Xbox"], tooltip = L["Input Device Xbox Tooltip"]},
                    {dbValue = 3, valueText = L["Input Device PlayStation"], tooltip = L["Input Device PlayStation Tooltip"]},
                },
            },
            {type = "ArrowOption", name = L["Primary Control Key"], description = L["Primary Control Key Desc"], dbKey = "PrimaryControlKey", valueTextFormatter = ValueTextFormatter_PrimaryControlKey, hasHotkey = true, parentKey = "InputDevice", requiredParentValue = 1,
                choices = {
                    {dbValue = 1, valueText = L["Key Space"]},
                    {dbValue = 2, valueText = L["Key Interact"], tooltip = PrimaryControlKey_Interact_Tooltip},
                    {dbValue = 0, valueText = L["Key Disabled"], tooltip = L["Key Disabled Tooltip"]},
                },
            },

            {type = "Subheader", name = L["Quest"]},
            {type = "Checkbox", name = L["Press Button To Scroll Down"], description = L["Press Button To Scroll Down Desc"], dbKey = "ScrollDownThenAcceptQuest"},
        },
    },

    {
        tabName = L["Gameplay"],
        options = {
            {type = "Checkbox", name = L["Quest Item Display"], description = L["Quest Item Display Desc"], dbKey = "QuestItemDisplay", preview = "QuestItemDisplay", ratio = 2},
            {type = "Checkbox", name = L["Quest Item Display Hide Seen"], description = L["Quest Item Display Hide Seen Desc"], dbKey = "QuestItemDisplayHideSeen", parentKey = "QuestItemDisplay", requiredParentValue = true},
            {type = "Custom", name = L["Move Position"], icon = "Settings-Move.png", parentKey = "QuestItemDisplay", requiredParentValue = true, onClickFunc = QuestItemDisplay_Move_OnClick},
            {type = "Custom", name = L["Reset Position"], icon = "Settings-Reset.png", description = L["Quest Item Display Reset Position Desc"], parentKey = "QuestItemDisplay", requiredParentValue = true, validationFunc = QuestItemDisplayPosition_Validation, onClickFunc = QuestItemDisplay_Reset_OnClick},

            {type = "Subheader", name = L["Gossip"]},
            {type = "Checkbox", name = L["Auto Select Gossip"], description = L["Auto Select Gossip Desc"], dbKey = "AutoSelectGossip"},
            {type = "Checkbox", name = L["Force Gossip"], description = L["Force Gossip Desc"], dbKey = "ForceGossip"},
            --{type = "Checkbox", name = L["Nameplate Dialog"], description = L["Nameplate Dialog Desc"], dbKey = "NameplateDialogEnabled", preview = "NameplateDialogEnabled", ratio = 1},
        },
    },

    {
        tabName = L["Accessibility"],
        options = {
            {type = "Checkbox", name = L["TTS"], description = L["TTS Desc"], dbKey = "TTSEnabled", preview = "TTSButton", ratio = 1},
            {type = "Checkbox", name = L["TTS Use Hotkey"], description = L["TTS Use Hotkey Desc"], tooltip = TTSHotkey_Tooltip, dbKey = "TTSUseHotkey", parentKey = "TTSEnabled", requiredParentValue = true},
            {type = "Checkbox", name = L["TTS Auto Play"], description = L["TTS Auto Play Desc"], dbKey = "TTSAutoPlay", parentKey = "TTSEnabled", requiredParentValue = true},
            {type = "Checkbox", name = L["TTS Auto Stop"], description = L["TTS Auto Stop Desc"], dbKey = "TTSAutoStop", parentKey = "TTSEnabled", requiredParentValue = true},
        },
    },
};


local CloseButtonScripts = {};

function CloseButtonScripts:OnClick()
    MainFrame:Hide();
end

function CloseButtonScripts:OnEnter()
    self.Highlight:Show();
end

function CloseButtonScripts:OnLeave()
    self.Highlight:Hide();
end

function CloseButtonScripts:OnMouseDown()
    self.Icon:SetPoint("CENTER", 0, -1);
end

function CloseButtonScripts:OnMouseUp()
    self.Icon:SetPoint("CENTER", 0, 0);
end

function CloseButtonScripts:OnHide()
    self.Highlight:Hide();
    self.Icon:SetPoint("CENTER", 0, 0);
end


local function RemoveWidget(widget)
    widget:Hide();
    widget:ClearAllPoints();
    widget:SetParent(MainFrame);
    widget.HotkeyFrame = nil;
end

local function CreateOptionButton()
    local optionButton = CreateFrame("Button", nil, MainFrame, "DUIDialogSettingsOptionTemplate");
    return optionButton
end

local function CreateCheckbox()
    local checkbox = CreateFrame("Button", nil, MainFrame, "DUIDialogSettingsCheckboxTemplate");
    return checkbox
end

local function OnAcquireCheckbox(checkbox)
    checkbox:SetTexture(ThemeUtil:GetTextureFile("Settings-Checkbox.png"));
end

local function CreateArrowOption()
    local widget = CreateFrame("Frame", nil, MainFrame, "DUIDialogSettingsArrowOptionTemplate");
    return widget
end

local function OnAcquireArrowOption(widget)
    widget:SetTexture(ThemeUtil:GetTextureFile("Settings-ArrowOption.png"));
end


local TextureFrameMixin = {};

function TextureFrameMixin:SetTexture(file)
    self.Texture:SetTexture(file);
end

function TextureFrameMixin:SetVertexColor(r, g, b, a)
    self.Texture:SetVertexColor(r, g, b, a)
end

local function CreateTexture()
    local f = CreateFrame("Frame", nil, MainFrame, "DUIDialogSettingsTextureFrameTemplate");
    API.Mixin(f, TextureFrameMixin);
    return f
end


local ScrollFrameMixin = {};

function ScrollFrameMixin:SetViewSize(height)
    self.viewSize = height;
    self:SetHeight(height);
end

function ScrollFrameMixin:GetViewSize()
    return self.viewSize or 0
end

function ScrollFrameMixin:SetScrollRange(range)
    if range < 0 then
        range = 0;
        self.scrollBar:Hide();
        MainFrame.VerticalDivider:Show();
    else
        self.scrollBar:Show();
        MainFrame.VerticalDivider:Hide();
    end

    self.maxScrollOffset = range;
    self.scrollBar:UpdateThumbSize();
end

function ScrollFrameMixin:GetScrollRange()
    return self.maxScrollOffset or 0
end

function ScrollFrameMixin:SetContentHeight(contentHeight)
    self:SetScrollRange(contentHeight - self:GetViewSize());
end

function ScrollFrameMixin:GetScrollOffset()
    return self.scrollOffset or 0
end

function ScrollFrameMixin:SetScrollOffset(scrollOffset)
    scrollOffset = Clamp(scrollOffset, 0, self.maxScrollOffset);
    if scrollOffset ~= self.scrollOffset then
        self.scrollOffset = scrollOffset;
        self.Reference:SetPoint("TOPLEFT", 0, scrollOffset);
        self.scrollBar:UpdateThumbPosition();
    end
end

function ScrollFrameMixin:IsAtBottom()
    return self:GetScrollOffset() + 0.1 >= self:GetScrollRange();
end

function ScrollFrameMixin:ScrollBy(offset)
    self:SetScrollOffset( self:GetScrollOffset() + offset);
end

function ScrollFrameMixin:ScrollToBottom()
    self:SetScrollOffset(self:GetScrollRange());
end

function ScrollFrameMixin:OnMouseWheel(delta)
    if delta > 0 then
        self:ScrollBy(-OPTIONBUTTON_HEIGHT);
    else
        self:ScrollBy(OPTIONBUTTON_HEIGHT);
    end
end


function DUIDialogSettingsMixin:Init()
    --Tab Buttons
    if not self.tabButtons then
        self.tabButtons = {};
    end

    self.optionButtonPool = API.CreateObjectPool(CreateOptionButton, RemoveWidget);
    self.checkboxPool = API.CreateObjectPool(CreateCheckbox, RemoveWidget, OnAcquireCheckbox);
    self.arrowOptionPool = API.CreateObjectPool(CreateArrowOption, RemoveWidget, OnAcquireArrowOption);
    self.texturePool = API.CreateObjectPool(CreateTexture);

    local function CreateHotkeyFrame()
        local f = CreateFrame("Frame", nil, self, "DUIDialogHotkeyTemplate");
        f:SetShowDisabledKey(true);
        return f
    end

    local function RemoveHotkeyFrame(f)
        f:ClearKey();
        f:ClearAllPoints();
        f:SetParent(self);
    end

    self.hotkeyFramePool = API.CreateObjectPool(CreateHotkeyFrame, RemoveHotkeyFrame);
    
    self.numTabs = #Schematic;

    for i, tabData in ipairs(Schematic) do
        if not self.tabButtons[i] then
            self.tabButtons[i] = CreateFrame("Button", nil, self.Header.Container, "DUIDialogSettingsTabButtonTemplate");
            SetupRepositionObject(self.tabButtons[i]);
        end
        self.tabButtons[i].tabID = i;
        self.tabButtons[i]:SetName(tabData.tabName);
    end

    --Close Button
    local CloseButton = self.Header.CloseButton;
    for method, func in pairs(CloseButtonScripts) do
        CloseButton:SetScript(method, func);
    end
    CloseButton.Background:SetTexCoord(0, 0.5, 0, 0.5);
    CloseButton.Highlight:SetTexCoord(0.5, 1, 0, 0.5);
    CloseButton.Icon:SetTexCoord(0.0625, 0.1875, 0.625, 0.875);

    API.Mixin(self.ScrollFrame, ScrollFrameMixin);
    self.ScrollFrame:SetScript("OnMouseWheel", ScrollFrameMixin.OnMouseWheel);

    --ScrollBar
    local ScrollBar = addon.CreateScrollBar(self);
    self.ScrollBar = ScrollBar;
    self.ScrollFrame.scrollBar = ScrollBar;
    ScrollBar:SetOwner(self.ScrollFrame);
    ScrollBar:SetAlwaysVisible(false);
    ScrollBar:ShowScrollToBottomButton(false);
    ScrollBar:Show();

    self.Init = nil;

    self:LoadTheme();
end

function DUIDialogSettingsMixin:Layout()
    OPTIONBUTTON_HEIGHT = FONT_HEIGHT_NORMAL + 2*BUTTON_PADDING_LARGE;

    --Header
    local tabButtonHeight = FONT_HEIGHT_LARGE + 2*BUTTON_PADDING_LARGE;
    local minTabButtonWidth = 2 * tabButtonHeight;
    local headerHeight = tabButtonHeight;

    self.Header:ClearAllPoints();
    self.Header:SetPoint("TOPLEFT", self, "TOPLEFT", FRAME_PADDING, -FRAME_PADDING);
    self.Header:SetPoint("TOPRIGHT", self, "TOPRIGHT", -FRAME_PADDING, -FRAME_PADDING);
    self.Header:SetHeight(headerHeight);

    local tabButtonContainer = self.Header.Container;

    self.Header.CloseButton:ClearAllPoints();
    self.Header.CloseButton:SetParent(tabButtonContainer);
    self.Header.CloseButton:SetPoint("RIGHT", tabButtonContainer, "RIGHT", 0, 0);
    self.Header.CloseButton:SetSize(minTabButtonWidth, tabButtonHeight);
    self.Header.CloseButton.Icon:SetSize(FONT_HEIGHT_LARGE, FONT_HEIGHT_LARGE);

    self.HeaderDivider:ClearAllPoints();
    self.HeaderDivider:SetPoint("TOPLEFT", self.Header, "BOTTOMLEFT", 0, -TAB_BUTTON_GAP);
    self.HeaderDivider:SetPoint("TOPRIGHT", self.Header, "BOTTOMRIGHT", 0, -TAB_BUTTON_GAP);
    self.HeaderDivider:SetHeight(9);

    --Tab Buttons
    local buttonWidth;
    local tabButtonsSpan = 0;

    for i, button in ipairs(self.tabButtons) do
        buttonWidth = API.Round(button.Name:GetWrappedWidth() + 2*FONT_HEIGHT_LARGE);
        if buttonWidth < minTabButtonWidth then
            buttonWidth = minTabButtonWidth;
        end
        button:SetSize(buttonWidth, tabButtonHeight);
        tabButtonsSpan = tabButtonsSpan + buttonWidth;
        button:ClearAllPoints();
        if i > 1 then
            button:SetPoint("LEFT", self.tabButtons[i - 1], "RIGHT", TAB_BUTTON_GAP, 0);
            tabButtonsSpan = tabButtonsSpan + TAB_BUTTON_GAP;
        end
    end

    local tabFromOffsetX = (INPUT_DEVICE_GAME_PAD and 60) or 0;
    local firstTabButton = self.tabButtons[1];
    firstTabButton:ClearAllPoints();
    firstTabButton:SetPoint("LEFT", tabButtonContainer, "LEFT", tabFromOffsetX, 0);

    --ScrollFrame
    local scrollFrameWidth = OPTIONBUTTON_WIDTH;
    local scrollFrameHeight = NUM_VISIBLE_OPTIONS * OPTIONBUTTON_HEIGHT;
    local ScrollFrame = self.ScrollFrame;
    local gapToHeader = 12;
    ScrollFrame:ClearAllPoints();
    ScrollFrame:SetPoint("TOPLEFT", self.Header, "BOTTOMLEFT", 0, -gapToHeader);
    ScrollFrame:SetWidth(scrollFrameWidth);
    ScrollFrame:SetViewSize(scrollFrameHeight);

    --ScrollBar
    local ScrollBar = self.ScrollBar;
    local scrollBarOffsetX = OPTIONBUTTON_FROM_Y;
    ScrollBar:SetPoint("TOPLEFT", ScrollFrame, "TOPRIGHT", scrollBarOffsetX, 0);
    ScrollBar:SetPoint("BOTTOMLEFT", ScrollFrame, "BOTTOMRIGHT", scrollBarOffsetX, 0);
    ScrollBar:OnSizeChanged();

    self.VerticalDivider:ClearAllPoints();
    self.VerticalDivider:SetPoint("TOPLEFT", ScrollBar, "TOP", -2, 0);
    self.VerticalDivider:SetPoint("BOTTOMLEFT", ScrollBar, "BOTTOM", -2, 0);

    local frameWidth = API.Round((scrollFrameWidth + 1.5*FRAME_PADDING) / 0.618);
    local frameHeight = FRAME_PADDING + headerHeight + gapToHeader + scrollFrameHeight + FRAME_PADDING;

    local rightAreaWidth = API.Round(frameWidth - scrollFrameWidth - 2*FRAME_PADDING);
    local descriptionWidth = API.Round(frameWidth - scrollFrameWidth - 3*FRAME_PADDING -BUTTON_PADDING_LARGE);
    local previewShrink = 1;
    self.Preview:ClearAllPoints();
    self.Preview:SetPoint("TOP", self, "TOPRIGHT", -rightAreaWidth*0.5 -previewShrink, -(headerHeight + 2.5*FRAME_PADDING));

    local previewWidth = descriptionWidth - 2*previewShrink;
    local previewHeight = 0.5*previewWidth;
    self.Preview:SetSize(previewWidth, 0.5*previewWidth);
    self.previewHeight = previewHeight;

    self.Description:SetWidth(descriptionWidth);

    self.Description:ClearAllPoints();
    self.Description:SetPoint("TOP", self.Preview, "TOP", 0, -8);

    self:SetSize(frameWidth, frameHeight);
    --self.BackgroundGradient:SetWidth(frameHeight);
    self.BackgroundDecor:SetSize(rightAreaWidth, rightAreaWidth);

    self.DecorMask:SetSize(rightAreaWidth, rightAreaWidth);
    self.DecorMask:ClearAllPoints();
    self.DecorMask:SetPoint("BOTTOM", self.Description, "BOTTOM", 0, -16);

    --Reduce tab button size if necessary
    local headerObjectWidth = tabButtonsSpan + minTabButtonWidth + TAB_BUTTON_GAP;
    if INPUT_DEVICE_GAME_PAD then
        headerObjectWidth = headerObjectWidth + tabFromOffsetX + 4 * TAB_BUTTON_GAP + minTabButtonWidth;
    end

    local headerWidth = frameWidth - 2*FRAME_PADDING;

    if headerObjectWidth > headerWidth then
        if headerObjectWidth * 0.8 < headerWidth then
            tabButtonContainer:SetScale(0.8);
        else
            tabButtonContainer:SetScale(0.6);
        end
    else
        tabButtonContainer:SetScale(1);
    end
end

function DUIDialogSettingsMixin:SelectTabByID(tabID, forceUpdate)
    if tabID == self.tabID and not forceUpdate then return false end;

    if self.tabID then  --Save Scroll Position
        self["tabOffset"..self.tabID] = self.ScrollFrame:GetScrollOffset();
        self["tab"..self.tabID.."gamepadFocusIndex"] = self.gamepadFocusIndex;
    end

    self.tabID = tabID;

    --Update Options
    self.optionButtonPool:Release();
    self.checkboxPool:Release();
    self.arrowOptionPool:Release();
    self.texturePool:Release();
    self.hotkeyFramePool:Release();

    self:ResetGamePadObjects();

    --Highlight Button
    self.Header.Selection:ClearAllPoints();
    self.Header.Selection:Hide();

    for i, button in ipairs(self.tabButtons) do
        if button.tabID == tabID then
            button:SetSelected(true);
            self.Header.Selection:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0);
            self.Header.Selection:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 0, 0);
            self.Header.Selection:Show();
        else
            button:SetSelected(false);
        end
    end

    if INPUT_DEVICE_GAME_PAD then
        local gap = 4 * TAB_BUTTON_GAP;
        local lb = self.hotkeyFramePool:Acquire();
        lb:SetKey("PADLSHOULDER");
        lb:SetPoint("LEFT", self.Header.Container, "LEFT", 0, 0);

        local firstTabButton = self.tabButtons[1];
        firstTabButton:ClearAllPoints();
        firstTabButton:SetPoint("LEFT", lb, "RIGHT", gap, 0);

        local rb = self.hotkeyFramePool:Acquire();
        rb:SetKey("PADRSHOULDER");
        rb:SetPoint("LEFT", self.tabButtons[ #self.tabButtons ], "RIGHT", gap, 0);
    end

    local tabData = Schematic[tabID];
    local optionButton;
    local numShownOptions = 0;
    local isOptionValid;

    local dbKeyToWidget = {};

    for i, optionData in ipairs(tabData.options) do
        if optionData.parentKey then
            isOptionValid = false;
            local requirement = optionData.requiredParentValue;
            local dbValue = GetDBValue(optionData.parentKey);

            if type(requirement) == "table" then
                for _, requiredParentValue in ipairs(requirement) do
                    if dbValue == requiredParentValue then
                        isOptionValid = true;
                        break
                    end
                end
            else
                if dbValue == optionData.requiredParentValue then
                    isOptionValid = true;
                end
            end


            if dbKeyToWidget[optionData.parentKey] then
                dbKeyToWidget[optionData.parentKey].isParentOption = true;
            end
        else
            isOptionValid = true;
        end

        if isOptionValid and optionData.validationFunc then
            isOptionValid = optionData.validationFunc();
        end

        if isOptionValid then
            numShownOptions = numShownOptions + 1;
            optionButton = self.optionButtonPool:Acquire();
            optionButton.isParentOption = nil;
            optionButton:SetParent(self.ScrollFrame);
            optionButton:SetData(optionData);
            optionButton:SetSize(OPTIONBUTTON_WIDTH, OPTIONBUTTON_HEIGHT);
            optionButton:SetPoint("TOPLEFT", self.ScrollFrame.Reference, "TOPLEFT", 0, (1 - numShownOptions)*OPTIONBUTTON_HEIGHT);
            if optionData.dbKey then
                dbKeyToWidget[optionData.dbKey] = optionButton;
            end
        end
    end

    self.ScrollFrame:SetContentHeight(numShownOptions * OPTIONBUTTON_HEIGHT);
    self.ScrollFrame:SetScrollOffset(self["tabOffset"..self.tabID] or 0);
    self.dbKeyToWidget = dbKeyToWidget;

    if GAME_PAD_ACTIVE then
        self:FocusObjectByIndex(self["tab"..self.tabID.."gamepadFocusIndex"] or 1);
    end
end

function DUIDialogSettingsMixin:SelectTabByDelta(delta)
    local tabID = self.tabID or 0;
    if delta > 0 and tabID < self.numTabs then
        self:SelectTabByID(tabID + 1);
    elseif delta < 0 and tabID > 1 then
        self:SelectTabByID(tabID - 1);
    end
end

function DUIDialogSettingsMixin:UpdateCurrentTab()
    self:SelectTabByID(self.tabID, true)
end

function DUIDialogSettingsMixin:RequestUpdate()
    if self:IsVisible() then
        self:UpdateCurrentTab();
    end
end

function DUIDialogSettingsMixin:SetFocusedObject(object)
    self.focusedObject = object;
    if object then
        local _;
        _, self.focusedObjectOffsetY = object:GetCenter();
    else
        self.focusedObjectOffsetY = nil;
    end
end

function DUIDialogSettingsMixin:ReAlignToFocusedObject()
    --The UI's dimension change with Font Size, so we need maintain the option's offsetY so players don't click another option by accident
    if self.focusedObject and self.focusedObject:IsShown() and self.focusedObjectOffsetY then
        local _, newOffsetY = self.focusedObject:GetCenter();
        local point, relativeTo, relativePoint, offsetX, offsetY = self:GetPoint(1);
        self:ClearAllPoints();
        self:SetPoint(point, relativeTo, relativePoint, offsetX, offsetY + self.focusedObjectOffsetY - newOffsetY);
        self:UpdateScrollFrameBound();
    end
end

function DUIDialogSettingsMixin:ToggleUI()
    self:SetShown(not self:IsShown());
end


DUIDialogSettingsTabButtonMixin = {};

function DUIDialogSettingsTabButtonMixin:OnEnter()
    MainFrame:HighlightButton(self);
end

function DUIDialogSettingsTabButtonMixin:OnLeave()
    MainFrame:HighlightButton(nil);
end

function DUIDialogSettingsTabButtonMixin:OnMouseDown(button)
    if button ~= "LeftButton" or self.isSelected then return end;
    self.Name:SetPoint("CENTER", 0, -1);
end

function DUIDialogSettingsTabButtonMixin:OnMouseUp()
    self.Name:SetPoint("CENTER", 0, 0);
end

function DUIDialogSettingsTabButtonMixin:OnClick()
    MainFrame:SelectTabByID(self.tabID);
end

function DUIDialogSettingsTabButtonMixin:SetName(name)
    self.Name:SetText(string.upper(name));
end

function DUIDialogSettingsTabButtonMixin:SetSelected(state)
    if state then
        self.isSelected = true;
        self.Name:SetFontObject("DUIFont_Quest_Quest");
    else
        if self.isSelected then
            self.isSelected = nil;
            self.Name:SetFontObject("DUIFont_Quest_Gossip");
        end
    end
end


DUIDialogSettingsOptionMixin = {};

function DUIDialogSettingsOptionMixin:OnEnter()
    local choiceTooltip;

    if not self.isSubheader then
        MainFrame:HighlightButton(self);
        MainFrame:SetGamePadFocus(self);
        if self.widget then
            choiceTooltip = self.widget:GetSelectedChoiceTooltip();
        end
    end

    MainFrame:DisplayOptionInfo(self.optionData, choiceTooltip);
end

function DUIDialogSettingsOptionMixin:OnLeave()
    if not self:IsMouseOver() then
        MainFrame:HighlightButton(nil);
    end
end

function DUIDialogSettingsOptionMixin:OnClick(button)
    MainFrame:SetFocusedObject(self);

    if self.widget and self.widget.OnClick and self.widget:IsEnabled() then
        self.widget:OnClick(button);
    elseif self.optionData.onClickFunc then
        self.optionData.onClickFunc(self);
    end

    MainFrame:HighlightButton(nil);
    self:OnEnter();
end

function DUIDialogSettingsOptionMixin:SetCheckbox(optionData)
    self.widget = MainFrame.checkboxPool:Acquire();
    self.widget:SetParent(self);
    self.widget:SetPoint("RIGHT", self, "RIGHT", -BUTTON_PADDING_LARGE, 0);
    self.widget:SetWidgetHeight(OPTION_WIDGET_SIZE);

    self.widget:SetChecked(GetDBValue(self.dbKey) == true);
end

function DUIDialogSettingsOptionMixin:SetArrowOption(optionData)
    self.widget = MainFrame.arrowOptionPool:Acquire();
    self.widget:SetParent(self);
    self.widget:SetPoint("RIGHT", self, "RIGHT", -BUTTON_PADDING_LARGE, 0);
    self.widget:SetWidgetHeight(OPTION_WIDGET_SIZE);
    self.widget:SetData(optionData);
end

function DUIDialogSettingsOptionMixin:SetCustomButton(optionData)
    self.widget = nil;
end


function DUIDialogSettingsOptionMixin:SetSubheader()
    --[[
    local icon = MainFrame.texturePool:Acquire();
    icon:SetSize(FONT_HEIGHT_NORMAL, FONT_HEIGHT_NORMAL);
    icon:SetTexture(FILE_PATH.."Settings-SubheaderIcon.png");
    icon:SetVertexColor(0.50, 0.36, 0.24);
    icon:SetPoint("LEFT", self, "LEFT", 4, 0);
    icon:SetParent(self);
    --]]
end

function DUIDialogSettingsOptionMixin:SetData(optionData)
    self.optionData = optionData;
    self.dbKey = optionData.dbKey;
    self.updateTabAfterClicks = optionData.updateTabAfterClicks;

    local isSubheader;

    if optionData.type == "Subheader" then
        isSubheader = true;
        self:SetSubheader();
    elseif optionData.type == "Checkbox" then
        self:SetCheckbox(optionData);
    elseif optionData.type == "ArrowOption" then
        self:SetArrowOption(optionData);
    elseif optionData.type == "Custom" then
        self:SetCustomButton(optionData);
    end
    self.widgetType = optionData.type;

    if isSubheader then
        self.Label:SetText(string.upper(optionData.name));
        if not self.isSubheader then
            self.Label:SetFontObject("DUIFont_Settings_Disabled");
        end
    else
        self.Label:SetText(optionData.name);
        if self.isSubheader then
            self.Label:SetFontObject("DUIFont_Quest_Paragraph");
        end
        MainFrame:IndexGamePadObject(self);
    end
    self.isSubheader = isSubheader;

    local nameOffset;

    if optionData.parentKey then
        nameOffset = OPTIONBUTTON_LABEL_OFFSET + 24;
        local branch = MainFrame.texturePool:Acquire();
        branch:SetSize(OPTIONBUTTON_HEIGHT*0.5, OPTIONBUTTON_HEIGHT*0.5);
        branch:SetTexture(ThemeUtil:GetTextureFile("Settings-SubOptionIcon.png"));
        branch:SetVertexColor(1, 1, 1, 0.5);
        branch:SetPoint("BOTTOM", self, "LEFT", OPTIONBUTTON_LABEL_OFFSET + 2, 0);
        branch:SetParent(self);
    else
        nameOffset = OPTIONBUTTON_LABEL_OFFSET;
    end

    if optionData.icon then
        local icon = MainFrame.texturePool:Acquire();
        local iconSize = OPTION_WIDGET_SIZE;
        local iconOffset = -0.125*iconSize;
        icon:SetSize(iconSize, iconSize);
        icon:SetTexture(ThemeUtil:GetTextureFile(optionData.icon));
        icon:SetVertexColor(1, 1, 1, 1);
        icon:SetPoint("LEFT", self, "LEFT", nameOffset + iconOffset, 0);
        icon:SetParent(self);
        nameOffset = nameOffset + iconSize;
    end

    self.Label:ClearAllPoints();
    self.Label:SetPoint("LEFT", self, "LEFT", nameOffset, 0);
end




DUIDialogSettingsArrowOptionMixin = {};

local function ArrowButton_OnClick(self)
    self:GetParent():SelectChoiceByDelta(self.delta);
end

local function ArrowButton_OnEnter(self)
    self:GetParent():GetParent():OnEnter();
end

local function ArrowButton_OnLeave(self)
    self:GetParent():GetParent():OnLeave();
end

local function ArrowButton_OnMouseDown(self)
    if not self:IsEnabled() then return end;

    if self.delta < 0 then
        self.Texture:SetPoint("CENTER", -1, 0);
    else
        self.Texture:SetPoint("CENTER", 1, 0);
    end
end

local function ArrowButton_OnMouseUp(self)
    self.Texture:SetPoint("CENTER", 0, 0);
end

local function ArrowButton_OnEnable(self)
    self:SetAlpha(1);
end

local function ArrowButton_OnDisable(self)
    self:SetAlpha(DISABLED_TEXTURE_ALPHA);
end

local function RemoveBar(bar)
    bar:Hide();
    bar:ClearAllPoints();
end

local function OnAcquireBar(bar)
    bar:SetTexture(ThemeUtil:GetTextureFile("Settings-ArrowOption.png"));
end

function DUIDialogSettingsArrowOptionMixin:OnLoad()
    self.ValueText:SetPoint("TOP", self, "TOP", 0, ARROWOPTION_VALUETEXT_OFFSET_Y);

    self.LeftArrow.delta = -1;
    self.LeftArrow:SetScript("OnEnter", ArrowButton_OnEnter);
    self.LeftArrow:SetScript("OnLeave", ArrowButton_OnLeave);
    self.LeftArrow:SetScript("OnClick", ArrowButton_OnClick);
    self.LeftArrow:SetScript("OnMouseDown", ArrowButton_OnMouseDown);
    self.LeftArrow:SetScript("OnMouseUp", ArrowButton_OnMouseUp);
    self.LeftArrow:SetScript("OnEnable", ArrowButton_OnEnable);
    self.LeftArrow:SetScript("OnDisable", ArrowButton_OnDisable);

    self.RightArrow.delta = 1;
    self.RightArrow:SetScript("OnEnter", ArrowButton_OnEnter);
    self.RightArrow:SetScript("OnLeave", ArrowButton_OnLeave);
    self.RightArrow:SetScript("OnClick", ArrowButton_OnClick);
    self.RightArrow:SetScript("OnMouseDown", ArrowButton_OnMouseDown);
    self.RightArrow:SetScript("OnMouseUp", ArrowButton_OnMouseUp);
    self.RightArrow:SetScript("OnEnable", ArrowButton_OnEnable);
    self.RightArrow:SetScript("OnDisable", ArrowButton_OnDisable);

    local function CreateBar()
        local bar = self:CreateTexture(nil, "OVERLAY");
        bar:SetTextureSliceMargins(1, 1, 1, 1);
        bar:SetTextureSliceMode(1);
        bar:SetTexCoord(0, 16/128, 124/128, 1);
        return bar
    end

    self.barPool = API.CreateObjectPool(CreateBar, RemoveBar, OnAcquireBar);
end

function DUIDialogSettingsArrowOptionMixin:SetNumChoices(numChoices, forceUpdate)
    if numChoices ~= self.numChoices or forceUpdate then
        self.barPool:Release();
        self.numChoices = numChoices;
        self.bars = {};
        local barHeight = API.GetPixelForWidget(self, ARROWOTPION_BAR_HEIGHT);
        local gap = API.GetPixelForWidget(self, 4);
        local buttonWidth = self.LeftArrow:GetWidth();
        local barShrink = 4;
        local fromOffsetX = buttonWidth + barShrink;
        local barWidth = (self:GetWidth() -2*barShrink - 2*buttonWidth - (numChoices - 1)*gap) / numChoices;
        local bar;
        for i = 1, numChoices do
            bar = self.barPool:Acquire();
            bar:SetSize(barWidth, barHeight);
            bar:SetPoint("BOTTOMLEFT", self, "BOTTOMLEFT", fromOffsetX + (gap + barWidth) * (i - 1), 2);
            self.bars[i] = bar;
        end
    end
end

function DUIDialogSettingsArrowOptionMixin:GetCentralWidth()
    return self:GetWidth() - 2*self.LeftArrow:GetWidth();
end

function DUIDialogSettingsArrowOptionMixin:SetWidgetWidth(width)
    local centralWidth = self:GetCentralWidth();
end

function DUIDialogSettingsArrowOptionMixin:SetWidgetHeight(height)
    self.LeftArrow.Texture:SetSize(height, height);
    self.RightArrow.Texture:SetSize(height, height);
    self:SetHeight(height);
    self:SetWidth(height * ARROWOTPION_WIDTH_RATIO);
end

function DUIDialogSettingsArrowOptionMixin:SetValueTextByID(id)
    if self.valueTextFormatter then
        self.valueTextFormatter(self, self.choices[id].dbValue);
    else
        local valueText = self.choices[id].valueText;
        self.ValueText:SetText(valueText);
    end
end

function DUIDialogSettingsArrowOptionMixin:SelectChoiceByID(id)
    if not self.choices[id] then
        id = 1;
    end

    local choiceData = self.choices[id];
    if choiceData then
        self:SetValueTextByID(id);
    end

    self.selectedID = id;

    for i, bar in ipairs(self.bars) do
        if i == id then
            bar:SetAlpha(1);
        else
            bar:SetAlpha(DISABLED_TEXTURE_ALPHA);
        end
    end

    self.LeftArrow:SetEnabled(id ~= 1);
    self.RightArrow:SetEnabled(id ~= self.numChoices);
end

function DUIDialogSettingsArrowOptionMixin:SelectNextChoice()
    if ARROWOTPION_CYCLING then
        self.selectedID = self.selectedID + 1;
        if self.selectedID > self.numChoices then
            self.selectedID = 1;
        end
    else
        if self.selectedID < self.numChoices then
            self.selectedID = self.selectedID + 1;
        else
            return false
        end
    end

    self:SelectChoiceByID(self.selectedID);
    return true
end

function DUIDialogSettingsArrowOptionMixin:SelectPreviousChoice()
    if ARROWOTPION_CYCLING then
        self.selectedID = self.selectedID - 1;
        if self.selectedID < 1 then
            self.selectedID = self.numChoices;
        end
    else
        if self.selectedID > 1 then
            self.selectedID = self.selectedID - 1;
        else
            return false
        end
    end

    self:SelectChoiceByID(self.selectedID);
    return true
end

function DUIDialogSettingsArrowOptionMixin:SelectChoiceByDelta(delta)
    --right = 1, left = -1
    local anyChange;

    if delta > 0 then
        anyChange = self:SelectNextChoice();
    else
        anyChange = self:SelectPreviousChoice();
    end

    if anyChange then
        self:PostClick();
    end
end

function DUIDialogSettingsArrowOptionMixin:PostClick()
    local optionButton = self:GetParent();
    MainFrame:SetFocusedObject(optionButton);

    local value = self.choices[self.selectedID].dbValue;
    SetDBValue(optionButton.dbKey, value);

    if optionButton.isParentOption or optionButton.updateTabAfterClicks then
        MainFrame:UpdateCurrentTab();
    end

    if self.realignAfterClicks then
        MainFrame:ReAlignToFocusedObject();
    end

    self:GetParent():OnEnter();
end

function DUIDialogSettingsArrowOptionMixin:SetData(optionData)
    self.choices = optionData.choices;
    self.dbKey = optionData.dbKey;
    self.valueTextFormatter = optionData.valueTextFormatter;
    self.realignAfterClicks = optionData.realignAfterClicks;

    self:SetNumChoices(#self.choices);

    if not optionData.hasHotkey then
        self.ValueText:ClearAllPoints();
        self.ValueText:SetPoint("TOP", self, "TOP", 0, ARROWOPTION_VALUETEXT_OFFSET_Y);
    end

    local selectedID = 1;
    local dbValue = GetDBValue(self.dbKey);

    for id, choiceData in ipairs(self.choices) do
        if dbValue == choiceData.dbValue then
            selectedID = id;
            break
        end
    end

    self:SelectChoiceByID(selectedID);
end

function DUIDialogSettingsArrowOptionMixin:SetTexture(file)
    self.LeftArrow.Texture:SetTexture(file);
    self.LeftArrow.Highlight:SetTexture(file);
    self.RightArrow.Texture:SetTexture(file);
    self.RightArrow.Highlight:SetTexture(file);
end

function DUIDialogSettingsArrowOptionMixin:GetSelectedChoiceTooltip()
    if self.selectedID and self.choices and self.choices[self.selectedID] then
        local tooltip = self.choices[self.selectedID].tooltip;
        if type(tooltip) == "function" then
            return tooltip()
        else
            return tooltip
        end
    end
end




DUIDialogSettingsCheckboxMixin = {};

function DUIDialogSettingsCheckboxMixin:OnEnter()
    self:GetParent():OnEnter();
end

function DUIDialogSettingsCheckboxMixin:OnLeave()
    self:GetParent():OnLeave();
end

function DUIDialogSettingsCheckboxMixin:SetTexture(file)
    self.Background:SetTexture(file);
    self.Check:SetTexture(file);
end

function DUIDialogSettingsCheckboxMixin:OnClick()
    self:Toggle();

    local optionButton = self:GetParent();
    SetDBValue(optionButton.dbKey, self.checked);

    if optionButton.isParentOption or optionButton.updateTabAfterClicks then
        MainFrame:UpdateCurrentTab();
    end

    self:OnEnter();
end

function DUIDialogSettingsCheckboxMixin:SetChecked(state)
    self.checked = state;
    self.Check:SetShown(state);
    if state then
        self.Background:SetTexCoord(0.5, 1, 0, 0.5);
    else
        self.Background:SetTexCoord(0, 0.5, 0, 0.5);
    end
end

function DUIDialogSettingsCheckboxMixin:GetChecked()
    return self.checked == true
end

function DUIDialogSettingsCheckboxMixin:Toggle()
    self.checked = not self.checked;
    self:SetChecked(self.checked);
end

function DUIDialogSettingsCheckboxMixin:SetWidgetHeight(height)
    self:SetSize(height, height);
    self.Check:SetSize(height*0.5, height*0.5);
end

function DUIDialogSettingsCheckboxMixin:GetSelectedChoiceTooltip()
    local optionButton = self:GetParent();
    local tooltip = optionButton.optionData and optionButton.optionData.tooltip;
    if tooltip then
        if type(tooltip) == "function" then
            return tooltip(self.checked)
        else
            return tooltip
        end
    end
end

do  --GamePad/Controller
    function DUIDialogSettingsMixin:ResetGamePadObjects()
        self.gamepadMaxIndex = 0;
        self.gamepadFocusIndex = nil;
        self.gamepadFocus = nil;
        self.gamepadObjects = {};
    end

    function DUIDialogSettingsMixin:IndexGamePadObject(object)
        self.gamepadMaxIndex = self.gamepadMaxIndex + 1;
        self.gamepadObjects[self.gamepadMaxIndex] = object;
        object.gamepadIndex = self.gamepadMaxIndex;
    end

    function DUIDialogSettingsMixin:ClearGamePadFocus()
        if self.gamepadFocus then
            --self.gamepadFocus:OnLeave();
            self.gamepadFocus = nil;
        end
    end

    function DUIDialogSettingsMixin:SetGamePadFocus(optionButton)
        self.gamepadFocusIndex = optionButton.gamepadIndex;
        self.gamepadFocus = optionButton;
    end

    function DUIDialogSettingsMixin:FocusObjectByIndex(index)
        local object = self.gamepadObjects[index];
        if object then
            self.gamepadFocusIndex = index;
            self.gamepadFocus = object;
            self.gamepadFocus:OnEnter();
            return true
        end
    end

    function DUIDialogSettingsMixin:FocusObjectByDelta(delta)
        local maxIndex = self.gamepadMaxIndex or 0;
        local index = self.gamepadFocusIndex;

        if not index then
            index = 0;
        end

        if delta < 0 and index < maxIndex then
            index = index + 1;
        elseif delta > 0 and index > 1 then
            index = index - 1;
        elseif index == 0 then
            index = 1;
        else
            return
        end

        self:ClearGamePadFocus();

        if self:FocusObjectByIndex(index) then
            local threshold = 2 * OPTIONBUTTON_HEIGHT;
            if delta > 0 then
                local top = self.gamepadFocus:GetTop();
                if top + threshold >= self.scrollFrameTop then
                    self.ScrollFrame:ScrollBy(-OPTIONBUTTON_HEIGHT);
                end
            else
                local bottom = self.gamepadFocus:GetBottom();
                if bottom - threshold <= self.scrollFrameBottom then
                    self.ScrollFrame:ScrollBy(OPTIONBUTTON_HEIGHT);
                end
            end
            return true
        end
    end

    function DUIDialogSettingsMixin:FocusNextObject()
        if self:FocusObjectByDelta(1) then
            return
        end
    end

    function DUIDialogSettingsMixin:FocusPreviousObject()
        if self:FocusObjectByDelta(-1) then
            return
        end
    end

    function DUIDialogSettingsMixin:ClickFocusedObject(gamepadButton)
        if self.gamepadFocus then
            local optionButton = self.gamepadFocus;
            if gamepadButton == "PAD1" then
                optionButton:OnClick();
            elseif gamepadButton == "PADDLEFT" then
                if optionButton.widgetType == "ArrowOption" then
                    optionButton.widget:SelectChoiceByDelta(-1);
                end
            elseif gamepadButton == "PADDRIGHT" then
                if optionButton.widgetType == "ArrowOption" then
                    optionButton.widget:SelectChoiceByDelta(1);
                end
            end
            return true
        end
        return false
    end
end


function DialogueUI_ShowSettingsFrame()
    MainFrame:ToggleUI();
end


do
    local function OnFontSizeChanged(baseFontSize, fontSizeID)
        FONT_HEIGHT_LARGE = baseFontSize;
        FONT_HEIGHT_NORMAL = baseFontSize;

        BUTTON_PADDING_LARGE = FONT_HEIGHT_NORMAL;
        OPTIONBUTTON_HEIGHT = 3*FONT_HEIGHT_NORMAL;
        OPTION_WIDGET_SIZE = 2 * FONT_HEIGHT_NORMAL;

        local widthMultiplier;

        if fontSizeID <= 1 then
            NUM_VISIBLE_OPTIONS = 10.5;
            widthMultiplier = 12;
            ARROWOTPION_WIDTH_RATIO = 7;
            ARROWOTPION_BAR_HEIGHT = 5;
        elseif fontSizeID == 2 then
            NUM_VISIBLE_OPTIONS = 9.5;
            widthMultiplier = 11;
            ARROWOTPION_WIDTH_RATIO = 7;
            ARROWOTPION_BAR_HEIGHT = 6;
        elseif fontSizeID == 3 then
            NUM_VISIBLE_OPTIONS = 8.5;
            widthMultiplier = 10;
            ARROWOTPION_WIDTH_RATIO = 6;
            ARROWOTPION_BAR_HEIGHT = 6;
        else
            NUM_VISIBLE_OPTIONS = 8.5;
            widthMultiplier = 10;
            ARROWOTPION_WIDTH_RATIO = 6;
            ARROWOTPION_BAR_HEIGHT = 6;
        end

        OPTIONBUTTON_WIDTH = widthMultiplier * OPTIONBUTTON_HEIGHT;
    end
    addon.CallbackRegistry:Register("FontSizeChanged", OnFontSizeChanged);

    local function PostFontSizeChanged()
        --We wait after hotkey frame size update
        local f = MainFrame;

        if f.Init then return end;

        if f.arrowOptionPool then
            local function ModifyArrowOption(widget)
                widget:SetWidgetHeight(OPTION_WIDGET_SIZE);
                widget.numChoices = 0;
            end
            f.arrowOptionPool:ProcessAllObjects(ModifyArrowOption);
        end

        if f.hotkeyFramePool then
            local method = "UpdateBaseHeight";
            f.hotkeyFramePool:CallAllObjects(method);
        end

        f:Layout();
        f:UpdateCurrentTab();
    end
    addon.CallbackRegistry:Register("PostFontSizeChanged", PostFontSizeChanged);

    local function PostInputDeviceChanged(dbValue)
        INPUT_DEVICE_GAME_PAD = dbValue ~= 1;
        INPUT_DEVICE_ID = dbValue;

        local f = MainFrame;
        if f.Init then return end;

        f:Layout();
        f:UpdateCurrentTab();
    end
    addon.CallbackRegistry:Register("PostInputDeviceChanged", PostInputDeviceChanged);
end