ZO_Dyeing_Keyboard = ZO_DeferredInitializingObject:Subclass()

function ZO_Dyeing_Keyboard:Initialize(control)
    self.control = control

    KEYBOARD_DYEING_FRAGMENT = ZO_FadeSceneFragment:New(control)
    ZO_DeferredInitializingObject.Initialize(self, KEYBOARD_DYEING_FRAGMENT)
end

function ZO_Dyeing_Keyboard:OnDeferredInitialize()
    self.pane = self.control:GetNamedChild("Pane")
    self.noDyesLabel = self.pane:GetNamedChild("NoDyesLabel")
    self.paneScrollChild = self.pane:GetNamedChild("ScrollChild")
    self.dyeIdToSwatch = {} -- Create it now so the APIs have a table to index even if we never view the fragment

    self.savedSetInterpolator = ZO_SimpleControlScaleInterpolator:New(.9, 1.0)

    self:InitializeSortsAndFilters()
    self:InitializeTools()
    self:InitializeSavedSets()
    self:InitializeSwatchPool()
    self:InitializeHeaderPool()

    local function UpdateDyeLayout()
        self:DirtyDyeLayout()
    end

    ZO_DYEING_MANAGER:RegisterCallback("UpdateDyeData", UpdateDyeLayout)
    ZO_DYEING_MANAGER:RegisterCallback("UpdateDyeLists", UpdateDyeLayout)
    ZO_DYEING_MANAGER:RegisterCallback("UpdateSearchResults", UpdateDyeLayout)
    ZO_DYEING_MANAGER:RegisterCallback("OptionsInfoAvailable", function() self:UpdateOptionControls() end)

    self:DirtyDyeLayout()
end

function ZO_Dyeing_Keyboard:OnShowing()
    TriggerTutorial(TUTORIAL_TRIGGER_DYEING_OPENED)
    if IsESOPlusSubscriber() then
        TriggerTutorial(TUTORIAL_TRIGGER_DYEING_OPENED_AS_SUBSCRIBER)
    end

    self:UpdateOptionControls()

    if self.dyeLayoutDirty then
        self:LayoutDyes()
    end
    self:RefreshSavedSets()

    if not ZO_MenuBar_GetSelectedDescriptor(self.toolsTabs) then
        self.suppressSounds = true
        ZO_MenuBar_SelectDescriptor(self.toolsTabs, self.dyeTool)
        self.suppressSounds = false
    end
end

function ZO_Dyeing_Keyboard:OnHiding()
    if ZO_MenuBar_GetSelectedDescriptor(self.toolsTabs) then
        self.suppressSounds = true
        ZO_MenuBar_SelectDescriptor(self.toolsTabs, self.dyeTool)
        self.suppressSounds = false
    end
end

function ZO_Dyeing_Keyboard:OnToolChanged(tool)
    local currentSheet = ZO_RESTYLE_SHEET_WINDOW_KEYBOARD:GetCurrentSheet()

    local mousedOverDyeableSlotData, mousedOverDyeChannel = currentSheet:GetMousedOverDyeableSlotInfo()
    local mousedOverSavedSetIndex
    if not mousedOverDyeableSlotData then
        mousedOverSavedSetIndex, mousedOverDyeChannel = self:GetMousedOverSavedSetInfo()
    end

    local lastTool = self.activeTool
    if lastTool then
        if mousedOverDyeableSlotData and mousedOverDyeChannel then
            ZO_RESTYLE_SHEET_WINDOW_KEYBOARD:OnDyeSlotExit(mousedOverDyeableSlotData, mousedOverDyeChannel)
        elseif mousedOverSavedSetIndex and mousedOverDyeChannel then
            self:OnSavedSetDyeSlotExit(mousedOverSavedSetIndex, mousedOverDyeChannel)
        end
        self.activeTool:Deactivate(self.suppressSounds)
    end

    self.activeTool = tool

    if self.activeTool then
        self.activeTool:Activate(lastTool, self.suppressSounds)

        if mousedOverDyeableSlotData and mousedOverDyeChannel then
            ZO_RESTYLE_SHEET_WINDOW_KEYBOARD:OnDyeSlotEnter(mousedOverDyeableSlotData, mousedOverDyeChannel)
        elseif mousedOverSavedSetIndex and mousedOverDyeChannel then
            self:OnSavedSetDyeSlotEnter(mousedOverSavedSetIndex, mousedOverDyeChannel)
        end

        if self.activeTool:HasSwatchSelection() then
            local TOOL_CHANGE = true
            local autoSelectDyeId = self.selectedDyeId or self.lastSelectedDyeId
            if not autoSelectDyeId then
                local firstUnlockedPlayerDye = ZO_DYEING_MANAGER:GetUnlockedPlayerDyes()[1]
                if firstUnlockedPlayerDye then
                    autoSelectDyeId = firstUnlockedPlayerDye.dyeId
                end
            end
            self:SetSelectedDyeId(autoSelectDyeId, nil, TOOL_CHANGE)
        else
            self:SetSelectedDyeId(nil)
        end

        if self.activeTool:HasSavedSetSelection() then
            self:SetSelectedSavedSetIndex(self.selectedSavedSetIndex or self.lastSelectedSavedSetIndex)
        else
            self:SetSelectedSavedSetIndex(nil)
        end
    end
end

function ZO_Dyeing_Keyboard:InitializeTools()
    local function GenerateTab(tool, tooltip, normal, pressed, highlight, disabled)
        return {
            descriptor = tool,
            tooltip = tooltip,

            normal = normal,
            pressed = pressed,
            highlight = highlight,
            disabled = disabled,
            callback = function(tabData) self:OnToolChanged(tool) end,
        }
    end

    self.toolsTabs = self.control:GetNamedChild("Tools")

    self.dyeTool = ZO_DyeingToolDye:New(self)
    self.fillTool = ZO_DyeingToolFill:New(self)
    self.eraseTool = ZO_DyeingToolErase:New(self)
    self.sampleTool = ZO_DyeingToolSample:New(self)
    self.setFillTool = ZO_DyeingToolSetFill:New(self)

    ZO_MenuBar_AddButton(self.toolsTabs, GenerateTab(self.dyeTool, SI_DYEING_TOOL_DYE_TOOLTIP, "EsoUI/Art/Dye/dyes_toolIcon_paint_up.dds", "EsoUI/Art/Dye/dyes_toolIcon_paint_down.dds", "EsoUI/Art/Dye/dyes_toolIcon_paint_over.dds"))
    ZO_MenuBar_AddButton(self.toolsTabs, GenerateTab(self.fillTool, SI_DYEING_TOOL_DYE_ALL_TOOLTIP, "EsoUI/Art/Dye/dyes_toolIcon_fill_up.dds", "EsoUI/Art/Dye/dyes_toolIcon_fill_down.dds", "EsoUI/Art/Dye/dyes_toolIcon_fill_over.dds"))
    ZO_MenuBar_AddButton(self.toolsTabs, GenerateTab(self.eraseTool, SI_DYEING_TOOL_ERASE_TOOLTIP, "EsoUI/Art/Dye/dyes_toolIcon_erase_up.dds", "EsoUI/Art/Dye/dyes_toolIcon_erase_down.dds", "EsoUI/Art/Dye/dyes_toolIcon_erase_over.dds"))
    ZO_MenuBar_AddButton(self.toolsTabs, GenerateTab(self.sampleTool, SI_DYEING_TOOL_SAMPLE_TOOLTIP, "EsoUI/Art/Dye/dyes_toolIcon_sample_up.dds", "EsoUI/Art/Dye/dyes_toolIcon_sample_down.dds", "EsoUI/Art/Dye/dyes_toolIcon_sample_over.dds"))
    ZO_MenuBar_AddButton(self.toolsTabs, GenerateTab(self.setFillTool, SI_DYEING_TOOL_SET_FILL, "EsoUI/Art/Dye/dyes_toolIcon_setFill_up.dds", "EsoUI/Art/Dye/dyes_toolIcon_setFill_down.dds", "EsoUI/Art/Dye/dyes_toolIcon_setFill_over.dds"))

    ZO_MenuBar_ClearClickSound(self.toolsTabs)
end

local SKIP_ANIM = true
function ZO_Dyeing_Keyboard:InitializeSavedSets()
    local function UpdateSelectedState(savedSet)
        if savedSet.mousedOver or savedSet.selected then
            self.savedSetInterpolator:ScaleUp(savedSet)
        else
            self.savedSetInterpolator:ScaleDown(savedSet)
        end
    end

    local function SetSelected(savedSet, selected)
        if savedSet.selected ~= selected then
            for i, control in ipairs(savedSet.dyeControls) do
                control.highlightTexture:SetHidden(not selected)
            end
            savedSet.selected = selected
            savedSet:UpdateSelectedState()
        end
    end

    local function OnMouseEnter(savedSet)
        savedSet.mousedOver = true
        savedSet:UpdateSelectedState()
    end

    local function OnMouseExit(savedSet)
        savedSet.mousedOver = false
        savedSet:UpdateSelectedState()
    end

    self.savedSets = {}
    self.lastSelectedSavedSetIndex = 1

    local header = self.control:GetNamedChild("SavedSetsHeader")
    for dyeSetIndex = 1, GetNumSavedDyeSets() do
        local savedSetSwatch = CreateControlFromVirtual("$(parent)SavedSet", self.control, "ZO_DyeingSwatchSlotDyes", dyeSetIndex)

        savedSetSwatch.SetSelected = SetSelected
        savedSetSwatch.UpdateSelectedState = UpdateSelectedState
        savedSetSwatch.OnMouseEnter = OnMouseEnter
        savedSetSwatch.OnMouseExit = OnMouseExit
        self.savedSetInterpolator:ResetToMin(savedSetSwatch)

        for dyeChannel, dyeControl in ipairs(savedSetSwatch.dyeControls) do
            dyeControl.frameTexture:SetPixelRoundingEnabled(false)
            dyeControl.swatchTexture:SetPixelRoundingEnabled(false)
            dyeControl.highlightTexture:SetPixelRoundingEnabled(false)

            dyeControl:SetHandler("OnMouseUp", function(dyeControl, button, upInside)
                if upInside then
                    self:OnSavedSetDyeSlotClicked(dyeSetIndex, dyeChannel, button)
                end
            end)

            dyeControl:SetHandler("OnMouseEnter", function(dyeControl)
                self.mousedOverSavedSetIndex = dyeSetIndex
                self.mousedOverSavedSetDyeChannel = dyeChannel
                self:OnSavedSetDyeSlotEnter(dyeSetIndex, dyeChannel, dyeControl)
            end)

            dyeControl:SetHandler("OnMouseExit", function(dyeControl)
                self.mousedOverSavedSetIndex = nil
                self.mousedOverSavedSetDyeChannel = nil
                self:OnSavedSetDyeSlotExit(dyeSetIndex, dyeChannel)
            end)
        end

        savedSetSwatch:SetAnchor(CENTER, header, BOTTOMLEFT, 47 + 124 * (dyeSetIndex - 1), 37)
        self.savedSets[#self.savedSets + 1] = savedSetSwatch
    end
end

local HIGHLIGHT_DIMENSIONS = 5

function ZO_Dyeing_Keyboard:InitializeSwatchPool()
    local CANNOT_SELECT_LOCKED = false
    self.swatchPool = ZO_Dyeing_InitializeSwatchPool(self, self.paneScrollChild, "ZO_DyeingSwatch", CANNOT_SELECT_LOCKED)
end

function ZO_Dyeing_Keyboard:InitializeHeaderPool()
    self.headerPool = ZO_ControlPool:New("ZO_DyeingHeader", self.paneScrollChild)
end

function ZO_Dyeing_Keyboard:InitializeSortsAndFilters()
    self.showLockedCheckBox = self.control:GetNamedChild("ShowLocked")
    local sortByControl = self.control:GetNamedChild("SortBy")

    local function OnFilterChanged(checkButton, isChecked)
        ZO_DYEING_MANAGER:SetShowLocked(isChecked)
    end

    ZO_CheckButton_SetToggleFunction(self.showLockedCheckBox, OnFilterChanged)
    ZO_CheckButton_SetLabelText(self.showLockedCheckBox, GetString(SI_RESTYLE_SHOW_LOCKED))
    ZO_CheckButton_SetLabelWrapMode(self.showLockedCheckBox, TEXT_WRAP_MODE_ELLIPSIS, sortByControl:GetLeft() - self.showLockedCheckBox:GetRight() - 10)

    local function SetSortStyle(_, _, entry)
        ZO_DYEING_MANAGER:SetSortStyle(entry.sortStyleType)
    end

    self.sortDropDown = ZO_ComboBox_ObjectFromContainer(sortByControl)
    self.sortDropDown:SetSortsItems(false)

    self.sortByRarityEntry = self.sortDropDown:CreateItemEntry(GetString(SI_DYEING_SORT_BY_RARITY), SetSortStyle)
    self.sortByRarityEntry.sortStyleType = ZO_DYEING_SORT_STYLE_RARITY
    self.sortDropDown:AddItem(self.sortByRarityEntry, ZO_COMBOBOX_SUPPRESS_UPDATE)

    self.sortByHueEntry = self.sortDropDown:CreateItemEntry(GetString(SI_DYEING_SORT_BY_HUE), SetSortStyle)
    self.sortByHueEntry.sortStyleType = ZO_DYEING_SORT_STYLE_HUE
    self.sortDropDown:AddItem(self.sortByHueEntry, ZO_COMBOBOX_SUPPRESS_UPDATE)

    self.sortDropDown:UpdateItems()
end

function ZO_Dyeing_Keyboard:UpdateOptionControls()
    self.sortDropDown:SelectItem(ZO_DYEING_MANAGER:GetSortStyle() == ZO_DYEING_SORT_STYLE_RARITY and self.sortByRarityEntry or self.sortByHueEntry)
    ZO_CheckButton_SetCheckState(self.showLockedCheckBox, ZO_DYEING_MANAGER:GetShowLocked())
end

function ZO_Dyeing_Keyboard:DirtyDyeLayout()
    if self.fragment:IsShowing() then
        self:LayoutDyes()
    else
        self.dyeLayoutDirty = true
    end
end

function ZO_Dyeing_Keyboard:OnDyeSlotClicked(restyleSlotData, dyeChannel, button)
    if self:GetActiveTool() then
        self.activeTool:OnClicked(restyleSlotData, dyeChannel, button)
    end
end

function ZO_Dyeing_Keyboard:OnSavedSetDyeSlotClicked(dyeSetIndex, dyeChannel, button)
    if self:GetActiveTool() then
        self.activeTool:OnSavedSetClicked(dyeSetIndex, dyeChannel, button)
    end
end

do
    local UNKNOWN_DYE = false
    local IS_NON_PLAYER_DYE = true

    function ZO_Dyeing_Keyboard:OnSavedSetDyeSlotEnter(dyeSetIndex, dyeChannel, dyeControl)
        if self:GetActiveTool() then
            if self.activeTool:HasSavedSetSelection() then
                self.savedSets[dyeSetIndex]:OnMouseEnter()
            else
                local highlightSlot, highlightDyeChannel = self.activeTool:GetHighlightRules(dyeSetIndex, dyeChannel)
                self:ToggleSavedSetHightlight(highlightSlot, true, highlightDyeChannel)
                WINDOW_MANAGER:SetMouseCursor(self.activeTool:GetCursorType())
            end
        end
        local dyeId = select(dyeChannel, GetSavedDyeSetDyes(dyeSetIndex))
        if dyeId ~= 0 then
            local playerDyeInfo = ZO_DYEING_MANAGER:GetPlayerDyeInfoById(dyeId)
            if playerDyeInfo then
                ZO_Dyeing_CreateTooltipOnMouseEnter(dyeControl, playerDyeInfo.dyeName, playerDyeInfo.known, playerDyeInfo.achievementId)
            else
                -- Technically should never be able to get here, but you never know
                local nonPlayerDye = ZO_DYEING_MANAGER:GetOrCreateNonPlayerDyeInfoById(dyeId)
                ZO_Dyeing_CreateTooltipOnMouseEnter(dyeControl, nonPlayerDye.dyeName, UNKNOWN_DYE, nonPlayerDye.achievementId, IS_NON_PLAYER_DYE)
            end
        end
    end
end

function ZO_Dyeing_Keyboard:OnSavedSetDyeSlotExit(dyeSetIndex, dyeChannel)
    if self:GetActiveTool() == nil or not self.activeTool:HasSavedSetSelection() then
        self:ToggleSavedSetHightlight(nil, false, nil)
    end
    self.savedSets[dyeSetIndex]:OnMouseExit()
    WINDOW_MANAGER:SetMouseCursor(MOUSE_CURSOR_DO_NOT_CARE)
    ZO_Dyeing_ClearTooltipOnMouseExit()
end

function ZO_Dyeing_Keyboard:GetSelectedDyeId()
    return self.selectedDyeId
end

function ZO_Dyeing_Keyboard:GetSelectedSavedSetIndex()
    return self.selectedSavedSetIndex
end

function ZO_Dyeing_Keyboard:GetMousedOverSavedSetInfo()
    return self.mousedOverSavedSetIndex, self.mousedOverSavedSetDyeChannel
end

function ZO_Dyeing_Keyboard:OnSavedSetSlotChanged(dyeSetIndex)
    if dyeSetIndex then
        self:RefreshSavedSet(dyeSetIndex)
    else
        self:RefreshSavedSets()
    end
end

function ZO_Dyeing_Keyboard:CancelExit()
    MAIN_MENU_MANAGER:CancelBlockingSceneNextScene()
end

function ZO_Dyeing_Keyboard:SwitchToDyeingWithDyeId(dyeId, suppressSounds)
    self.suppressSounds = suppressSounds

    local toolChanged = false
    if not self.activeTool:HasSwatchSelection() then
        ZO_MenuBar_SelectDescriptor(self.toolsTabs, self.dyeTool)
        toolChanged = true
    end
    self:SetSelectedDyeId(dyeId, nil, toolChanged)

    local swatchObject = self.dyeIdToSwatch[dyeId]
    if swatchObject then
        ZO_Scroll_ScrollControlIntoCentralView(self.pane, swatchObject.control)
    end

    self.suppressSounds = false
end

function ZO_Dyeing_Keyboard:SetSelectedDyeId(dyeId, becauseOfRebuild, becauseToolChange)
    if self.selectedDyeId ~= dyeId or becauseOfRebuild then
        if not becauseOfRebuild then
            local oldSwatchObject = self.dyeIdToSwatch[self.selectedDyeId]
            if oldSwatchObject then
                oldSwatchObject:SetSelected(false)
            end
        end

        if self.selectedDyeId then
            self.lastSelectedDyeId = self.selectedDyeId
        end

        self.selectedDyeId = dyeId

        local newSwatchObject = self.activeTool:HasSwatchSelection() and self.dyeIdToSwatch[self.selectedDyeId]
        if newSwatchObject then
            local skipAnim = becauseOfRebuild
            local skipSound = becauseOfRebuild or becauseToolChange
            newSwatchObject:SetSelected(true, skipAnim, skipSound)
        end
    end
end

function ZO_Dyeing_Keyboard:SetSelectedSavedSetIndex(dyeSetIndex)
    if self.selectedSavedSetIndex ~= dyeSetIndex then
        if self.selectedSavedSetIndex then
            self.savedSets[self.selectedSavedSetIndex]:SetSelected(false)
        end

        self.lastSelectedSavedSetIndex = self.selectedSavedSetIndex
        self.selectedSavedSetIndex = dyeSetIndex

        if self.selectedSavedSetIndex then
            self.savedSets[self.selectedSavedSetIndex]:SetSelected(true)

            if self.lastSelectedSavedSetIndex then
                PlaySound(SOUNDS.DYEING_SAVED_SET_SELECTED)
            end
        end
    end
end

function ZO_Dyeing_Keyboard:ToggleSavedSetHightlightBySlotControl(slotControl, isHighlighted, dyeChannel)
    if dyeChannel ~= nil then
        slotControl.dyeControls[dyeChannel].highlightTexture:SetHidden(not isHighlighted)
    else
        for dyeChannel, dyeControl in ipairs(slotControl.dyeControls) do
            dyeControl.highlightTexture:SetHidden(not isHighlighted)
        end
    end
end

function ZO_Dyeing_Keyboard:ToggleSavedSetHightlight(dyeSetIndex, isHighlighted, dyeChannel)
    if dyeSetIndex ~= nil then
        local slotControl = self.savedSets[dyeSetIndex]
        self:ToggleSavedSetHightlightBySlotControl(slotControl, isHighlighted, dyeChannel)
    else
        for _, slotControl in ipairs(self.savedSets) do
            self:ToggleSavedSetHightlightBySlotControl(slotControl, isHighlighted, dyeChannel)
        end
    end
end

do
    local USE_SEARCH_RESULTS = true
    local BECAUSE_OF_REBUILD = true

    local SWATCHES_LAYOUT_OPTIONS = 
    {
        padding = 6,
        leftMargin = 27,
        topMargin = 18,
        rightMargin = 0,
        bottomMargin = 0,
        selectionScale = ZO_DYEING_SWATCH_SELECTION_SCALE,
    }

    function ZO_Dyeing_Keyboard:LayoutDyes()
        self.dyeLayoutDirty = false

        local _, _, dyeIdToSwatch = ZO_Dyeing_LayoutSwatches(ZO_DYEING_MANAGER:GetShowLocked(), ZO_DYEING_MANAGER:GetSortStyle(), self.swatchPool, self.headerPool, SWATCHES_LAYOUT_OPTIONS, self.pane, USE_SEARCH_RESULTS)
        self.dyeIdToSwatch = dyeIdToSwatch

        local anyDyesToSwatch = (next(dyeIdToSwatch) ~= nil)
        self.noDyesLabel:SetHidden(anyDyesToSwatch)
        if self.selectedDyeId then
            self:SetSelectedDyeId(self.selectedDyeId, BECAUSE_OF_REBUILD)
        end
    end
end

function ZO_Dyeing_Keyboard:AttemptExit(exitingToAchievementId)
    local exitDestinationData = { achievementId = exitingToAchievementId, }
    ZO_RESTYLE_STATION_KEYBOARD:AttemptExit(exitDestinationData)
end

function ZO_Dyeing_Keyboard:RefreshSavedSet(dyeSetIndex)
    local savedSetSwatch = self.savedSets[dyeSetIndex]
    for dyeChannel, dyeControl in ipairs(savedSetSwatch.dyeControls) do
        local currentDyeId = select(dyeChannel, GetSavedDyeSetDyes(dyeSetIndex))
        ZO_DyeingUtils_SetSlotDyeSwatchDyeId(dyeControl, currentDyeId)
    end
end

function ZO_Dyeing_Keyboard:RefreshSavedSets()
    for dyeSetIndex in ipairs(self.savedSets) do
        self:RefreshSavedSet(dyeSetIndex)
    end
end

function ZO_Dyeing_Keyboard:GetSwatchControlFromDyeId(dyeId)
    local swatchObject = self.dyeIdToSwatch[dyeId]
    if swatchObject then
        return swatchObject.control
    end
end

function ZO_Dyeing_Keyboard:GetActiveTool()
    if self.fragment:IsShowing() then
        return self.activeTool
    end
    return nil
end

function ZO_Dyeing_Keyboard:OnPendingDyesChanged(restyleSlotData)
    ZO_RESTYLE_STATION_KEYBOARD:OnPendingDyesChanged(restyleSlotData)
end

function ZO_Dyeing_Keyboard_OnInitialized(control)
    ZO_DYEING_KEYBOARD = ZO_Dyeing_Keyboard:New(control)
end