local addonName, addonTable = ...

local MK = addonTable.MK
local TRANSLATE = addonTable.TRANSLATE or function(msg) return msg end

local BUTTON_NAME = "MyKeyStoneMenuBarButton"
local ICON_TEXTURE = "Interface\\AddOns\\" .. addonName .. "\\Art\\Ui\\UIMicroMenu.png"
local ICON_TEXTURE_HEIGHT = 243
local BUTTON_WIDTH = 32
local BUTTON_HEIGHT = 40
local ICON_VISUAL_SCALE = 0.78
local ICON_OFFSET_X = 0.5
local ICON_OFFSET_Y = -1
local FALLBACK_BUTTON_GAP = -5
local PUSHED_ICON_OFFSET_X = 1
local PUSHED_ICON_OFFSET_Y = -1
local REFRESH_RETRY_DELAYS = { 0.05, 0.25, 1, 2 }
local SETUP_RETRY_DELAYS = { 0.25, 0.75, 1.5, 3 }

local ICON_RECTS = {
    disabled = { 0, 60 },
    pushed = { 61, 121 },
    highlight = { 122, 182 },
    normal = { 183, 243 },
}

local RIGHT_NEIGHBOR_NAMES = {
    "ProfessionMicroButton",
    "PlayerSpellsMicroButton",
    "SpellbookMicroButton",
    "TalentMicroButton",
}

local BLOCKING_PANEL_NAMES = {
    "GameMenuFrame",
    "StoreFrame",
    "AccountStoreFrame",
    "ShopFrame",
}

local menuBarButton
local menuBarOwner
local menuBarEventFrame
local menuBarRefreshPending
local menuBarRefreshTicket = 0
local menuBarSetupTicket = 0
local menuBarSetupRetriesScheduled
local queueStatusBasePoints
local queueStatusOffsetApplied
local queueStatusButton
local queueStatusRefreshPending
local blockingPanelHooks = {}
local microMenuFrameHooks = {}
local queueStatusButtonHooks = {}

local function IsInCombat()
    return _G.InCombatLockdown and _G.InCombatLockdown()
end

local function IsFrameShown(frame)
    if not (frame and frame.IsShown) then
        return false
    end

    local ok, shown = pcall(frame.IsShown, frame)
    return ok and shown == true
end

local function SetShown(frame, shown)
    if not frame then
        return
    end
    if frame.SetShown then
        frame:SetShown(shown == true)
    elseif shown then
        frame:Show()
    else
        frame:Hide()
    end
end

local function IsEntryEnabled(owner)
    if owner and owner.IsMicroMenuButtonEnabled then
        return owner:IsMicroMenuButtonEnabled()
    end
    return true
end

local function IsBlockingSystemPanelShown()
    if IsFrameShown(_G.GameMenuFrame) then
        return true
    end
    if IsFrameShown(_G.StoreFrame) or IsFrameShown(_G.AccountStoreFrame) or IsFrameShown(_G.ShopFrame) then
        return true
    end
    if _G.StoreFrame_IsShown then
        local ok, shown = pcall(_G.StoreFrame_IsShown)
        if ok and shown == true then
            return true
        end
    end

    return false
end

local function RefreshMenuBarEntry(owner)
    owner = owner or menuBarOwner
    if not owner then
        return false
    end

    local anchored = false
    if owner.RefreshMicroMenuButtonAnchor then
        anchored = owner:RefreshMicroMenuButtonAnchor()
    end

    if owner.RefreshQueueStatusButtonOffset then
        owner:RefreshQueueStatusButtonOffset()
    end

    return anchored
end

local function QueueMenuBarEntryRefresh(owner)
    owner = owner or menuBarOwner
    if not owner then
        return false
    end

    local anchored = RefreshMenuBarEntry(owner)
    if not (C_Timer and C_Timer.After) then
        return anchored
    end

    menuBarRefreshTicket = menuBarRefreshTicket + 1
    local ticket = menuBarRefreshTicket
    for _, delay in ipairs(REFRESH_RETRY_DELAYS) do
        C_Timer.After(delay, function()
            if ticket ~= menuBarRefreshTicket then
                return
            end
            RefreshMenuBarEntry(owner)
        end)
    end

    return anchored
end

local function TryLoadMicroMenu()
    if _G.CharacterMicroButton then
        return true
    end

    if C_AddOns and C_AddOns.LoadAddOn then
        pcall(C_AddOns.LoadAddOn, "Blizzard_MicroMenu")
    elseif UIParentLoadAddOn then
        pcall(UIParentLoadAddOn, "Blizzard_MicroMenu")
    elseif LoadAddOn then
        pcall(LoadAddOn, "Blizzard_MicroMenu")
    end

    return _G.CharacterMicroButton ~= nil
end

local function InstallBlockingPanelHooks()
    for _, frameName in ipairs(BLOCKING_PANEL_NAMES) do
        local frame = _G[frameName]
        if frame and frame.HookScript and not blockingPanelHooks[frameName] then
            local function refresh()
                QueueMenuBarEntryRefresh(menuBarOwner)
            end
            local showOk = pcall(frame.HookScript, frame, "OnShow", refresh)
            local hideOk = pcall(frame.HookScript, frame, "OnHide", refresh)
            if showOk or hideOk then
                blockingPanelHooks[frameName] = true
            end
        end
    end
end

local function InstallMicroMenuFrameHooks()
    local frameNames = { "CharacterMicroButton" }
    for _, frameName in ipairs(RIGHT_NEIGHBOR_NAMES) do
        frameNames[#frameNames + 1] = frameName
    end

    for _, frameName in ipairs(frameNames) do
        local frame = _G[frameName]
        if frame and frame.HookScript and not microMenuFrameHooks[frameName] then
            local function refresh()
                QueueMenuBarEntryRefresh(menuBarOwner)
            end
            local showOk = pcall(frame.HookScript, frame, "OnShow", refresh)
            local hideOk = pcall(frame.HookScript, frame, "OnHide", refresh)
            local sizeOk = pcall(frame.HookScript, frame, "OnSizeChanged", refresh)
            if showOk or hideOk or sizeOk then
                microMenuFrameHooks[frameName] = true
            end
        end
    end
end

local function InstallQueueStatusButtonHooks(button)
    if not (button and button.HookScript) or queueStatusButtonHooks[button] then
        return
    end

    local function refresh()
        QueueMenuBarEntryRefresh(menuBarOwner)
    end
    local showOk = pcall(button.HookScript, button, "OnShow", refresh)
    local hideOk = pcall(button.HookScript, button, "OnHide", refresh)
    if showOk or hideOk then
        queueStatusButtonHooks[button] = true
    end
end

local function QueueMenuBarSetupRetries(owner)
    if menuBarSetupRetriesScheduled then
        return
    end
    if not (C_Timer and C_Timer.After) then
        return
    end

    menuBarSetupRetriesScheduled = true
    menuBarSetupTicket = menuBarSetupTicket + 1
    local ticket = menuBarSetupTicket
    for _, delay in ipairs(SETUP_RETRY_DELAYS) do
        C_Timer.After(delay, function()
            if ticket ~= menuBarSetupTicket or menuBarButton then
                return
            end
            if owner and owner.SetupMenuBarEntry then
                owner:SetupMenuBarEntry()
            elseif MK and MK.SetupMenuBarEntry then
                MK:SetupMenuBarEntry()
            end
        end)
    end
end

local function ApplyIconRect(texture, rect)
    if not (texture and rect) then
        return
    end

    texture:SetTexture(ICON_TEXTURE)
    texture:SetTexCoord(0, 1, rect[1] / ICON_TEXTURE_HEIGHT, rect[2] / ICON_TEXTURE_HEIGHT)
end

local function GetMicroButtonGap()
    local characterButton = _G.CharacterMicroButton
    if not (characterButton and characterButton.GetRight) then
        return FALLBACK_BUTTON_GAP
    end

    local characterRight = characterButton:GetRight()
    if not characterRight then
        return FALLBACK_BUTTON_GAP
    end

    for _, name in ipairs(RIGHT_NEIGHBOR_NAMES) do
        local neighbor = _G[name]
        if neighbor and neighbor.GetLeft and IsFrameShown(neighbor) then
            local neighborLeft = neighbor:GetLeft()
            if neighborLeft then
                local gap = neighborLeft - characterRight
                if gap >= -20 and gap <= 20 then
                    return gap
                end
            end
        end
    end

    local microMenu = _G.MicroMenu
    if microMenu and tonumber(microMenu.childXPadding) then
        return tonumber(microMenu.childXPadding)
    end

    return FALLBACK_BUTTON_GAP
end

local function GetButtonSizeFromCharacter()
    local characterButton = _G.CharacterMicroButton
    local width = characterButton and characterButton.GetWidth and characterButton:GetWidth() or 0
    local height = characterButton and characterButton.GetHeight and characterButton:GetHeight() or 0

    if not width or width <= 0 then
        width = BUTTON_WIDTH
    end
    if not height or height <= 0 then
        height = BUTTON_HEIGHT
    end

    return width, height
end

local function PositionIcon(button, pushed)
    if not (button and button.Icon) then
        return
    end

    local offsetX = ICON_OFFSET_X + (pushed and PUSHED_ICON_OFFSET_X or 0)
    local offsetY = ICON_OFFSET_Y + (pushed and PUSHED_ICON_OFFSET_Y or 0)
    local width, height = GetButtonSizeFromCharacter()
    width = width * ICON_VISUAL_SCALE
    height = height * ICON_VISUAL_SCALE

    button.Icon:ClearAllPoints()
    button.Icon:SetPoint("CENTER", button, "CENTER", offsetX, offsetY)
    button.Icon:SetSize(width, height)
end

local function CreateAtlasTexture(parent, layer, atlas)
    local texture = parent:CreateTexture(nil, layer)
    texture:SetPoint("CENTER", parent, "CENTER")
    if texture.SetAtlas then
        local ok = pcall(texture.SetAtlas, texture, atlas, true)
        if ok then
            return texture
        end
    end

    texture:SetAllPoints(parent)
    return texture
end

local function CreateMenuBarButton(parent)
    local button = CreateFrame("Button", BUTTON_NAME, parent or UIParent)
    button:SetSize(BUTTON_WIDTH, BUTTON_HEIGHT)
    button:RegisterForClicks("AnyUp")
    button:EnableMouse(true)
    if button.SetMouseClickEnabled then
        button:SetMouseClickEnabled(true)
    end
    if button.SetMouseMotionEnabled then
        button:SetMouseMotionEnabled(true)
    end

    button.Background = CreateAtlasTexture(button, "BACKGROUND", "UI-HUD-MicroMenu-ButtonBG-Up")
    button.PushedBackground = CreateAtlasTexture(button, "BACKGROUND", "UI-HUD-MicroMenu-ButtonBG-Down")
    button.PushedBackground:Hide()

    button.Icon = button:CreateTexture(nil, "ARTWORK")
    ApplyIconRect(button.Icon, ICON_RECTS.normal)
    PositionIcon(button, false)

    return button
end

local function RefreshButtonVisualState(button)
    if not button then
        return
    end

    local owner = menuBarOwner
    local enabled = button:IsEnabled()
    local pushed = enabled and (button.mkMouseDown == true or (owner and owner.IsMainWindowShown and owner:IsMainWindowShown()))
    local hovered = enabled and button.IsMouseOver and button:IsMouseOver()
    local iconState = "normal"

    if not enabled then
        iconState = "disabled"
    elseif pushed then
        iconState = "pushed"
    elseif hovered then
        iconState = "highlight"
    end

    if pushed then
        button.Background:Hide()
        button.PushedBackground:Show()
    else
        button.Background:Show()
        button.PushedBackground:Hide()
    end

    ApplyIconRect(button.Icon, ICON_RECTS[iconState])
    PositionIcon(button, pushed)
    button:SetAlpha(enabled and 1 or 0.5)
end

local function OpenAssistantWindow(owner, mouseButton)
    if not owner then
        return
    end

    if mouseButton == "RightButton" then
        if owner.ToggleMainWindowAtRightClickStartupTab then
            owner:ToggleMainWindowAtRightClickStartupTab()
        elseif owner.OpenMainWindowAtRightClickStartupTab then
            owner:OpenMainWindowAtRightClickStartupTab()
        elseif owner.OpenOptionsTab then
            owner:OpenOptionsTab()
        end
        return
    end

    if owner.ToggleMainWindowAtStartupTab then
        owner:ToggleMainWindowAtStartupTab()
    elseif owner.OpenMainWindowAtStartupTab then
        owner:OpenMainWindowAtStartupTab()
    elseif owner.ShowMainWindow then
        owner:ShowMainWindow(owner.GetStartupTab and owner:GetStartupTab() or nil, true)
    elseif owner.ToggleMainWindow then
        owner:ToggleMainWindow()
    end
end

local function RefreshButtonAvailability(button)
    if not button then
        return
    end

    local shouldEnable = not IsBlockingSystemPanelShown()
    if shouldEnable and not button:IsEnabled() then
        button:Enable()
    elseif not shouldEnable and button:IsEnabled() then
        button:Disable()
    end

    RefreshButtonVisualState(button)
end

local function CaptureFramePoints(frame)
    if not (frame and frame.GetNumPoints and frame.GetPoint) then
        return nil
    end

    local points = {}
    local ok, count = pcall(frame.GetNumPoints, frame)
    if not ok then
        return nil
    end

    for index = 1, count do
        local pointOk, point, relativeTo, relativePoint, xOfs, yOfs = pcall(frame.GetPoint, frame, index)
        if pointOk and point then
            points[#points + 1] = {
                point = point,
                relativeTo = relativeTo,
                relativePoint = relativePoint,
                xOfs = xOfs or 0,
                yOfs = yOfs or 0,
            }
        end
    end

    if #points == 0 then
        return nil
    end

    return points
end

local function ApplyFramePointsWithOffset(frame, points, offsetX)
    if not (frame and frame.ClearAllPoints and frame.SetPoint and points) then
        return false
    end

    local ok = pcall(frame.ClearAllPoints, frame)
    if not ok then
        return false
    end

    local applied = false
    for _, pointData in ipairs(points) do
        local xOfs = (tonumber(pointData.xOfs) or 0) + (tonumber(offsetX) or 0)
        local yOfs = tonumber(pointData.yOfs) or 0
        local pointOk
        if pointData.relativeTo then
            pointOk = pcall(frame.SetPoint, frame, pointData.point, pointData.relativeTo, pointData.relativePoint or pointData.point, xOfs, yOfs)
        else
            pointOk = pcall(frame.SetPoint, frame, pointData.point, UIParent, pointData.relativePoint or pointData.point, xOfs, yOfs)
        end
        applied = applied or pointOk == true
    end

    return applied
end

local function GetQueueStatusButton()
    local bridge = addonTable.MeetingStoneBridge
    if bridge and bridge.GetQueueStatusButton then
        local ok, button = pcall(bridge.GetQueueStatusButton)
        if ok and button then
            return button
        end
    end

    return _G.QueueStatusButton or _G.QueueStatusMinimapButton
end

local function GetQueueStatusOffset()
    local button = menuBarButton
    if not (button and IsFrameShown(button)) then
        return 0
    end

    local width = button:GetWidth() or BUTTON_WIDTH
    local gap = GetMicroButtonGap()
    local slotWidth = math.max(1, width + gap)
    return -slotWidth
end

local function RestoreQueueStatusButtonPosition(queueButton)
    if not queueButton then
        return false
    end

    if queueStatusBasePoints and ApplyFramePointsWithOffset(queueButton, queueStatusBasePoints, 0) then
        queueStatusOffsetApplied = nil
        queueStatusBasePoints = nil
        return true
    end

    queueStatusOffsetApplied = nil
    queueStatusBasePoints = nil
    return false
end

local function AnchorMenuBarButton(owner)
    local button = menuBarButton
    local characterButton = _G.CharacterMicroButton
    if not (button and characterButton) then
        return false
    end
    if IsInCombat() then
        menuBarRefreshPending = true
        return false
    end

    local parent = UIParent
    if button:GetParent() ~= parent then
        button:SetParent(parent)
    end
    if characterButton.GetFrameStrata then
        button:SetFrameStrata(characterButton:GetFrameStrata())
    end
    if characterButton.GetFrameLevel then
        button:SetFrameLevel((characterButton:GetFrameLevel() or 0) + 2)
    end

    local width, height = GetButtonSizeFromCharacter()
    local gap = GetMicroButtonGap()
    button:SetSize(width, height)
    button:ClearAllPoints()
    local characterLeft = characterButton.GetLeft and characterButton:GetLeft()
    local characterBottom = characterButton.GetBottom and characterButton:GetBottom()
    if characterLeft and characterBottom then
        button:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", characterLeft - width - gap, characterBottom)
    else
        button:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 0, 0)
    end
    SetShown(button, IsEntryEnabled(owner) and IsFrameShown(characterButton))
    RefreshButtonAvailability(button)

    if owner.RefreshQueueStatusButtonOffset then
        owner:RefreshQueueStatusButtonOffset()
    end

    return true
end

local function InstallButtonScripts(owner, button)
    button:SetScript("OnEnter", function(menuButton)
        RefreshButtonAvailability(menuButton)
        GameTooltip:SetOwner(menuButton, "ANCHOR_RIGHT")
        GameTooltip:SetText(TRANSLATE("大秘境钥石助手"), 1, 1, 1)
        local leftTooltip = owner.GetStartupClickTooltipLine and owner:GetStartupClickTooltipLine("LeftButton") or TRANSLATE("左键：打开总览")
        local rightTooltip = owner.GetStartupClickTooltipLine and owner:GetStartupClickTooltipLine("RightButton") or TRANSLATE("右键：打开设置")
        GameTooltip:AddLine(leftTooltip, 1, 0.82, 0, true)
        GameTooltip:AddLine(rightTooltip, 1, 0.82, 0, true)
        GameTooltip:Show()
    end)

    button:SetScript("OnLeave", function(menuButton)
        menuButton.mkMouseDown = nil
        GameTooltip:Hide()
        RefreshButtonVisualState(menuButton)
    end)

    button:SetScript("OnMouseDown", function(menuButton, mouseButton)
        if not menuButton:IsEnabled() then
            RefreshButtonVisualState(menuButton)
            return
        end
        if mouseButton == "LeftButton" or mouseButton == "RightButton" then
            menuButton.mkMouseDown = true
            RefreshButtonVisualState(menuButton)
        end
    end)

    button:SetScript("OnMouseUp", function(menuButton)
        menuButton.mkMouseDown = nil
        RefreshButtonAvailability(menuButton)
    end)

    button:SetScript("OnClick", function(menuButton, mouseButton)
        if not menuButton:IsEnabled() then
            RefreshButtonVisualState(menuButton)
            return
        end

        if mouseButton == "RightButton" or mouseButton == "LeftButton" then
            OpenAssistantWindow(owner, mouseButton)
        end

        RefreshButtonVisualState(menuButton)
    end)

    button:SetScript("OnShow", function(menuButton)
        RefreshButtonAvailability(menuButton)
        if owner.RefreshQueueStatusButtonOffset then
            owner:RefreshQueueStatusButtonOffset()
        end
    end)
    button:SetScript("OnHide", function()
        if owner.RefreshQueueStatusButtonOffset then
            owner:RefreshQueueStatusButtonOffset()
        end
    end)
    button:SetScript("OnEnable", RefreshButtonVisualState)
    button:SetScript("OnDisable", RefreshButtonVisualState)
end

function MK:RefreshQueueStatusButtonOffset()
    local currentQueueButton = GetQueueStatusButton()
    if not currentQueueButton then
        queueStatusButton = nil
        queueStatusBasePoints = nil
        queueStatusOffsetApplied = nil
        return false
    end
    InstallQueueStatusButtonHooks(currentQueueButton)
    if not IsFrameShown(currentQueueButton) then
        return RestoreQueueStatusButtonPosition(currentQueueButton)
    end
    if IsInCombat() then
        queueStatusRefreshPending = true
        return false
    end

    if queueStatusButton ~= currentQueueButton then
        queueStatusButton = currentQueueButton
        queueStatusBasePoints = nil
        queueStatusOffsetApplied = nil
    end

    local offsetX = GetQueueStatusOffset()
    if offsetX == 0 or not IsEntryEnabled(self) then
        return RestoreQueueStatusButtonPosition(currentQueueButton)
    end

    if not queueStatusOffsetApplied then
        queueStatusBasePoints = CaptureFramePoints(currentQueueButton)
    end
    local basePoints = queueStatusBasePoints
    if not basePoints then
        return false
    end

    local applied = ApplyFramePointsWithOffset(currentQueueButton, basePoints, offsetX)
    if applied then
        queueStatusOffsetApplied = true
        return true
    end

    return false
end

function MK:RefreshMicroMenuButtonAnchor()
    if not menuBarButton then
        self:SetupMicroMenuButton()
        return false
    end

    local anchored = AnchorMenuBarButton(self)
    if anchored then
        menuBarRefreshPending = nil
        return true
    end

    if IsInCombat() then
        return false
    end

    return false
end

function MK:RefreshMicroMenuButtonState()
    if not menuBarButton then
        return
    end

    QueueMenuBarEntryRefresh(self)
end

function MK:SetupMenuBarEntry()
    rawset(self, "microMenuButton", nil)

    if menuBarButton then
        InstallBlockingPanelHooks()
        InstallMicroMenuFrameHooks()
        QueueMenuBarEntryRefresh(self)
        return
    end

    if not TryLoadMicroMenu() then
        QueueMenuBarSetupRetries(self)
        return
    end
    menuBarSetupTicket = menuBarSetupTicket + 1
    menuBarSetupRetriesScheduled = nil
    InstallBlockingPanelHooks()
    InstallMicroMenuFrameHooks()

    local parent = UIParent
    local button = CreateMenuBarButton(parent)
    InstallButtonScripts(self, button)
    menuBarButton = button
    menuBarOwner = self

    QueueMenuBarEntryRefresh(self)

    local eventFrame = CreateFrame("Frame")
    eventFrame:RegisterEvent("ADDON_LOADED")
    eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
    eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
    eventFrame:RegisterEvent("UPDATE_BINDINGS")
    pcall(eventFrame.RegisterEvent, eventFrame, "EDIT_MODE_LAYOUTS_UPDATED")
    pcall(eventFrame.RegisterEvent, eventFrame, "LFG_UPDATE")
    pcall(eventFrame.RegisterEvent, eventFrame, "LFG_QUEUE_STATUS_UPDATE")
    eventFrame:SetScript("OnEvent", function(_, event, loadedAddonName)
        if event == "ADDON_LOADED"
            and loadedAddonName ~= "Blizzard_MicroMenu"
            and loadedAddonName ~= "Blizzard_QueueStatusFrame"
            and loadedAddonName ~= "Blizzard_StoreUI"
            and loadedAddonName ~= "Blizzard_AccountStore"
            and loadedAddonName ~= "Blizzard_ShopUI"
        then
            return
        end

        if event == "PLAYER_REGEN_ENABLED" then
            menuBarRefreshPending = nil
            queueStatusRefreshPending = nil
        end

        InstallBlockingPanelHooks()
        InstallMicroMenuFrameHooks()
        QueueMenuBarEntryRefresh(self)
    end)
    menuBarEventFrame = eventFrame
end

function MK:SetupMicroMenuButton()
    self:SetupMenuBarEntry()
end
