local addonName, ns = ...

-- ============================================================================
-- BrightQoL – Minimap Module
-- Native minimap customisation: shape, size, border, clock, coords, zone text,
-- button visibility, drag-to-move, auto-zoom, mouse-wheel zoom.
-- ============================================================================

local MinimapMod = {}
ns.MinimapMod = MinimapMod

local function DB() return BrightQoL.minimap end

---------------------------------------------------------------------------
-- Helpers
---------------------------------------------------------------------------
local BRIGHT_FONT = "Interface\\AddOns\\BrightQoL\\Assets\\Fonts\\Bright.ttf"
local W8 = "Interface\\Buttons\\WHITE8X8"
local CIRCLE_MASK = "Interface\\MiniMap\\UI-Minimap-Background"  -- round mask shipped with WoW

local function GetAccentColor()
    if ns.Theme then return ns.Theme.GetAccent() end
    return 0, 207/255, 1.0
end

local function GetClassColor()
    if ns.GetClassColor then return ns.GetClassColor() end
    return 0, 207/255, 1.0
end

local function GetBorderColor()
    local db = DB()
    if db.useClassBorder then return GetClassColor() end
    return db.borderColorR or 0, db.borderColorG or 0, db.borderColorB or 0
end

---------------------------------------------------------------------------
-- State
---------------------------------------------------------------------------
local initialized  = false
local backdropFrame = nil   -- sits behind Minimap as the coloured border
local clockText     = nil   -- our own clock FontString
local coordText     = nil   -- coordinates FontString
local zoneText      = nil   -- zone name FontString
local coordTimer    = nil   -- C_Timer ticker
local clockTimer    = nil   -- C_Timer ticker
local zoomTimer     = nil   -- auto-zoom-out timer id (counter)
local zoomStart, zoomCurrent = 0, 0

---------------------------------------------------------------------------
-- Shape / Size / Border
---------------------------------------------------------------------------
local function ApplyShape()
    local db = DB()
    local shape = db.shape or "SQUARE"
    if shape == "SQUARE" then
        Minimap:SetMaskTexture(W8)
        if HybridMinimap then
            HybridMinimap.MapCanvas:SetUseMaskTexture(false)
            HybridMinimap.CircleMask:SetTexture(W8)
            HybridMinimap.MapCanvas:SetUseMaskTexture(true)
        end
    else
        -- Round: use WoW's built-in minimap circle texture
        Minimap:SetMaskTexture("Interface\\AddOns\\BrightQoL\\Assets\\Minimap\\circle")
        if HybridMinimap then
            HybridMinimap.MapCanvas:SetUseMaskTexture(false)
            HybridMinimap.CircleMask:SetTexture("Interface\\AddOns\\BrightQoL\\Assets\\Minimap\\circle")
            HybridMinimap.MapCanvas:SetUseMaskTexture(true)
        end
    end
    -- Suppress quest/arch blob rings (ugly with square minimap)
    if Minimap.SetArchBlobRingScalar then
        Minimap:SetArchBlobRingScalar(0)
        Minimap:SetArchBlobRingAlpha(0)
        Minimap:SetQuestBlobRingScalar(0)
        Minimap:SetQuestBlobRingAlpha(0)
    end
end

local function ApplySize()
    local db = DB()
    local size = db.size or 200
    Minimap:SetSize(size, size)
    -- Prevent Blizzard's MinimapMixin:Layout() from resetting our changes.
    -- PLAYER_ENTERING_WORLD and UPDATE_PENDING_MAIL both trigger Layout() which
    -- calls methods we've stubbed out. Nullifying it prevents those resets.
    Minimap.Layout = function() end
    -- Trigger a zoom cycle so the render layer updates to the new size
    if Minimap.ZoomIn and Minimap.ZoomOut then
        if Minimap:GetZoom() ~= 5 then
            Minimap.ZoomIn:Click()
            Minimap.ZoomOut:Click()
        else
            Minimap.ZoomOut:Click()
            Minimap.ZoomIn:Click()
        end
    end
    if db.scale and db.scale ~= 1.0 then
        Minimap:SetScale(db.scale)
    else
        Minimap:SetScale(1.0)
    end
end

local function ApplyBorder()
    local db = DB()
    local size = db.size or 200
    local bsize = db.borderSize or 5
    local full = size + bsize * 2
    if backdropFrame then
        backdropFrame:SetSize(full, full)
        local r, g, b = GetBorderColor()
        local a = db.borderColorA or 1
        backdropFrame:GetRegions():SetColorTexture(r, g, b, a)
    end
end

---------------------------------------------------------------------------
-- Backdrop frame (coloured border behind Minimap)
---------------------------------------------------------------------------
local function BuildBackdrop()
    if backdropFrame then return end
    local db = DB()
    local size = db.size or 200
    local bsize = db.borderSize or 5
    backdropFrame = CreateFrame("Frame")
    backdropFrame:SetParent(Minimap)
    backdropFrame:SetFrameStrata("BACKGROUND")
    backdropFrame:SetFrameLevel(1)
    backdropFrame:SetFixedFrameStrata(true)
    backdropFrame:SetFixedFrameLevel(true)
    backdropFrame:SetSize(size + bsize * 2, size + bsize * 2)
    backdropFrame:SetPoint("CENTER", Minimap, "CENTER")
    backdropFrame:Show()
    local tex = backdropFrame:CreateTexture(nil, "BACKGROUND")
    tex:SetAllPoints(backdropFrame)
    local r, g, b = GetBorderColor()
    local a = db.borderColorA or 1
    tex:SetColorTexture(r, g, b, a)
    -- Re-use GetRegions() to get at it later
end

---------------------------------------------------------------------------
-- Clock
---------------------------------------------------------------------------
local function UpdateClock()
    if not clockText or not clockText:IsShown() then return end
    local db = DB()
    local hour, minute
    if GetCVarBool("timeMgrUseLocalTime") then
        hour   = tonumber(date("%H"))
        minute = tonumber(date("%M"))
    else
        hour, minute = GetGameTime()
    end
    if GetCVarBool("timeMgrUseMilitaryTime") then
        clockText:SetFormattedText(TIMEMANAGER_TICKER_24HOUR, hour, minute)
    else
        if hour == 0 then hour = 12
        elseif hour > 12 then hour = hour - 12 end
        clockText:SetFormattedText(TIMEMANAGER_TICKER_12HOUR, hour, minute)
    end
end

local function BuildClock()
    if clockText then return end
    clockText = Minimap:CreateFontString(nil, "OVERLAY")
    clockText:SetFont(BRIGHT_FONT, DB().clockFontSize or 12, "OUTLINE")
    clockText:SetTextColor(1, 1, 1, 0.9)
    clockText:SetPoint("BOTTOMLEFT", Minimap, "BOTTOMLEFT", 4, 4)
    clockText:SetShown(DB().showClock)
    UpdateClock()
    clockTimer = C_Timer.NewTicker(30, UpdateClock)
end

---------------------------------------------------------------------------
-- Zone Text
---------------------------------------------------------------------------
local function UpdateZone()
    if not zoneText or not zoneText:IsShown() then return end
    local text = GetMinimapZoneText()
    zoneText:SetText(text)
    local pvpType = (C_PvP and C_PvP.GetZonePVPInfo or GetZonePVPInfo)()
    local colors = {
        sanctuary = {0.41, 0.80, 0.94},
        arena      = {1.0,  0.1,  0.1},
        friendly   = {0.1,  1.0,  0.1},
        hostile    = {1.0,  0.1,  0.1},
        contested  = {1.0,  0.7,  0.0},
    }
    local c = colors[pvpType] or {1, 0.82, 0}
    zoneText:SetTextColor(c[1], c[2], c[3], 1)
end

local function BuildZoneText()
    if zoneText then return end
    zoneText = Minimap:CreateFontString(nil, "OVERLAY")
    zoneText:SetFont(BRIGHT_FONT, DB().zoneFontSize or 12, "OUTLINE")
    zoneText:SetPoint("TOP", Minimap, "TOP", 0, -4)
    zoneText:SetWidth(Minimap:GetWidth() - 8)
    zoneText:SetJustifyH("CENTER")
    zoneText:SetShown(DB().showZoneText)

    -- Hide Blizzard zone text
    if MinimapZoneText then MinimapZoneText:SetAlpha(0) end
    if MinimapCluster and MinimapCluster.ZoneTextButton then
        MinimapCluster.ZoneTextButton:SetAlpha(0)
    end

    -- Event listener on minimap frame
    Minimap:RegisterEvent("ZONE_CHANGED")
    Minimap:RegisterEvent("ZONE_CHANGED_INDOORS")
    Minimap:RegisterEvent("ZONE_CHANGED_NEW_AREA")
    local oldOnEvent = Minimap:GetScript("OnEvent")
    Minimap:HookScript("OnEvent", function(_, event)
        if event == "ZONE_CHANGED" or event == "ZONE_CHANGED_INDOORS"
                or event == "ZONE_CHANGED_NEW_AREA" then
            UpdateZone()
        end
    end)
    UpdateZone()
end

---------------------------------------------------------------------------
-- Coordinates
---------------------------------------------------------------------------
local function UpdateCoords()
    if not coordText or not coordText:IsShown() then return end
    local mapID = C_Map.GetBestMapForUnit("player")
    if mapID then
        local pos = C_Map.GetPlayerMapPosition(mapID, "player")
        if pos then
            coordText:SetFormattedText("%.0f, %.0f", pos.x * 100, pos.y * 100)
            return
        end
    end
    coordText:SetText("–, –")
end

local function BuildCoords()
    if coordText then return end
    coordText = Minimap:CreateFontString(nil, "OVERLAY")
    coordText:SetFont(BRIGHT_FONT, DB().coordFontSize or 12, "OUTLINE")
    coordText:SetTextColor(1, 1, 1, 0.85)
    coordText:SetPoint("BOTTOMRIGHT", Minimap, "BOTTOMRIGHT", -4, 4)
    coordText:SetShown(DB().showCoords)
    UpdateCoords()
    coordTimer = C_Timer.NewTicker(DB().coordTime or 1, UpdateCoords)
end

---------------------------------------------------------------------------
-- Button visibility
---------------------------------------------------------------------------
local function SetButtonVisible(frame, visible)
    if not frame then return end
    -- Use alpha+mouse rather than SetParent or SetShown:
    -- * SetShown on protected frames (ExpansionLandingPage, etc.) triggers
    --   Blizzard tooltip code with nil args in 12.x.
    -- * SetParent breaks OnEvent handlers that call Minimap:Layout().
    -- alpha=0 + EnableMouse(false) is the safe silent hide.
    if visible then
        frame:SetAlpha(1)
        frame:EnableMouse(true)
    else
        frame:SetAlpha(0)
        frame:EnableMouse(false)
    end
end

local function ApplyButtons()
    local db = DB()

    -- Zoom buttons — these are regular Buttons, SetShown is fine
    if Minimap.ZoomIn then
        Minimap.ZoomIn:SetShown(db.showZoomBtns)
        Minimap.ZoomOut:SetShown(db.showZoomBtns)
    end

    -- Mail & Crafting Order indicators
    if MinimapCluster and MinimapCluster.IndicatorFrame then
        SetButtonVisible(MinimapCluster.IndicatorFrame.MailFrame,         db.showMail)
        SetButtonVisible(MinimapCluster.IndicatorFrame.CraftingOrderFrame, db.showCraftingOrder)
    end

    -- Addon Compartment
    SetButtonVisible(AddonCompartmentFrame, db.showAddonCompartment)

    -- Missions / Landing Page — SetShown triggers Blizzard tooltip with nil args in 12.x;
    -- use SetParent to suppress it cleanly instead
    SetButtonVisible(ExpansionLandingPageMinimapButton, db.showMissions)

    -- Raid/instance difficulty icon
    if MinimapCluster and MinimapCluster.InstanceDifficulty then
        SetButtonVisible(MinimapCluster.InstanceDifficulty, db.raidDiffIcon)
    end
end

---------------------------------------------------------------------------
-- Drag to move
---------------------------------------------------------------------------
local function ApplyDragging()
    local db = DB()
    Minimap:SetParent(UIParent)
    MinimapCluster:EnableMouse(false)
    Minimap:SetMovable(not db.lock)
    Minimap:SetClampedToScreen(true)
    if not Minimap:GetScript("OnDragStart") then
        Minimap:RegisterForDrag("LeftButton")
        Minimap:SetScript("OnDragStart", function(f)
            if DB().lock then return end
            if DB().shape == "ROUND" then
                local mx, my = GetCursorPosition()
                local scale = f:GetEffectiveScale()
                mx, my = mx / scale, my / scale
                local cx, cy = f:GetCenter()
                if math.sqrt((mx-cx)^2 + (my-cy)^2) > f:GetWidth() / 2 then return end
            end
            f:StartMoving()
        end)
        Minimap:SetScript("OnDragStop", function(f)
            f:StopMovingOrSizing()
            local a, _, b, c, d = f:GetPoint()
            local mm = DB()
            mm.savedPoint = a; mm.savedRelPoint = b
            mm.savedX = c; mm.savedY = d
        end)
    end
    -- Restore saved position if any
    local mm = db
    if mm.savedPoint then
        Minimap:ClearAllPoints()
        Minimap:SetPoint(mm.savedPoint, UIParent, mm.savedRelPoint, mm.savedX, mm.savedY)
    end
end

---------------------------------------------------------------------------
-- Mouse wheel zoom
---------------------------------------------------------------------------
local function ApplyMouseWheel()
    Minimap:EnableMouseWheel(true)
    if not Minimap.__brightWheelHooked then
        Minimap.__brightWheelHooked = true
        Minimap:HookScript("OnMouseWheel", function(_, d)
            if d > 0 then Minimap.ZoomIn:Click()
            else          Minimap.ZoomOut:Click() end
        end)
    end
end

---------------------------------------------------------------------------
-- Auto-zoom out
---------------------------------------------------------------------------
local function SetupAutoZoom()
    local db = DB()
    if not db.autoZoom then return end
    local function doZoomOut()
        zoomCurrent = zoomCurrent + 1
        if zoomStart == zoomCurrent then
            Minimap:SetZoom(0)
            Minimap.ZoomIn:Enable()
            Minimap.ZoomOut:Disable()
            zoomStart, zoomCurrent = 0, 0
        end
    end
    local function onZoom()
        if DB().autoZoom then
            zoomStart = zoomStart + 1
            C_Timer.After(10, doZoomOut)
        end
    end
    onZoom()
    Minimap.ZoomIn:HookScript("OnClick", onZoom)
    Minimap.ZoomOut:HookScript("OnClick", onZoom)
end

---------------------------------------------------------------------------
-- Mouse click bindings (calendar, tracking, map, missions)
---------------------------------------------------------------------------
-- Hijack frame for mouse clicks — Minimap is not a Button in 12.x and does not
-- support RegisterForClicks, so we place a transparent frame on top of it instead.
local clickHijack = nil

local function ApplyMouseClicks()
    local db = DB()

    local TrackingButton = MinimapCluster and MinimapCluster.Tracking
                           and MinimapCluster.Tracking.Button

    -- Build the click hijack frame once
    if not clickHijack then
        clickHijack = CreateFrame("Frame")
        clickHijack:SetParent(Minimap)
        clickHijack:SetPoint("CENTER", Minimap, "CENTER")
        clickHijack:SetSize(db.size or 200, db.size or 200)
        clickHijack:EnableMouse(true)
        -- Pass left-button through so the minimap still pans/pings
        clickHijack:SetPassThroughButtons("LeftButton")
        clickHijack:SetPropagateMouseMotion(true)
        clickHijack:Show()

        clickHijack:SetScript("OnMouseUp", function(_, btn)
            if btn == DB().calendarBtn and not InCombatLockdown() then
                GameTimeFrame:Click()
            elseif btn == DB().trackingBtn and TrackingButton then
                TrackingButton:OpenMenu()
            elseif btn == DB().missionsBtn and ExpansionLandingPageMinimapButton then
                ExpansionLandingPageMinimapButton:Click()
            elseif btn == DB().mapBtn and not InCombatLockdown() then
                ToggleWorldMap()
            end
        end)
    else
        -- Resize if minimap size changed
        clickHijack:SetSize(db.size or 200, db.size or 200)
    end
end

---------------------------------------------------------------------------
-- Hide LDB addon icons (show on hover)
---------------------------------------------------------------------------
local function ApplyHideAddons()
    if not DB().hideAddons then return end
    local ldbi = LibStub and LibStub("LibDBIcon-1.0", true)
    if not ldbi then return end
    local tbl = ldbi:GetButtonList()
    for i = 1, #tbl do
        ldbi:ShowOnEnter(tbl[i], true)
    end
    ldbi.RegisterCallback(MinimapMod, "LibDBIcon_IconCreated", function(_, _, name)
        ldbi:ShowOnEnter(name, true)
    end)
end

---------------------------------------------------------------------------
-- Public API
---------------------------------------------------------------------------
function MinimapMod:ApplySettings()
    local db = DB()
    if not db.enabled then return end
    BuildBackdrop()
    ApplyShape()
    ApplySize()
    ApplyBorder()
    BuildClock()
    BuildZoneText()
    BuildCoords()
    ApplyButtons()
    ApplyDragging()
    ApplyMouseWheel()
    SetupAutoZoom()
    ApplyMouseClicks()
    ApplyHideAddons()
end

function MinimapMod:RefreshVisuals()
    local db = DB()
    -- Clock
    if clockText  then clockText:SetShown(db.showClock)    end
    if zoneText   then zoneText:SetShown(db.showZoneText)  end
    if coordText  then coordText:SetShown(db.showCoords)   end
    -- Shape / size / border refresh
    ApplyShape()
    ApplyBorder()
    ApplySize()
    ApplyButtons()
    -- Font sizes
    if clockText  then clockText:SetFont(BRIGHT_FONT, db.clockFontSize or 12, "OUTLINE") end
    if zoneText   then zoneText:SetFont(BRIGHT_FONT, db.zoneFontSize or 12, "OUTLINE")   end
    if coordText  then coordText:SetFont(BRIGHT_FONT, db.coordFontSize or 12, "OUTLINE") end
    -- Lock
    Minimap:SetMovable(not db.lock)
end

function MinimapMod:UpdateDisplay()
    MinimapMod:RefreshVisuals()
end

---------------------------------------------------------------------------
-- Init
---------------------------------------------------------------------------
local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("PLAYER_LOGIN")
initFrame:SetScript("OnEvent", function(self, event)
    if event == "PLAYER_LOGIN" then
        self:UnregisterEvent("PLAYER_LOGIN")
        if DB().enabled then
            MinimapMod:ApplySettings()
        end
    end
end)
