--## Author: minidice ## Version: 1.0.0 ## SavedVariables: MiniSimpleBindOverlayDB
local MSBO = CreateFrame("Frame", "MiniSimpleBindOverlayFrame")
MiniSimpleBindOverlay = MSBO

local locale = GetLocale()
local LABEL_BOE = locale == "zhCN" and "装绑" or "BOE"
local LABEL_WUE = locale == "zhCN" and "战团" or "WuE"
local LABEL_CLOSE = locale == "zhCN" and "关闭" or "Close"

local BOE_COLOR = { r = 1.0, g = 0.95, b = 0.15 }
local WUE_COLOR = { r = 0.0, g = 0.78, b = 1.0 }

local hookedContainerFrames = {}
local updateQueued = false
local optionFrame
local positionButtons = {}

local atan2 = math.atan2 or atan2 or function(y, x)
    if x > 0 then
        return math.atan(y / x)
    elseif x < 0 and y >= 0 then
        return math.atan(y / x) + math.pi
    elseif x < 0 then
        return math.atan(y / x) - math.pi
    elseif y > 0 then
        return math.pi / 2
    elseif y < 0 then
        return -math.pi / 2
    end

    return 0
end

local defaults = {
    minimap = { angle = 225 },
    point = "TOPRIGHT",
}

local positionPresets = {
    TOPLEFT = { point = "TOPLEFT", relativePoint = "TOPLEFT", x = 1, y = -1, justifyH = "LEFT", justifyV = "TOP" },
    TOP = { point = "TOP", relativePoint = "TOP", x = 0, y = -1, justifyH = "CENTER", justifyV = "TOP" },
    TOPRIGHT = { point = "TOPRIGHT", relativePoint = "TOPRIGHT", x = -1, y = -1, justifyH = "RIGHT", justifyV = "TOP" },
    LEFT = { point = "LEFT", relativePoint = "LEFT", x = 1, y = 0, justifyH = "LEFT", justifyV = "MIDDLE" },
    RIGHT = { point = "RIGHT", relativePoint = "RIGHT", x = -1, y = 0, justifyH = "RIGHT", justifyV = "MIDDLE" },
    BOTTOMLEFT = { point = "BOTTOMLEFT", relativePoint = "BOTTOMLEFT", x = 1, y = 1, justifyH = "LEFT", justifyV = "BOTTOM" },
    BOTTOM = { point = "BOTTOM", relativePoint = "BOTTOM", x = 0, y = 1, justifyH = "CENTER", justifyV = "BOTTOM" },
    BOTTOMRIGHT = { point = "BOTTOMRIGHT", relativePoint = "BOTTOMRIGHT", x = -1, y = 1, justifyH = "RIGHT", justifyV = "BOTTOM" },
}

local function GetDB()
    if type(MiniSimpleBindOverlayDB) ~= "table" then
        MiniSimpleBindOverlayDB = {}
    end

    if type(MiniSimpleBindOverlayDB.minimap) ~= "table" then
        MiniSimpleBindOverlayDB.minimap = {}
    end

    MiniSimpleBindOverlayDB.minimap.angle = MiniSimpleBindOverlayDB.minimap.angle or defaults.minimap.angle
    MiniSimpleBindOverlayDB.point = positionPresets[MiniSimpleBindOverlayDB.point] and MiniSimpleBindOverlayDB.point or defaults.point
    return MiniSimpleBindOverlayDB
end

local function IsItemBindingLine(line)
    local lineType = Enum and Enum.TooltipDataLineType and Enum.TooltipDataLineType.ItemBinding
    return line and (line.type == lineType or line.bonding ~= nil)
end

local function BindingKindFromTooltipData(data)
    if not data or not data.lines then
        return nil
    end

    local bindings = Enum and Enum.TooltipDataItemBinding
    local bindOnEquip = bindings and bindings.BindOnEquip or 7
    local accountUntilEquipped = bindings and bindings.AccountUntilEquipped or 9
    local bindToAccountUntilEquipped = bindings and bindings.BindToAccountUntilEquipped or 10

    for _, line in ipairs(data.lines) do
        if IsItemBindingLine(line) then
            if line.bonding == bindOnEquip then
                return "boe"
            elseif line.bonding == accountUntilEquipped or line.bonding == bindToAccountUntilEquipped then
                return "wue"
            end
        end
    end

    return nil
end

local function GetBindingKindForBagSlot(bagID, slotID)
    if bagID == nil or slotID == nil or not C_TooltipInfo or not C_TooltipInfo.GetBagItem then
        return nil
    end

    local ok, data = pcall(C_TooltipInfo.GetBagItem, bagID, slotID)
    if ok then
        return BindingKindFromTooltipData(data)
    end

    return nil
end

local function GetButtonBagSlot(button)
    if not button then
        return nil, nil
    end

    local bagID = button.GetBagID and button:GetBagID() or button.bagID
    local slotID = button.GetSlotID and button:GetSlotID() or button.GetID and button:GetID() or button.slotID

    if bagID == nil and button:GetParent() and button:GetParent().GetID then
        bagID = button:GetParent():GetID()
    end

    return bagID, slotID
end

local function ButtonHasItem(button)
    if not button then
        return false
    end

    if button.hasItem ~= nil then
        return button.hasItem
    end

    local bagID, slotID = GetButtonBagSlot(button)
    if bagID ~= nil and slotID ~= nil and C_Container and C_Container.GetContainerItemInfo then
        return C_Container.GetContainerItemInfo(bagID, slotID) ~= nil
    end

    return false
end

local function GetButtonIconRegion(button)
    if not button then
        return nil
    end

    if button.icon then
        return button.icon
    elseif button.Icon then
        return button.Icon
    elseif button.IconTexture then
        return button.IconTexture
    elseif button.texture then
        return button.texture
    elseif button.itemTexture then
        return button.itemTexture
    end

    local name = button.GetName and button:GetName()
    if name then
        return _G[name .. "IconTexture"] or _G[name .. "Icon"]
    end

    return nil
end

local function EnsureOverlay(button)
    if button.MSBOText then
        return button.MSBOText
    end

    local text = button:CreateFontString(nil, "OVERLAY", "NumberFontNormalSmall")
    text:SetShadowOffset(1, -1)
    text:SetShadowColor(0, 0, 0, 1)
    text:SetWordWrap(false)
    text:SetNonSpaceWrap(false)
    button.MSBOText = text
    return text
end

local function ClearButton(button)
    if button and button.MSBOText then
        button.MSBOText:SetText("")
        button.MSBOText:Hide()
    end
end

local function ResetOverlayText(button)
    if button and button.MSBOText then
        button.MSBOText:SetText("")
        button.MSBOText:Hide()
        button.MSBOText:ClearAllPoints()
        button.MSBOText = nil
    end
end

local function SetOverlay(button, kind)
    local text = EnsureOverlay(button)

    if not kind then
        text:SetText("")
        text:Hide()
        return
    end

    local anchor = GetButtonIconRegion(button) or button
    local preset = positionPresets[GetDB().point] or positionPresets.TOPLEFT
    local width = math.max(26, anchor.GetWidth and anchor:GetWidth() or button:GetWidth() or 36)
    local color = kind == "wue" and WUE_COLOR or BOE_COLOR

    text:SetDrawLayer("OVERLAY", 7)
    text:ClearAllPoints()
    text:SetSize(width, 14)
    text:SetPoint(preset.point, anchor, preset.relativePoint, preset.x, preset.y)
    text:SetJustifyH(preset.justifyH)
    text:SetJustifyV(preset.justifyV)
    text:SetFont(STANDARD_TEXT_FONT, 11, "OUTLINE")
    text:SetTextColor(color.r, color.g, color.b, 1)
    text:SetText(kind == "wue" and LABEL_WUE or LABEL_BOE)
    text:Show()
end

local function UpdateButton(button)
    if not button or not button.CreateFontString then
        return
    end

    if not ButtonHasItem(button) then
        ClearButton(button)
        return
    end

    local bagID, slotID = GetButtonBagSlot(button)
    SetOverlay(button, GetBindingKindForBagSlot(bagID, slotID))
end

local function UpdateContainerFrame(frame)
    if not frame or not frame:IsShown() then
        return
    end

    if frame.EnumerateValidItems then
        for _, button in frame:EnumerateValidItems() do
            UpdateButton(button)
        end
    elseif frame.Items then
        for _, button in ipairs(frame.Items) do
            UpdateButton(button)
        end
    end
end

local function HookContainerFrame(frame)
    if not frame or hookedContainerFrames[frame] then
        return
    end

    if not frame.EnumerateValidItems and not frame.Items then
        return
    end

    hookedContainerFrames[frame] = true

    if frame.Update then
        hooksecurefunc(frame, "Update", UpdateContainerFrame)
    end

    frame:HookScript("OnShow", function(shownFrame)
        C_Timer.After(0, function()
            UpdateContainerFrame(shownFrame)
        end)
        C_Timer.After(0.15, function()
            UpdateContainerFrame(shownFrame)
        end)
    end)
end

local function HookContainerFrames()
    for frameIndex = 1, NUM_CONTAINER_FRAMES or 13 do
        HookContainerFrame(_G["ContainerFrame" .. frameIndex])
    end

    HookContainerFrame(_G.ContainerFrameCombinedBags)
end

local function UpdateVisibleContainerButtons()
    HookContainerFrames()

    for frameIndex = 1, NUM_CONTAINER_FRAMES or 13 do
        UpdateContainerFrame(_G["ContainerFrame" .. frameIndex])
    end

    UpdateContainerFrame(_G.ContainerFrameCombinedBags)
end

local function QueueUpdate(delay)
    if type(delay) ~= "number" then
        delay = 0.05
    end

    if updateQueued then
        return
    end

    updateQueued = true
    C_Timer.After(delay, function()
        updateQueued = false
        UpdateVisibleContainerButtons()
    end)
end

local function HookIfExists(name, func)
    if _G[name] then
        hooksecurefunc(name, func)
    end
end

local function SetOptionPosition(point)
    GetDB().point = point
    for frameIndex = 1, NUM_CONTAINER_FRAMES or 13 do
        local frame = _G["ContainerFrame" .. frameIndex]
        if frame and frame:IsShown() then
            if frame.EnumerateValidItems then
                for _, button in frame:EnumerateValidItems() do
                    ResetOverlayText(button)
                end
            elseif frame.Items then
                for _, button in ipairs(frame.Items) do
                    ResetOverlayText(button)
                end
            end
        end
    end

    local combined = _G.ContainerFrameCombinedBags
    if combined and combined:IsShown() then
        if combined.EnumerateValidItems then
            for _, button in combined:EnumerateValidItems() do
                ResetOverlayText(button)
            end
        elseif combined.Items then
            for _, button in ipairs(combined.Items) do
                ResetOverlayText(button)
            end
        end
    end

    for key, button in pairs(positionButtons) do
        if button.MSBOFill then
            if key == point then
                button.MSBOFill:SetColorTexture(0.95, 0.68, 0.10, 0.78)
            else
                button.MSBOFill:SetColorTexture(0.04, 0.04, 0.04, 0.58)
            end
        end
    end
end

local function CreatePositionButton(parent, point, x, y)
    local button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
    button:SetSize(26, 24)
    button:SetPoint("TOPLEFT", x, y)

    local fill = button:CreateTexture(nil, "ARTWORK")
    fill:SetPoint("TOPLEFT", 5, -5)
    fill:SetPoint("BOTTOMRIGHT", -5, 5)
    fill:SetTexture("Interface\\BUTTONS\\WHITE8X8")
    fill:SetColorTexture(0.04, 0.04, 0.04, 0.58)
    button.MSBOFill = fill

    button:SetScript("OnClick", function()
        SetOptionPosition(point)
    end)

    positionButtons[point] = button
    return button
end

local function CreateOptionsFrame()
    if optionFrame then
        return optionFrame
    end

    local frame = CreateFrame("Frame", "MiniSimpleBindOverlayOptions", UIParent, "BackdropTemplate")
    frame:SetSize(104, 110)
    frame:SetPoint("CENTER")
    frame:SetFrameStrata("DIALOG")
    frame:SetMovable(true)
    frame:EnableMouse(true)
    frame:RegisterForDrag("LeftButton")
    frame:SetScript("OnDragStart", frame.StartMoving)
    frame:SetScript("OnDragStop", frame.StopMovingOrSizing)
    frame:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 2,
    })
    frame:SetBackdropColor(0.07, 0.075, 0.085, 0.88)
    frame:SetBackdropBorderColor(0.28, 0.27, 0.25, 1)
    frame:Hide()

    CreatePositionButton(frame, "TOPLEFT", 2, -4)
    CreatePositionButton(frame, "TOP", 39, -4)
    CreatePositionButton(frame, "TOPRIGHT", 76, -4)
    CreatePositionButton(frame, "LEFT", 2, -33)
    CreatePositionButton(frame, "RIGHT", 76, -33)
    CreatePositionButton(frame, "BOTTOMLEFT", 2, -62)
    CreatePositionButton(frame, "BOTTOM", 39, -62)
    CreatePositionButton(frame, "BOTTOMRIGHT", 76, -62)

    local close = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    close:SetSize(100, 22)
    close:SetPoint("BOTTOM", 0, 0)
    close:SetText(LABEL_CLOSE)
    close:SetScript("OnClick", function()
        frame:Hide()
    end)

    frame:SetScript("OnShow", function()
        SetOptionPosition(GetDB().point)
    end)

    optionFrame = frame
    return frame
end

function MSBO:ToggleOptions()
    local frame = CreateOptionsFrame()
    frame:SetShown(not frame:IsShown())
end

local function CreateMinimapButton()
    local db = GetDB()
    if MSBO.minimapButton then
        return
    end

    local button = CreateFrame("Button", "MiniSimpleBindOverlayMinimapButton", Minimap)
    button:SetSize(31, 31)
    button:SetFrameStrata("MEDIUM")
    button:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")

    local overlay = button:CreateTexture(nil, "OVERLAY")
    overlay:SetSize(53, 53)
    overlay:SetPoint("TOPLEFT")
    overlay:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")

    local icon = button:CreateTexture(nil, "BACKGROUND")
    icon:SetSize(20, 20)
    icon:SetPoint("CENTER", 0, 1)
    icon:SetTexture("Interface\\Icons\\INV_Misc_Bag_10")

    local function UpdatePosition()
        local angle = math.rad(db.minimap.angle or defaults.minimap.angle)
        button:SetPoint("CENTER", Minimap, "CENTER", math.cos(angle) * 80, math.sin(angle) * 80)
    end

    button:SetScript("OnClick", function()
        MSBO:ToggleOptions()
    end)
    button:SetScript("OnMouseDown", function(self)
        self:SetScript("OnUpdate", function()
            local mx, my = Minimap:GetCenter()
            local px, py = GetCursorPosition()
            local scale = UIParent:GetEffectiveScale()
            px, py = px / scale, py / scale
            db.minimap.angle = math.deg(atan2(py - my, px - mx))
            UpdatePosition()
        end)
    end)
    button:SetScript("OnMouseUp", function(self)
        self:SetScript("OnUpdate", nil)
    end)

    MSBO.minimapButton = button
    UpdatePosition()
end

local function RegisterSlashCommands()
    SLASH_MINISIMPLEBINDOVERLAY1 = "/msbo"
    SlashCmdList.MINISIMPLEBINDOVERLAY = function()
        MSBO:ToggleOptions()
    end
end

MSBO:SetScript("OnEvent", function(_, event, addonName)
    if C_AddOns.IsAddOnLoaded("Baganator") then return end
    if event == "ADDON_LOADED" and addonName == "OrzUI" then
        GetDB()
        --CreateMinimapButton()
        RegisterSlashCommands()
        MSBO:RegisterEvent("PLAYER_LOGIN")
        QueueUpdate()
    elseif event == "PLAYER_LOGIN" then
        HookContainerFrames()
        QueueUpdate()
        C_Timer.After(0.5, UpdateVisibleContainerButtons)
    elseif event ~= "ADDON_LOADED" then
        QueueUpdate()
    end
end)

MSBO:RegisterEvent("ADDON_LOADED")
MSBO:RegisterEvent("BAG_UPDATE_DELAYED")
MSBO:RegisterEvent("TOOLTIP_DATA_UPDATE")

HookIfExists("ContainerFrameItemButton_Update", UpdateButton)
HookIfExists("ContainerFrame_Update", QueueUpdate)
HookIfExists("ContainerFrame_UpdateAll", QueueUpdate)

HookIfExists("OpenAllBags", function()
    QueueUpdate()
    C_Timer.After(0.3, UpdateVisibleContainerButtons)
end)

HookIfExists("ToggleAllBags", function()
    QueueUpdate()
    C_Timer.After(0.3, UpdateVisibleContainerButtons)
end)

HookIfExists("OpenBag", function()
    QueueUpdate()
    C_Timer.After(0.3, UpdateVisibleContainerButtons)
end)

HookIfExists("ToggleBag", function()
    QueueUpdate()
    C_Timer.After(0.3, UpdateVisibleContainerButtons)
end)

HookIfExists("OpenBackpack", function()
    QueueUpdate()
    C_Timer.After(0.3, UpdateVisibleContainerButtons)
end)

HookIfExists("ToggleBackpack", function()
    QueueUpdate()
    C_Timer.After(0.3, UpdateVisibleContainerButtons)
end)
