local _, Addon = ...

local AddonButtons = {}
Addon.AddonButtons = AddonButtons
local L = Addon.L

local mapFrame
local anchorFrame
local hubButton
local settingsButton
local bossButton
local addsButton
local routeButton
local gatheringButton
local servicesButton
local folioButton
local optionsButton
local panel
local panelTitle
local emptyText
local mapMenu
local enabled = false
local managed = {}
local compartmentWasShown
local slots = {}
local folioAvailable = false

local BUTTON_SIZE = 34
local PANEL_PADDING = 10
local PANEL_HEADER = 25
local SLOT_INSET = 3
local MAP_MENU_WIDTH = 212
local MAP_MENU_ROW_HEIGHT = 32

local excludedNames = {
    AddonCompartmentFrame = true,
    ExpansionLandingPageMinimapButton = true,
    GameTimeFrame = true,
    GarrisonLandingPageMinimapButton = true,
    MiniMapMailFrame = true,
    MiniMapTracking = true,
    MiniMapWorldMapButton = true,
    MinimapZoomIn = true,
    MinimapZoomOut = true,
    MinimapZoneTextButton = true,
    MapaEnMiniaturaAddonHubButton = true,
    MapaEnMiniaturaMinimapButton = true,
    MapaEnMiniaturaSettingsButton = true,
    MapaEnMiniaturaBossButton = true,
    MapaEnMiniaturaAddsButton = true,
    MapaEnMiniaturaRouteButton = true,
    MapaEnMiniaturaGatheringButton = true,
    MapaEnMiniaturaServicesButton = true,
    MapaEnMiniaturaFolioButton = true,
    MapaEnMiniaturaOptionsButton = true,
    QueueStatusButton = true,
}

local function IsProtectedInCombat(button)
    return InCombatLockdown()
        and button.IsProtected
        and button:IsProtected()
end

local function IsAddonButton(button)
    if not button or not button.IsObjectType or not button:IsObjectType("Button") then
        return false
    end
    if button.IsForbidden and button:IsForbidden() then
        return false
    end
    if IsProtectedInCombat(button) then
        return false
    end

    local parent = button:GetParent()
    if parent ~= Minimap and parent ~= MinimapCluster and parent ~= panel and not managed[button] then
        return false
    end

    local name = button:GetName()
    if name and excludedNames[name] then
        return false
    end

    local lowerName = name and string.lower(name) or ""
    return button.dataObject ~= nil
        or string.find(lowerName, "libdbicon10_", 1, true) ~= nil
        or (
            string.find(lowerName, "minimap", 1, true) ~= nil
            and string.find(lowerName, "button", 1, true) ~= nil
        )
end

local function SavePoints(button)
    local points = {}
    for index = 1, button:GetNumPoints() do
        points[index] = { button:GetPoint(index) }
    end
    return points
end

local function FindButtonIcon(button)
    local candidates = {
        button.icon,
        button.Icon,
        button.iconTexture,
        button.IconTexture,
    }
    for _, texture in ipairs(candidates) do
        if texture and texture.IsObjectType and texture:IsObjectType("Texture") then
            return texture
        end
    end

    local fallback
    for _, region in ipairs({ button:GetRegions() }) do
        if region.IsObjectType and region:IsObjectType("Texture") then
            fallback = fallback or region
            if region:GetDrawLayer() == "ARTWORK" then
                return region
            end
        end
    end
    return fallback
end

local function SaveButtonStyle(button, info)
    if info.styleSaved then
        return
    end

    info.styleSaved = true
    info.icon = FindButtonIcon(button)
    info.regions = {}
    for _, region in ipairs({ button:GetRegions() }) do
        if region.IsObjectType and region:IsObjectType("Texture") then
            info.regions[#info.regions + 1] = {
                region = region,
                alpha = region:GetAlpha(),
                width = region:GetWidth(),
                height = region:GetHeight(),
                points = SavePoints(region),
            }
        end
    end
end

local function StyleButton(button, info, slot, iconSize)
    SaveButtonStyle(button, info)

    button:SetScale(1)
    button:SetSize(
        math.max(iconSize - (SLOT_INSET * 2), 1),
        math.max(iconSize - (SLOT_INSET * 2), 1)
    )
    button:ClearAllPoints()
    button:SetPoint("TOPLEFT", slot, "TOPLEFT", SLOT_INSET, -SLOT_INSET)
    button:SetPoint("BOTTOMRIGHT", slot, "BOTTOMRIGHT", -SLOT_INSET, SLOT_INSET)

    if info.icon then
        for _, state in ipairs(info.regions) do
            state.region:SetAlpha(state.region == info.icon and 1 or 0)
        end
        info.icon:ClearAllPoints()
        info.icon:SetAllPoints(button)
    end
end

local function RestoreButtonStyle(button, info)
    if not info.styleSaved then
        return
    end

    for _, state in ipairs(info.regions or {}) do
        local region = state.region
        if region then
            region:SetAlpha(state.alpha or 1)
            region:ClearAllPoints()
            if state.width and state.height and state.width > 0 and state.height > 0 then
                region:SetSize(state.width, state.height)
            end
            for _, point in ipairs(state.points or {}) do
                region:SetPoint(unpack(point))
            end
        end
    end
    button:SetSize(info.width or 1, info.height or info.width or 1)
end

local function IsLibDBIconButton(button)
    local name = button:GetName()
    return button.dataObject ~= nil
        or (name and string.find(name, "LibDBIcon10_", 1, true) == 1)
end

local function UnlockFrameOrder(button)
    if button.SetFixedFrameStrata then
        button:SetFixedFrameStrata(false)
    end
    if button.SetFixedFrameLevel then
        button:SetFixedFrameLevel(false)
    end
end

local function ManageButton(button)
    if managed[button] or not IsAddonButton(button) then
        return
    end

    local info = {
        parent = button:GetParent(),
        points = SavePoints(button),
        strata = button:GetFrameStrata(),
        level = button:GetFrameLevel(),
        scale = button:GetScale(),
        width = math.max(button:GetWidth() or 1, 1),
        height = math.max(button:GetHeight() or 1, 1),
        fixedOrder = IsLibDBIconButton(button),
    }

    local ok = pcall(function()
        UnlockFrameOrder(button)
        button:SetParent(panel)
        button:SetFrameStrata("DIALOG")
        button:SetFrameLevel(panel:GetFrameLevel() + 5)
    end)
    if ok then
        managed[button] = info
    end
end

local function RestoreButton(button, info)
    if not button or (button.IsForbidden and button:IsForbidden()) or IsProtectedInCombat(button) then
        return false
    end

    return pcall(function()
        UnlockFrameOrder(button)
        RestoreButtonStyle(button, info)
        button:SetParent(info.parent or Minimap)
        button:SetFrameStrata(info.strata or "MEDIUM")
        button:SetFrameLevel(info.level or 1)
        button:SetScale(info.scale or 1)
        button:ClearAllPoints()
        for _, point in ipairs(info.points) do
            button:SetPoint(unpack(point))
        end
        if info.fixedOrder then
            if button.SetFixedFrameStrata then
                button:SetFixedFrameStrata(true)
            end
            if button.SetFixedFrameLevel then
                button:SetFixedFrameLevel(true)
            end
        end
    end)
end

local function AddCandidate(candidates, seen, button)
    if button and not seen[button] and IsAddonButton(button) then
        seen[button] = true
        candidates[#candidates + 1] = button
    end
end

local function SaveAnchorPosition()
    local centerX, centerY = anchorFrame:GetCenter()
    if not centerX or not centerY then
        return
    end

    Addon.db.addonHubMoved = true
    Addon.db.addonHubX = centerX
    Addon.db.addonHubY = centerY
    anchorFrame:ClearAllPoints()
    anchorFrame:SetPoint("CENTER", UIParent, "BOTTOMLEFT", centerX, centerY)
end

local function StartAnchorDrag()
    if Addon.db.locked then
        return
    end
    panel:Hide()
    mapMenu:Hide()
    anchorFrame._wasDragged = true
    anchorFrame._moving = true
    anchorFrame:StartMoving()
end

local function StopAnchorDrag()
    if not anchorFrame._moving then
        return
    end
    anchorFrame._moving = false
    anchorFrame:StopMovingOrSizing()
    SaveAnchorPosition()
    C_Timer.After(0, function()
        anchorFrame._wasDragged = false
    end)
end

local function AddDragScripts(button)
    button:RegisterForDrag("LeftButton")
    button:SetScript("OnDragStart", StartAnchorDrag)
    button:SetScript("OnDragStop", StopAnchorDrag)
end

local function CreateLauncherButton(name, parent, iconPath, useAtlas, red, green, blue)
    local button = CreateFrame("Button", name, parent, "BackdropTemplate")
    button:SetSize(BUTTON_SIZE, BUTTON_SIZE)
    button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    AddDragScripts(button)

    button:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
    })
    button:SetBackdropColor(0.018, 0.025, 0.035, 0.96)
    button:SetBackdropBorderColor(red, green, blue, 0.95)
    button.normalBorder = { red, green, blue }

    local icon = button:CreateTexture(nil, "ARTWORK")
    icon:SetPoint("CENTER")
    icon:SetSize(20, 20)
    if useAtlas then
        icon:SetAtlas(iconPath, false)
    else
        icon:SetTexture(iconPath)
        icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
    end
    button.icon = icon

    local highlight = button:CreateTexture(nil, "HIGHLIGHT")
    highlight:SetAllPoints()
    highlight:SetColorTexture(1, 1, 1, 0.12)
    button:SetHighlightTexture(highlight)

    local pushed = button:CreateTexture(nil, "ARTWORK", nil, 2)
    pushed:SetAllPoints()
    pushed:SetColorTexture(0, 0, 0, 0.28)
    button:SetPushedTexture(pushed)
    return button
end

local function CreateMapMenuRow(name, iconPath, labelText, red, green, blue)
    local button = CreateFrame("Button", name, mapMenu, "BackdropTemplate")
    button:SetSize(MAP_MENU_WIDTH - 20, MAP_MENU_ROW_HEIGHT)
    button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    button:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
    })
    button:SetBackdropColor(0.025, 0.04, 0.055, 0.98)
    button:SetBackdropBorderColor(red, green, blue, 0.82)
    button.normalBorder = { red, green, blue }

    local icon = button:CreateTexture(nil, "ARTWORK")
    icon:SetSize(21, 21)
    icon:SetPoint("LEFT", 7, 0)
    icon:SetTexture(iconPath)
    icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
    button.icon = icon

    local label = button:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    label:SetPoint("LEFT", icon, "RIGHT", 8, 0)
    label:SetPoint("RIGHT", button, "RIGHT", -31, 0)
    label:SetJustifyH("LEFT")
    label:SetText(labelText)
    button.label = label

    local check = button:CreateTexture(nil, "OVERLAY")
    check:SetSize(20, 20)
    check:SetPoint("RIGHT", -6, 0)
    check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
    check:Hide()
    button.check = check

    local highlight = button:CreateTexture(nil, "HIGHLIGHT")
    highlight:SetAllPoints()
    highlight:SetColorTexture(1, 1, 1, 0.1)
    button:SetHighlightTexture(highlight)
    return button
end

local function CreateMapMenu()
    mapMenu = CreateFrame("Frame", "MapaEnMiniaturaMapMenu", UIParent, "BackdropTemplate")
    mapMenu:SetSize(MAP_MENU_WIDTH, 250)
    mapMenu:SetFrameStrata("DIALOG")
    mapMenu:SetFrameLevel(40)
    mapMenu:SetClampedToScreen(true)
    mapMenu:SetToplevel(true)
    mapMenu:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true,
        tileSize = 16,
        edgeSize = 12,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    mapMenu:SetBackdropColor(0.012, 0.018, 0.026, 0.98)
    mapMenu:SetBackdropBorderColor(1, 0.55, 0.12, 0.95)
    mapMenu:Hide()

    local title = mapMenu:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetPoint("TOPLEFT", 10, -8)
    title:SetText(L.MAP)

    local closeButton = CreateFrame("Button", nil, mapMenu)
    closeButton:SetSize(18, 18)
    closeButton:SetPoint("TOPRIGHT", -5, -5)
    local closeIcon = closeButton:CreateTexture(nil, "ARTWORK")
    closeIcon:SetAllPoints()
    closeIcon:SetAtlas("uitools-icon-close", false)
    closeButton:SetHighlightTexture(closeIcon, "ADD")
    closeButton:SetScript("OnClick", function()
        mapMenu:Hide()
    end)

    addsButton = CreateMapMenuRow(
        "MapaEnMiniaturaAddsButton",
        "Interface\\Icons\\INV_Misc_MonsterClaw_04",
        L.ENEMIES,
        0.12, 0.82, 0.66
    )
    addsButton:SetPoint("TOPLEFT", mapMenu, "TOPLEFT", 10, -30)

    bossButton = CreateMapMenuRow(
        "MapaEnMiniaturaBossButton",
        "Interface\\Icons\\INV_Misc_Bone_HumanSkull_01",
        L.BOSSES,
        0.82, 0.28, 0.28
    )
    bossButton:SetPoint("TOP", addsButton, "BOTTOM", 0, -4)

    routeButton = CreateMapMenuRow(
        "MapaEnMiniaturaRouteButton",
        "Interface\\Icons\\INV_Misc_Map_01",
        L.MDT_ROUTES,
        0.72, 0.42, 1
    )
    routeButton:SetPoint("TOP", bossButton, "BOTTOM", 0, -4)

    gatheringButton = CreateMapMenuRow(
        "MapaEnMiniaturaGatheringButton",
        "Interface\\Icons\\INV_Misc_Herb_11",
        L.GATHERING_MENU,
        0.42, 0.82, 0.34
    )
    gatheringButton:SetPoint("TOP", routeButton, "BOTTOM", 0, -4)

    servicesButton = CreateMapMenuRow(
        "MapaEnMiniaturaServicesButton",
        "Interface\\Icons\\INV_Misc_Coin_02",
        L.NEARBY_SERVICES,
        0.28, 0.68, 1
    )
    servicesButton:SetPoint("TOP", gatheringButton, "BOTTOM", 0, -4)

    optionsButton = CreateMapMenuRow(
        "MapaEnMiniaturaOptionsButton",
        "Interface\\Icons\\INV_Misc_Gear_01",
        L.SETTINGS,
        1, 0.62, 0.12
    )
    optionsButton:SetPoint("TOP", servicesButton, "BOTTOM", 0, -4)
end

local function PositionMapMenu()
    local anchorX = anchorFrame:GetCenter() or (UIParent:GetWidth() / 2)
    local opensLeft = anchorX > (UIParent:GetWidth() / 2)
    mapMenu:ClearAllPoints()
    if opensLeft then
        mapMenu:SetPoint("TOPRIGHT", settingsButton, "TOPLEFT", -6, 0)
    else
        mapMenu:SetPoint("TOPLEFT", settingsButton, "TOPRIGHT", 6, 0)
    end
end

local function CopyFolioIcon(sourceButton)
    if not folioButton then
        return
    end

    local sourceIcon = FindButtonIcon(sourceButton)
    if not sourceIcon then
        return
    end

    local atlas = sourceIcon.GetAtlas and sourceIcon:GetAtlas()
    if atlas then
        folioButton.icon:SetAtlas(atlas, false)
        return
    end

    local texture = sourceIcon:GetTexture()
    if texture then
        folioButton.icon:SetTexture(texture)
        folioButton.icon:SetTexCoord(sourceIcon:GetTexCoord())
    end
end

local function UpdateAnchorButtons()
    local showHub = enabled and Addon.db.showAddonButtons
    local showFolio = enabled and folioAvailable
    local buttons = {}

    if showHub then
        buttons[#buttons + 1] = hubButton
    else
        panel:Hide()
    end
    if showFolio then
        buttons[#buttons + 1] = folioButton
    end
    if enabled then
        buttons[#buttons + 1] = settingsButton
    end

    hubButton:SetShown(showHub)
    folioButton:SetShown(showFolio)
    settingsButton:SetShown(enabled)

    for index, button in ipairs(buttons) do
        button:ClearAllPoints()
        if index == 1 then
            button:SetPoint("TOP", anchorFrame, "TOP", 0, 0)
        else
            button:SetPoint("TOP", buttons[index - 1], "BOTTOM", 0, -4)
        end
    end

    local buttonCount = #buttons
    anchorFrame:SetHeight(
        (BUTTON_SIZE * math.max(buttonCount, 1)) + (4 * math.max(buttonCount - 1, 0))
    )
end

local function CreateLaunchers()
    anchorFrame = CreateFrame("Frame", "MapaEnMiniaturaLauncherAnchor", UIParent)
    anchorFrame:SetSize(BUTTON_SIZE, (BUTTON_SIZE * 3) + 8)
    anchorFrame:SetFrameStrata("LOW")
    anchorFrame:SetFrameLevel(30)
    anchorFrame:SetMovable(true)
    anchorFrame:SetClampedToScreen(true)

    hubButton = CreateLauncherButton(
        "MapaEnMiniaturaAddonHubButton",
        anchorFrame,
        "bag-main",
        true,
        0.12, 0.72, 1
    )
    hubButton:SetPoint("TOP", anchorFrame, "TOP")

    local countBadge = hubButton:CreateTexture(nil, "OVERLAY", nil, 1)
    countBadge:SetSize(16, 16)
    countBadge:SetPoint("TOPRIGHT", 5, 5)
    countBadge:SetTexture(186178)
    countBadge:SetVertexColor(0.02, 0.38, 0.58, 1)
    hubButton.countBadge = countBadge

    local countText = hubButton:CreateFontString(nil, "OVERLAY", "GameFontNormalTiny")
    countText:SetPoint("CENTER", countBadge, "CENTER", 0, 0)
    countText:SetTextColor(1, 1, 1)
    countText:SetText("0")
    hubButton.countText = countText

    settingsButton = CreateLauncherButton(
        "MapaEnMiniaturaSettingsButton",
        anchorFrame,
        "Interface\\Icons\\INV_Misc_Gear_01",
        false,
        1, 0.62, 0.12
    )

    folioButton = CreateLauncherButton(
        "MapaEnMiniaturaFolioButton",
        anchorFrame,
        "Interface\\Icons\\INV_Misc_Book_09",
        false,
        0.92, 0.72, 0.22
    )

    hubButton:SetScript("OnClick", function()
        if anchorFrame._wasDragged then
            return
        end
        AddonButtons:Refresh()
        mapMenu:Hide()
        panel:SetShown(not panel:IsShown())
    end)
    hubButton:SetScript("OnEnter", function(button)
        button:SetBackdropBorderColor(0.45, 0.9, 1, 1)
        GameTooltip:SetOwner(button, "ANCHOR_LEFT")
        GameTooltip:SetText(L.ADDON_BUTTONS)
        GameTooltip:AddLine(L.CLICK_OPEN_CONTAINER, 1, 1, 1)
        GameTooltip:AddLine(L.DRAG_MOVE_BUTTONS, 0.65, 0.82, 1)
        GameTooltip:Show()
    end)
    hubButton:SetScript("OnLeave", function(button)
        button:SetBackdropBorderColor(unpack(button.normalBorder))
        GameTooltip_Hide()
    end)

    folioButton:SetScript("OnClick", function(_, mouseButton)
        if anchorFrame._wasDragged then
            return
        end

        local source = folioButton.source
        if source and source.Click then
            pcall(source.Click, source, mouseButton)
        end
    end)
    folioButton:SetScript("OnEnter", function(button)
        button:SetBackdropBorderColor(1, 0.88, 0.42, 1)
        GameTooltip:SetOwner(button, "ANCHOR_LEFT")
        local source = folioButton.source
        GameTooltip:SetText((source and source.title) or "Folio Omnium")
        GameTooltip:AddLine(L.CLICK_OPEN_FOLIO, 1, 1, 1)
        GameTooltip:AddLine(L.DRAG_MOVE_BUTTONS, 0.65, 0.82, 1)
        GameTooltip:Show()
    end)
    folioButton:SetScript("OnLeave", function(button)
        button:SetBackdropBorderColor(unpack(button.normalBorder))
        GameTooltip_Hide()
    end)

    settingsButton:SetScript("OnClick", function()
        if anchorFrame._wasDragged then
            return
        end
        panel:Hide()
        if mapMenu:IsShown() then
            mapMenu:Hide()
        else
            AddonButtons:UpdateBossButton()
            AddonButtons:UpdateAddsButton()
            AddonButtons:UpdateRouteButton()
            AddonButtons:UpdateGatheringButton()
            AddonButtons:UpdateServicesButton()
            PositionMapMenu()
            mapMenu:Show()
        end
    end)
    settingsButton:SetScript("OnEnter", function(button)
        button:SetBackdropBorderColor(1, 0.85, 0.38, 1)
        GameTooltip:SetOwner(button, "ANCHOR_LEFT")
        GameTooltip:SetText(L.MAP_CONTROLS)
        GameTooltip:AddLine(L.CLICK_OPEN, 1, 1, 1)
        GameTooltip:AddLine(L.DRAG_MOVE_BUTTONS, 0.65, 0.82, 1)
        GameTooltip:Show()
    end)
    settingsButton:SetScript("OnLeave", function(button)
        button:SetBackdropBorderColor(unpack(button.normalBorder))
        GameTooltip_Hide()
    end)

    bossButton:SetScript("OnClick", function()
        Addon.Markers:SetBossesShown(not Addon.db.showBosses)
        AddonButtons:UpdateBossButton()
    end)
    bossButton:SetScript("OnEnter", function(button)
        button:SetBackdropBorderColor(1, 0.45, 0.4, 1)
        GameTooltip:SetOwner(button, "ANCHOR_LEFT")
        GameTooltip:SetText(L.DUNGEON_BOSSES)
        GameTooltip:AddLine(Addon.db.showBosses and L.VISIBLE_PLURAL or L.HIDDEN_PLURAL, 0.75, 0.85, 1)
        GameTooltip:AddLine(L.CLICK_SHOW_HIDE, 1, 1, 1)
        GameTooltip:Show()
    end)
    bossButton:SetScript("OnLeave", function(button)
        button:SetBackdropBorderColor(unpack(button.normalBorder))
        GameTooltip_Hide()
    end)

    addsButton:SetScript("OnClick", function()
        Addon.Markers:SetAddsShown(not Addon.db.showAdds)
        AddonButtons:UpdateAddsButton()
    end)
    addsButton:SetScript("OnEnter", function(button)
        button:SetBackdropBorderColor(0.35, 1, 0.78, 1)
        GameTooltip:SetOwner(button, "ANCHOR_LEFT")
        GameTooltip:SetText(L.DUNGEON_ENEMIES)
        GameTooltip:AddLine(Addon.db.showAdds and L.VISIBLE_PLURAL or L.HIDDEN_PLURAL, 0.75, 0.85, 1)
        GameTooltip:AddLine(L.INTEGRATED_ENEMY_DATABASE, 0.45, 0.8, 1)
        GameTooltip:AddLine(L.CLICK_SHOW_HIDE, 1, 1, 1)
        GameTooltip:Show()
    end)
    addsButton:SetScript("OnLeave", function(button)
        button:SetBackdropBorderColor(unpack(button.normalBorder))
        GameTooltip_Hide()
    end)

    routeButton:SetScript("OnClick", function(_, mouseButton)
        if mouseButton == "RightButton" and Addon.Routes:HasRoutes() then
            Addon.Routes:SetVisible(not Addon.db.showRoute)
            AddonButtons:UpdateRouteButton()
        else
            mapMenu:Hide()
            Addon.Routes:OpenImportDialog()
        end
    end)
    routeButton:SetScript("OnEnter", function(button)
        button:SetBackdropBorderColor(0.82, 0.62, 1, 1)
        GameTooltip:SetOwner(button, "ANCHOR_LEFT")
        local route = Addon.Routes:GetRoute()
        local routeCount = Addon.Routes:GetRouteCount()
        GameTooltip:SetText(L.MDT_ROUTES)
        if route then
            GameTooltip:AddLine(route.name, 1, 0.82, 0)
            GameTooltip:AddLine(
                Addon:Format("ROUTE_SUMMARY", route.dungeonName or L.DUNGEON, #route.pulls),
                0.75, 0.85, 1
            )
            GameTooltip:AddLine(Addon.db.showRoute and L.VISIBLE or L.ROUTE_HIDDEN_LABEL, 0.75, 0.85, 1)
            GameTooltip:AddLine(
                Addon:Format(routeCount == 1 and "ROUTE_COUNT_ONE" or "ROUTE_COUNT_MANY", routeCount),
                0.65, 0.82, 1
            )
            GameTooltip:AddLine(L.RIGHT_CLICK_SHOW_HIDE, 1, 1, 1)
        else
            GameTooltip:AddLine(L.NO_IMPORTED_ROUTE, 0.75, 0.75, 0.75)
        end
        GameTooltip:AddLine(L.CLICK_IMPORT_MANAGE, 1, 1, 1)
        GameTooltip:Show()
    end)
    routeButton:SetScript("OnLeave", function(button)
        button:SetBackdropBorderColor(unpack(button.normalBorder))
        GameTooltip_Hide()
    end)

    gatheringButton:SetScript("OnClick", function()
        Addon.Gathering:SetShown(not Addon.Gathering:IsEnabled(), true)
        AddonButtons:UpdateGatheringButton()
    end)
    gatheringButton:SetScript("OnEnter", function(button)
        button:SetBackdropBorderColor(0.7, 1, 0.48, 1)
        GameTooltip:SetOwner(button, "ANCHOR_LEFT")
        GameTooltip:SetText(L.GATHERING_TITLE)
        GameTooltip:AddLine(
            Addon.Gathering:IsPaused()
                and L.GATHERING_PAUSED
                or (Addon.Gathering:IsEnabled() and L.GATHERING_ENABLED or L.GATHERING_DISABLED),
            0.75, 0.85, 1
        )
        GameTooltip:AddLine(L.GATHERING_DETAIL, 0.45, 0.8, 1)
        GameTooltip:AddLine(L.GATHERING_CLICK, 1, 1, 1)
        GameTooltip:Show()
    end)
    gatheringButton:SetScript("OnLeave", function(button)
        button:SetBackdropBorderColor(unpack(button.normalBorder))
        GameTooltip_Hide()
    end)

    servicesButton:SetScript("OnClick", function()
        Addon.Gathering:SetNativeServicesShown(not Addon.Gathering:AreNativeServicesShown(), true)
        AddonButtons:UpdateServicesButton()
    end)
    servicesButton:SetScript("OnEnter", function(button)
        button:SetBackdropBorderColor(0.48, 0.82, 1, 1)
        GameTooltip:SetOwner(button, "ANCHOR_LEFT")
        GameTooltip:SetText(L.NEARBY_SERVICES)
        GameTooltip:AddLine(
            Addon.Gathering:AreNativeServicesShown() and L.VISIBLE_PLURAL or L.HIDDEN_PLURAL,
            0.75, 0.85, 1
        )
        GameTooltip:AddLine(L.NEARBY_SERVICES_DETAIL, 0.45, 0.8, 1)
        GameTooltip:AddLine(L.GATHERING_CLICK, 1, 1, 1)
        GameTooltip:Show()
    end)
    servicesButton:SetScript("OnLeave", function(button)
        button:SetBackdropBorderColor(unpack(button.normalBorder))
        GameTooltip_Hide()
    end)

    optionsButton:SetScript("OnClick", function()
        mapMenu:Hide()
        Addon.Options:Toggle()
    end)
    optionsButton:SetScript("OnEnter", function(button)
        button:SetBackdropBorderColor(1, 0.85, 0.38, 1)
        GameTooltip:SetOwner(button, "ANCHOR_LEFT")
        GameTooltip:SetText(L.SETTINGS)
        GameTooltip:AddLine(L.OPEN_OPTIONS, 1, 1, 1)
        GameTooltip:Show()
    end)
    optionsButton:SetScript("OnLeave", function(button)
        button:SetBackdropBorderColor(unpack(button.normalBorder))
        GameTooltip_Hide()
    end)

    AddonButtons:UpdateBossButton()
    AddonButtons:UpdateAddsButton()
    AddonButtons:UpdateRouteButton()
    AddonButtons:UpdateGatheringButton()
    AddonButtons:UpdateServicesButton()
    AddonButtons:UpdateFolioButton(false)

    anchorFrame:SetPoint("TOPRIGHT", mapFrame, "TOPLEFT", -7, 0)
    anchorFrame:Hide()
end

function AddonButtons:UpdateFolioButton(refreshNative)
    if not folioButton then
        return false
    end

    local source = _G.ExpansionLandingPageMinimapButton
    if source
        and not source:IsShown()
        and refreshNative
        and not InCombatLockdown()
        and source.RefreshButton then
        pcall(source.RefreshButton, source, true)
    end

    local available = source ~= nil
        and not (source.IsForbidden and source:IsForbidden())
        and source:IsShown()
    local changed = available ~= folioAvailable
    folioAvailable = available
    folioButton.source = source

    if available then
        CopyFolioIcon(source)
    end
    return changed
end

function AddonButtons:UpdateBossButton()
    if not bossButton then
        return
    end
    local visible = Addon.db.showBosses
    local red, green, blue = 0.82, 0.28, 0.28
    if not visible then
        red, green, blue = 0.38, 0.42, 0.48
    end
    bossButton.normalBorder = { red, green, blue }
    bossButton:SetBackdropBorderColor(red, green, blue, 0.95)
    bossButton.icon:SetDesaturated(not visible)
    bossButton.icon:SetAlpha(visible and 1 or 0.55)
    bossButton.label:SetTextColor(visible and 1 or 0.65, visible and 1 or 0.68, visible and 1 or 0.72)
    bossButton.check:SetShown(visible)
end

function AddonButtons:UpdateAddsButton()
    if not addsButton then
        return
    end
    local visible = Addon.db.showAdds
    local red, green, blue = 0.12, 0.82, 0.66
    if not visible then
        red, green, blue = 0.38, 0.42, 0.48
    end
    addsButton.normalBorder = { red, green, blue }
    addsButton:SetBackdropBorderColor(red, green, blue, 0.95)
    addsButton.icon:SetDesaturated(not visible)
    addsButton.icon:SetAlpha(visible and 1 or 0.55)
    addsButton.label:SetTextColor(visible and 1 or 0.65, visible and 1 or 0.68, visible and 1 or 0.72)
    addsButton.check:SetShown(visible)
end

function AddonButtons:UpdateRouteButton()
    if not routeButton then
        return
    end
    local hasRoutes = Addon.Routes and Addon.Routes:HasRoutes()
    local visible = hasRoutes and Addon.db.showRoute
    local red, green, blue = 0.72, 0.42, 1
    if not hasRoutes then
        red, green, blue = 0.38, 0.42, 0.48
    elseif not visible then
        red, green, blue = 0.48, 0.4, 0.58
    end
    routeButton.normalBorder = { red, green, blue }
    routeButton:SetBackdropBorderColor(red, green, blue, 0.95)
    routeButton.icon:SetDesaturated(not visible)
    routeButton.icon:SetAlpha(visible and 1 or 0.55)
    routeButton.label:SetTextColor(hasRoutes and 1 or 0.65, hasRoutes and 1 or 0.68, hasRoutes and 1 or 0.72)
    routeButton.check:SetShown(visible)
end

function AddonButtons:UpdateGatheringButton()
    if not gatheringButton then
        return
    end
    local available = Addon.Gathering and Addon.Gathering:IsAvailable()
    local visible = available and Addon.Gathering:IsEnabled()
    local red, green, blue = 0.42, 0.82, 0.34
    if not available then
        red, green, blue = 0.32, 0.34, 0.38
    elseif not visible then
        red, green, blue = 0.38, 0.42, 0.48
    end
    gatheringButton.normalBorder = { red, green, blue }
    gatheringButton:SetBackdropBorderColor(red, green, blue, 0.95)
    gatheringButton.icon:SetDesaturated(not visible)
    gatheringButton.icon:SetAlpha(visible and 1 or 0.55)
    gatheringButton.label:SetTextColor(
        available and 1 or 0.55,
        available and 1 or 0.58,
        available and 1 or 0.62
    )
    gatheringButton.check:SetShown(visible)
end

function AddonButtons:UpdateServicesButton()
    if not servicesButton then
        return
    end
    local available = Addon.Gathering and Addon.Gathering:IsAvailable()
    local visible = available and Addon.Gathering:AreNativeServicesShown()
    local red, green, blue = 0.28, 0.68, 1
    if not available then
        red, green, blue = 0.32, 0.34, 0.38
    elseif not visible then
        red, green, blue = 0.38, 0.42, 0.48
    end
    servicesButton.normalBorder = { red, green, blue }
    servicesButton:SetBackdropBorderColor(red, green, blue, 0.95)
    servicesButton.icon:SetDesaturated(not visible)
    servicesButton.icon:SetAlpha(visible and 1 or 0.55)
    servicesButton.label:SetTextColor(
        available and 1 or 0.55,
        available and 1 or 0.58,
        available and 1 or 0.62
    )
    servicesButton.check:SetShown(visible)
end

local function CreatePanel()
    panel = CreateFrame("Frame", "MapaEnMiniaturaAddonPanel", UIParent, "BackdropTemplate")
    panel:SetFrameStrata("DIALOG")
    panel:SetFrameLevel(40)
    panel:SetClampedToScreen(true)
    panel:SetToplevel(true)
    panel:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true,
        tileSize = 16,
        edgeSize = 12,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    panel:SetBackdropColor(0.012, 0.018, 0.026, 0.98)
    panel:SetBackdropBorderColor(0.12, 0.52, 0.78, 1)
    panel:Hide()

    panelTitle = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    panelTitle:SetPoint("TOPLEFT", 10, -8)
    panelTitle:SetText("Addons")

    local closeButton = CreateFrame("Button", nil, panel)
    closeButton:SetSize(18, 18)
    closeButton:SetPoint("TOPRIGHT", -5, -5)
    local closeIcon = closeButton:CreateTexture(nil, "ARTWORK")
    closeIcon:SetAllPoints()
    closeIcon:SetAtlas("uitools-icon-close", false)
    closeButton:SetHighlightTexture(closeIcon, "ADD")
    closeButton:SetScript("OnClick", function()
        panel:Hide()
    end)

    emptyText = panel:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    emptyText:SetPoint("CENTER", 0, -8)
    emptyText:SetText(L.NO_VISIBLE_ADDON_BUTTONS)
end

local function AcquireSlot(index)
    if slots[index] then
        return slots[index]
    end

    local slot = CreateFrame("Frame", nil, panel, "BackdropTemplate")
    slot:SetClipsChildren(true)
    slot:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
    })
    slot:SetBackdropColor(0.025, 0.04, 0.055, 0.98)
    slot:SetBackdropBorderColor(0.14, 0.42, 0.62, 0.9)
    slots[index] = slot
    return slot
end

local function HideUnusedSlots(firstUnused)
    for index = firstUnused, #slots do
        slots[index]:Hide()
    end
end

function AddonButtons:Discover()
    if not enabled then
        return
    end

    local candidates = {}
    local seen = {}
    if LibStub then
        local library = LibStub("LibDBIcon-1.0", true)
        if library and type(library.objects) == "table" then
            for _, button in pairs(library.objects) do
                AddCandidate(candidates, seen, button)
            end
        end
    end
    if Minimap then
        for _, child in ipairs({ Minimap:GetChildren() }) do
            AddCandidate(candidates, seen, child)
        end
    end
    if MinimapCluster then
        for _, child in ipairs({ MinimapCluster:GetChildren() }) do
            AddCandidate(candidates, seen, child)
        end
    end

    for _, button in ipairs(candidates) do
        ManageButton(button)
    end
end

function AddonButtons:Layout()
    if not enabled then
        return
    end

    local buttons = {}
    for button, info in pairs(managed) do
        if button:IsShown() then
            buttons[#buttons + 1] = { button = button, info = info }
        end
    end

    table.sort(buttons, function(left, right)
        return (left.button:GetName() or "") < (right.button:GetName() or "")
    end)

    local count = #buttons
    local iconSize = Addon.db.addonButtonSize
    local gap = Addon.db.addonButtonDistance
    local anchorX = anchorFrame:GetCenter() or (UIParent:GetWidth() / 2)
    local opensLeft = anchorX > (UIParent:GetWidth() / 2)
    local availableWidth = opensLeft and (anchorX - 24) or (UIParent:GetWidth() - anchorX - 24)
    local maxColumnsByWidth = math.max(
        math.floor((availableWidth - (PANEL_PADDING * 2) + gap) / (iconSize + gap)),
        1
    )
    local preferredColumns = math.max(Addon.db.addonHubColumns, 1)
    local columns = math.min(preferredColumns, maxColumnsByWidth, math.max(count, 1))
    local availableHeight = math.max(UIParent:GetHeight() - 50, iconSize + PANEL_HEADER + PANEL_PADDING)
    local rows = math.max(math.ceil(count / columns), 1)
    local calculatedHeight = PANEL_HEADER + PANEL_PADDING + (rows * iconSize) + ((rows - 1) * gap)
    while calculatedHeight > availableHeight
        and columns < math.min(maxColumnsByWidth, math.max(count, 1)) do
        columns = columns + 1
        rows = math.max(math.ceil(count / columns), 1)
        calculatedHeight = PANEL_HEADER + PANEL_PADDING + (rows * iconSize) + ((rows - 1) * gap)
    end
    local width = (PANEL_PADDING * 2) + (columns * iconSize) + ((columns - 1) * gap)
    local height = calculatedHeight
    panel:SetSize(math.max(width, 132), math.max(height, 64))
    emptyText:SetShown(count == 0)
    hubButton.countText:SetText(tostring(count))
    hubButton.countBadge:SetShown(count > 0)
    hubButton.countText:SetShown(count > 0)
    panelTitle:SetText(Addon:Format("ADDONS_COUNT", count))

    for index, entry in ipairs(buttons) do
        local column = (index - 1) % columns
        local row = math.floor((index - 1) / columns)
        local slot = AcquireSlot(index)
        slot:SetSize(iconSize, iconSize)
        slot:ClearAllPoints()
        slot:SetPoint(
            "TOPLEFT",
            panel,
            "TOPLEFT",
            PANEL_PADDING + (column * (iconSize + gap)),
            -(PANEL_HEADER + (row * (iconSize + gap)))
        )
        slot:Show()

        local button = entry.button
        pcall(function()
            UnlockFrameOrder(button)
            button:SetParent(slot)
            button:SetFrameStrata("DIALOG")
            button:SetFrameLevel(panel:GetFrameLevel() + 5)
            StyleButton(button, entry.info, slot, iconSize)
        end)
        entry.info.slot = slot
    end
    HideUnusedSlots(count + 1)

    panel:ClearAllPoints()
    if opensLeft then
        panel:SetPoint("TOPRIGHT", anchorFrame, "TOPLEFT", -6, 0)
    else
        panel:SetPoint("TOPLEFT", anchorFrame, "TOPRIGHT", 6, 0)
    end
end

function AddonButtons:Refresh()
    if enabled then
        if self:UpdateFolioButton(true) then
            UpdateAnchorButtons()
        end
        self:Discover()
        self:Layout()
    end
end

function AddonButtons:RestoreAll()
    for button, info in pairs(managed) do
        if RestoreButton(button, info) then
            if info.slot then
                info.slot:Hide()
            end
            managed[button] = nil
        end
    end
end

function AddonButtons:SetEnabled(value)
    value = not not value
    if value == enabled then
        if value then
            UpdateAnchorButtons()
            self:Refresh()
        end
        return
    end

    enabled = value
    if enabled then
        if AddonCompartmentFrame then
            compartmentWasShown = AddonCompartmentFrame:IsShown()
            AddonCompartmentFrame:Hide()
        end
        anchorFrame:Show()
        UpdateAnchorButtons()
        self:Refresh()
    else
        panel:Hide()
        mapMenu:Hide()
        anchorFrame:Hide()
        self:RestoreAll()
        if AddonCompartmentFrame and compartmentWasShown then
            AddonCompartmentFrame:Show()
        end
        compartmentWasShown = nil
    end
end

function AddonButtons:SetHubShown(shown)
    Addon.db.showAddonButtons = not not shown
    UpdateAnchorButtons()
end

function AddonButtons:GetVisibleCount()
    local count = 0
    for button in pairs(managed) do
        if button:IsShown() then
            count = count + 1
        end
    end
    return count
end

function AddonButtons:ResetAnchor(save)
    Addon.db.addonHubMoved = false
    Addon.db.addonHubX = nil
    Addon.db.addonHubY = nil
    anchorFrame:ClearAllPoints()
    anchorFrame:SetPoint("TOPRIGHT", mapFrame, "TOPLEFT", -7, 0)
    if save ~= false then
        Addon:Print(L.BUTTONS_POSITION_RESET)
    end
end

function AddonButtons:Initialize(ui)
    mapFrame = ui.mapFrame
    CreatePanel()
    CreateMapMenu()
    CreateLaunchers()

    if AddonCompartmentFrame then
        AddonCompartmentFrame:HookScript("OnShow", function(self)
            if enabled then
                self:Hide()
            end
        end)
    end

    if Addon.db.addonHubMoved and Addon.db.addonHubX and Addon.db.addonHubY then
        anchorFrame:ClearAllPoints()
        anchorFrame:SetPoint(
            "CENTER",
            UIParent,
            "BOTTOMLEFT",
            Addon.db.addonHubX,
            Addon.db.addonHubY
        )
    end

    C_Timer.NewTicker(1.5, function()
        if enabled and mapFrame:IsShown() then
            self:Refresh()
        end
    end)
end
