local _, Addon = ...

local AddonButtons = {}
Addon.AddonButtons = AddonButtons

local mapFrame
local anchorFrame
local hubButton
local settingsButton
local bossButton
local panel
local panelTitle
local emptyText
local enabled = false
local managed = {}
local compartmentWasShown
local slots = {}

local BUTTON_SIZE = 34
local PANEL_PADDING = 10
local PANEL_HEADER = 25
local SLOT_INSET = 3

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,
    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()
    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 UpdateAnchorButtons()
    local showHub = enabled and Addon.db.showAddonButtons
    hubButton:SetShown(showHub)
    bossButton:SetShown(enabled)
    settingsButton:SetShown(enabled)

    bossButton:ClearAllPoints()
    settingsButton:ClearAllPoints()
    if showHub then
        bossButton:SetPoint("TOP", hubButton, "BOTTOM", 0, -4)
    else
        bossButton:SetPoint("TOP", anchorFrame, "TOP", 0, 0)
        panel:Hide()
    end
    settingsButton:SetPoint("TOP", bossButton, "BOTTOM", 0, -4)
    local buttonCount = showHub and 3 or 2
    anchorFrame:SetHeight((BUTTON_SIZE * buttonCount) + (4 * (buttonCount - 1)))
end

local function CreateLaunchers()
    anchorFrame = CreateFrame("Frame", "MapaEnMiniaturaLauncherAnchor", UIParent)
    anchorFrame:SetSize(BUTTON_SIZE, (BUTTON_SIZE * 3) + 8)
    anchorFrame:SetFrameStrata("DIALOG")
    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
    )

    bossButton = CreateLauncherButton(
        "MapaEnMiniaturaBossButton",
        anchorFrame,
        "Interface\\Icons\\INV_Misc_Bone_HumanSkull_01",
        false,
        0.82, 0.28, 0.28
    )

    hubButton:SetScript("OnClick", function()
        if anchorFrame._wasDragged then
            return
        end
        AddonButtons:Refresh()
        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("Botones de addons")
        GameTooltip:AddLine("Clic: abrir el contenedor", 1, 1, 1)
        GameTooltip:AddLine("Arrastrar: mover los botones", 0.65, 0.82, 1)
        GameTooltip:Show()
    end)
    hubButton:SetScript("OnLeave", function(button)
        button:SetBackdropBorderColor(unpack(button.normalBorder))
        GameTooltip_Hide()
    end)

    settingsButton:SetScript("OnClick", function()
        if not anchorFrame._wasDragged then
            Addon.Options:Toggle()
        end
    end)
    settingsButton:SetScript("OnEnter", function(button)
        button:SetBackdropBorderColor(1, 0.85, 0.38, 1)
        GameTooltip:SetOwner(button, "ANCHOR_LEFT")
        GameTooltip:SetText("Configurar Mapa en Miniatura")
        GameTooltip:AddLine("Clic: abrir la interfaz", 1, 1, 1)
        GameTooltip:AddLine("Arrastrar: mover los botones", 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()
        if not anchorFrame._wasDragged then
            Addon.Markers:SetBossesShown(not Addon.db.showBosses)
        end
    end)
    bossButton:SetScript("OnEnter", function(button)
        button:SetBackdropBorderColor(1, 0.45, 0.4, 1)
        GameTooltip:SetOwner(button, "ANCHOR_LEFT")
        GameTooltip:SetText("Jefes de mazmorra")
        GameTooltip:AddLine(Addon.db.showBosses and "Visibles" or "Ocultos", 0.75, 0.85, 1)
        GameTooltip:AddLine("Clic: mostrar u ocultar", 1, 1, 1)
        GameTooltip:AddLine("Arrastrar: mover los botones", 0.65, 0.82, 1)
        GameTooltip:Show()
    end)
    bossButton:SetScript("OnLeave", function(button)
        button:SetBackdropBorderColor(unpack(button.normalBorder))
        GameTooltip_Hide()
    end)

    AddonButtons:UpdateBossButton()

    anchorFrame:SetPoint("TOPRIGHT", mapFrame, "TOPLEFT", -7, 0)
    anchorFrame:Hide()
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)
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("No se encontraron botones visibles.")
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("Addons (" .. 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
        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()
        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("posición de los botones restablecida.")
    end
end

function AddonButtons:Initialize(ui)
    mapFrame = ui.mapFrame
    CreatePanel()
    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
