-- TOG Tools — Shared UI helpers
--
-- Loaded BEFORE MainWindow.lua in every TOC. All helpers here are addon-
-- global so any tab (existing or future) can use them without duplicating
-- the boilerplate.
--
-- Globals exposed:
--   addon.Tooltip.Owner(frame, [budget])      auto-flipping tooltip anchor
--   addon.UI.Brand(text)                      wrap in brand colour
--   addon.UI.AttachTooltip(frame, t, body)    HookScript-based tooltip
--   addon.UI.MakeIcon(parent, opts)           bottom-row icon button factory
--   addon.UI.DetachChildren(host, keys)       release helper for OnClose

local _, addon = ...

-- ---------------------------------------------------------------------------
-- Tooltip — auto-flipping anchor
-- ---------------------------------------------------------------------------
-- Two entry points share the same "flip if frame is near the bottom of
-- the screen" logic via a single internal placement computation:
--
--   addon.Tooltip.Owner(frame, [budget])
--     Use at TOOLTIP ATTACHMENT TIME — calls GameTooltip:SetOwner with
--     ANCHOR_TOPRIGHT or ANCHOR_BOTTOMLEFT. SetOwner CLEARS the tooltip
--     content, so you populate (SetText / AddLine / Show) after.
--
--   addon.Tooltip.AnchorTo(tooltipFrame, ownerFrame, [budget])
--     Use AFTER the tooltip has been populated by SOMEBODY ELSE (e.g.,
--     AceConfigDialog) when you want to re-position without clearing
--     content. Calls ClearAllPoints + SetPoint on the tooltip — content
--     survives. Used by the Settings panel re-anchor hook to make
--     AceConfigDialog tooltips obey our flip rules while OUR panel is
--     active.
--
-- Both call `_computePlacement` so the flip threshold (250 px below
-- the owner frame's top edge) stays consistent between them.
addon.Tooltip = addon.Tooltip or {}

-- Room-below check used by both Owner and AnchorTo. Uses the frame's
-- BOTTOM edge against the screen origin (Y=0 at the bottom of the
-- screen in WoW's coordinate system) — earlier versions used
-- `screenH - frame:GetTop()` which measures distance from the frame
-- to the TOP of the screen (the wrong direction) and flipped the
-- tooltip's anchor opposite of what the user expected.
local function _computePlacement(frame, budget)
    local bottom = frame and frame.GetBottom and frame:GetBottom()
    budget = budget or 250
    return bottom and bottom > budget
end

-- Narrow-owner detection. Owners thinner than this (checkboxes ~22 px,
-- icon buttons 20-24 px) get the tooltip offset rightward by the
-- owner's width + a small gap so the tooltip's left edge aligns with
-- where the LABEL text starts — not the icon's left edge. For wider
-- owners (header buttons, dropdowns, etc.) the offset is 0 and the
-- tooltip drops straight down aligned with the owner.
local NARROW_OWNER_THRESHOLD = 40

local function _xOffsetForLabel(ownerFrame)
    local width = ownerFrame and ownerFrame.GetWidth and ownerFrame:GetWidth() or 0
    if width > 0 and width < NARROW_OWNER_THRESHOLD then
        return width + 8   -- small gap past the icon/checkbox
    end
    return 0
end

function addon.Tooltip.Owner(frame, budget)
    local anchor = _computePlacement(frame, budget)
        and "ANCHOR_TOPRIGHT"
        or  "ANCHOR_BOTTOMLEFT"
    GameTooltip:SetOwner(frame, anchor)
end

function addon.Tooltip.AnchorTo(tooltipFrame, ownerFrame, budget)
    if not tooltipFrame or not ownerFrame then return end
    tooltipFrame:ClearAllPoints()
    local xOff = _xOffsetForLabel(ownerFrame)
    if _computePlacement(ownerFrame, budget) then
        -- Plenty of room below the owner → drop tooltip down, top-left
        -- corner of tooltip aligned to bottom-left corner of owner
        -- (with optional offset right so narrow owners' tooltips don't
        -- start under the icon).
        tooltipFrame:SetPoint("TOPLEFT", ownerFrame, "BOTTOMLEFT", xOff, -4)
    else
        -- Near bottom of screen → flip up so the tooltip doesn't clip.
        tooltipFrame:SetPoint("BOTTOMLEFT", ownerFrame, "TOPLEFT", xOff, 4)
    end
end

-- (An always-on global `hooksecurefunc(GameTooltip, "SetOwner", ...)`
-- lived here briefly in v0.5.2. It re-anchored every tooltip in the
-- entire UI — including Blizzard's native corpse / unit / item
-- tooltips — and made them visually wonky. User reverted; the scoped
-- version lives in `GUI/Settings.lua` instead, gated by the
-- `_blizPanel:OnShow/OnHide` flag so the re-anchor only runs while
-- the TOG Tools settings panel is the active page.)

-- ---------------------------------------------------------------------------
-- UI helpers
-- ---------------------------------------------------------------------------
addon.UI = addon.UI or {}
local UI = addon.UI

--- Wrap `text` in the addon brand colour. Safe to embed in fontstrings,
--- tooltips, and chat output.
function UI.Brand(text)
    local c = addon.BrandColor or "ffFF8000"
    return "|c" .. c .. tostring(text or "") .. "|r"
end

--- Canonical tooltip-attach helper. Accepts either an AceGUI widget or
--- a raw `CreateFrame` frame and picks the correct binding mechanism
--- automatically:
---
---   * AceGUI widget (table with a `SetCallback` method): uses
---     `widget:SetCallback("OnEnter"/"OnLeave", ...)`. AceGUI clears
---     widget callbacks on Release, so the handler is properly
---     released when the widget returns to the pool. For Dropdown /
---     EditBox the label area sits outside the interactive body and
---     never fires the SetCallback OnEnter — for those types we also
---     wire `widget.frame` via `addon.AceGUIFrameScripts` (defined in
---     Compat.lua), which saves the prior script and restores it on
---     Release so the recycled widget's next owner gets a clean frame.
---
---   * Raw frame (no SetCallback): uses `HookScript` because raw
---     `CreateFrame` frames aren't pool-shared, so the never-unhookable
---     HookScript chain is safe — the handler lives as long as the
---     frame does.
---
--- |cffff5555Never call this with `widget.frame` for an AceGUI widget|r —
--- the AceGUI widget pool will recycle that frame into AceConfigDialog
--- option toggles (and any other addon that uses AceGUI), where the
--- residual HookScript fires alongside the new owner's handlers and
--- renders tooltip content twice. This was the cause of the v0.5.1
--- Settings>Modules tooltip doubling. Pass the WIDGET, not the frame.
---
--- For back-compat, `addon.GUI.AttachTooltip` is aliased to this same
--- function at the bottom of this file, so callers using either
--- namespace get identical behaviour.
function UI.AttachTooltip(target, title, body)
    if not target then return end

    local function show(anchorFrame)
        addon.Tooltip.Owner(anchorFrame)
        if title and title ~= "" then GameTooltip:SetText(title, 1, 1, 1) end
        if body  and body  ~= "" then GameTooltip:AddLine(body, nil, nil, nil, true) end
        GameTooltip:Show()
    end
    local function hide() GameTooltip:Hide() end

    -- AceGUI widget: tables with a SetCallback method. AceGUI clears
    -- callbacks on Release, so this is the leak-safe path.
    if type(target) == "table" and type(target.SetCallback) == "function" then
        target:SetCallback("OnEnter", function(w) show(w.frame) end)
        target:SetCallback("OnLeave", hide)

        -- Dropdown / EditBox label-area fallback. The label text sits
        -- outside the interactive body and SetCallback OnEnter doesn't
        -- fire for hovers on the label. Wire the wrapper frame via
        -- AceGUIFrameScripts, which records the prior script and
        -- restores it on Release — leaving the pooled widget clean.
        if (target.type == "Dropdown" or target.type == "EditBox")
           and target.frame and addon.AceGUIFrameScripts then
            if target.frame.EnableMouse then
                target.frame:EnableMouse(true)
            end
            addon.AceGUIFrameScripts(target, {
                OnEnter = function(f) show(f) end,
                OnLeave = hide,
            })
        end
        return
    end

    -- Raw CreateFrame frame. HookScript is fine here because the frame
    -- isn't pool-shared.
    if target.HookScript then
        target:HookScript("OnEnter", function(self_) show(self_) end)
        target:HookScript("OnLeave", hide)
    end
end

--- Build a bottom-row icon button (gear, help-i, future icons).
---
--- opts:
---   size           number  button size (default 20)
---   texture        string  normal/pushed texture path (required)
---   texCoordCrop   bool    apply 0.08-0.92 crop. Interface\Icons\* have
---                          ~8% transparent border padding so the visible
---                          icon doesn't fill the button; crop fixes that.
---                          Default false (Interface\Common\* etc. don't
---                          need it).
---   hitRectInsets  number  hit-rect expansion in px (negative widens);
---                          default -2 — gives 2 px slop on every side
---                          inside the row's standard 3 px gap.
---   onClick        func    optional click handler.
---   onEnter        func    optional OnEnter; overrides tooltipTitle/Body.
---   onLeave        func    optional OnLeave; defaults to GameTooltip:Hide.
---   tooltipTitle   string  if set (and no onEnter), installs an auto
---   tooltipBody    string  tooltip via addon.Tooltip.Owner.
---
--- Returns the Button frame. Caller is responsible for SetPoint and for
--- storing the frame somewhere DetachChildren can find it on close.
function UI.MakeIcon(parent, opts)
    opts = opts or {}
    local size = opts.size or 20
    local btn = CreateFrame("Button", nil, parent)
    btn:SetSize(size, size)
    btn:EnableMouse(true)
    if opts.texture then
        btn:SetNormalTexture(opts.texture)
        btn:SetPushedTexture(opts.texture)
    end
    if opts.texCoordCrop then
        local n = btn:GetNormalTexture()
        if n then n:SetTexCoord(0.08, 0.92, 0.08, 0.92) end
        local p = btn:GetPushedTexture()
        if p then
            p:SetTexCoord(0.08, 0.92, 0.08, 0.92)
            p:SetVertexColor(0.7, 0.7, 0.7)
        end
    end
    btn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD")
    local slop = opts.hitRectInsets or -2
    btn:SetHitRectInsets(slop, slop, slop, slop)

    if opts.onEnter then
        btn:SetScript("OnEnter", opts.onEnter)
        btn:SetScript("OnLeave", opts.onLeave or function() GameTooltip:Hide() end)
    elseif opts.tooltipTitle or opts.tooltipBody then
        btn:SetScript("OnEnter", function(self_)
            addon.Tooltip.Owner(self_)
            if opts.tooltipTitle then
                GameTooltip:SetText(opts.tooltipTitle, 1, 1, 1)
            end
            if opts.tooltipBody then
                GameTooltip:AddLine(opts.tooltipBody, nil, nil, nil, true)
            end
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
    end

    if opts.onClick then btn:SetScript("OnClick", opts.onClick) end

    return btn
end

--- Detach child frames that were manually attached to a pooled AceGUI
--- widget's underlying WoW frame. Without this, the next AceGUI:Create
--- of the same widget type returns the same pooled instance with our
--- frames still parented — they stack on top of each other across
--- Open/Close cycles, and can leak into other addons' UIs since the
--- AceGUI pool is global.
---
--- `host` is the table holding the references (typically MainWindow self
--- or a tab module's self). `keys` is a list of self-keys to detach.
--- Hides each frame, reparents to UIParent, clears its points, and nils
--- the reference so a fresh icon can take its place on next Open.
function UI.DetachChildren(host, keys)
    if not (host and keys) then return end
    for _, k in ipairs(keys) do
        local frame = host[k]
        if frame then
            if frame.Hide          then frame:Hide()          end
            if frame.SetParent     then frame:SetParent(UIParent) end
            if frame.ClearAllPoints then frame:ClearAllPoints() end
            host[k] = nil
        end
    end
end

-- ---------------------------------------------------------------------------
-- Cross-namespace alias.
--
-- Compat.lua used to expose a separate `addon.GUI.AttachTooltip` for
-- AceGUI widgets. Now there's a single canonical implementation in
-- `addon.UI.AttachTooltip` that handles both AceGUI widgets and raw
-- frames — point the GUI namespace alias at it so existing call sites
-- in either namespace get the same leak-safe behaviour. UI.lua loads
-- after Compat.lua per the TOC ordering, so this override is final.
-- ---------------------------------------------------------------------------
addon.GUI = addon.GUI or {}
addon.GUI.AttachTooltip = UI.AttachTooltip
