-- [ MINIMAP CAPTURE ]--------------------------------------------------------------------------------
-- Blizzard minimap capture, art stripping, frame guard, and Blizzard component reparenting.

---@type Orbit
local Orbit = Orbit
local L = Orbit.L
local OrbitEngine = Orbit.Engine
local C = Orbit.MinimapConstants

local SYSTEM_ID = C.SYSTEM_ID
local MISSIONS_BASE_SIZE = C.MISSIONS_BASE_SIZE
local CORNER_INSET = C.COMPONENT_CORNER_INSET
local CRAFTING_ORDER_OFFSET_Y = C.CRAFTING_ORDER_OFFSET_Y
local DIFFICULTY_DEFAULT_ICON_SIZE = C.DIFFICULTY_DEFAULT_ICON_SIZE
local MAIL_ICON_SIZE = C.MAIL_ICON_SIZE
local CRAFTING_ICON_SIZE = C.CRAFTING_ICON_SIZE

local Plugin = Orbit:GetPlugin(SYSTEM_ID)

-- [ BLIZZARD REFERENCES ]----------------------------------------------------------------------------
local function GetBlizzardMinimap() return Minimap end
local function GetBlizzardCluster() return MinimapCluster end

local function OpenTrackingMenu(frame)
    local nativeButton = MinimapCluster and MinimapCluster.Tracking and MinimapCluster.Tracking.Button
    if not (nativeButton and nativeButton.menuGenerator) then return end
    local menuMixin = frame.menuMixin or MenuVariants.GetDefaultContextMenuMixin()
    local description = MenuUtil.CreateRootMenuDescription(menuMixin)
    Menu.PopulateDescription(nativeButton.menuGenerator, nativeButton, description)
    Menu.GetManager():OpenMenu(frame, description, AnchorUtil.CreateAnchor("TOPLEFT", frame, "BOTTOMLEFT", 0, 0))
end

local function GetActiveDifficultyFrame(difficulty)
    if difficulty.ChallengeMode and difficulty.ChallengeMode:IsShown() then return difficulty.ChallengeMode end
    if difficulty.Guild and difficulty.Guild:IsShown() then return difficulty.Guild end
    return difficulty.Default
end

local function UpdateDifficultyBounds(difficulty)
    local activeFrame = GetActiveDifficultyFrame(difficulty)
    local width = 0
    local height = 0

    local bg = activeFrame and activeFrame.Background
    local border = activeFrame and activeFrame.Border
    local icon = difficulty.Icon
    if bg then
        width = math.max(width, bg:GetWidth() or 0)
        height = math.max(height, bg:GetHeight() or 0)
    end
    if border then
        width = math.max(width, border:GetWidth() or 0)
        height = math.max(height, border:GetHeight() or 0)
    end
    if icon then
        width = math.max(width, icon:GetWidth() or 0)
        height = math.max(height, icon:GetHeight() or 0)
    end

    difficulty.orbitOriginalWidth = width > 0 and width or DIFFICULTY_DEFAULT_ICON_SIZE
    difficulty.orbitOriginalHeight = height > 0 and height or DIFFICULTY_DEFAULT_ICON_SIZE
end

local function GetDifficultyIconTexture(difficulty)
    local activeFrame = GetActiveDifficultyFrame(difficulty)
    if not activeFrame then return nil end

    for _, region in ipairs({ activeFrame:GetRegions() }) do
        if region and region:GetObjectType() == "Texture" and region:IsShown() and region ~= activeFrame.Background and region ~= activeFrame.Border then
            local atlas = region.GetAtlas and region:GetAtlas()
            local texturePath = region:GetTexture()
            if atlas or texturePath then
                return region
            end
        end
    end

    return nil
end

local function SyncDifficultyPreviewIcon(difficulty)
    if not difficulty or not difficulty.Icon then return end

    local sourceTexture = GetDifficultyIconTexture(difficulty)
    if not sourceTexture then return end

    local atlas = sourceTexture.GetAtlas and sourceTexture:GetAtlas()
    if atlas then
        local info = C_Texture.GetAtlasInfo(atlas)
        if info and info.file then
            difficulty.Icon:SetTexture(info.file)
            difficulty.Icon:SetTexCoord(info.leftTexCoord, info.rightTexCoord, info.topTexCoord, info.bottomTexCoord)
        else
            difficulty.Icon:SetAtlas(atlas, false)
        end
    else
        difficulty.Icon:SetTexture(sourceTexture:GetTexture())
        difficulty.Icon:SetTexCoord(sourceTexture:GetTexCoord())
    end

    local width = sourceTexture:GetWidth()
    local height = sourceTexture:GetHeight()
    difficulty.Icon:SetSize(width and width > 0 and width or DIFFICULTY_DEFAULT_ICON_SIZE, height and height > 0 and height or DIFFICULTY_DEFAULT_ICON_SIZE)
    difficulty.Icon:SetAlpha(0)
end

function Plugin:RunMinimapClickAction(action, frame)
    -- worldmap/calendar route through ShowUIPanel, which Blizzard blocks for tainted (addon) callers in combat — attempting it throws "Interface Action Failed".
    if (action == "worldmap" or action == "calendar") and InCombatLockdown() then
        UIErrorsFrame:AddMessage(L.PLU_MINIMAP_COMBAT_BLOCK, 1, 0, 0)
        return
    end
    if action == "worldmap" then
        ToggleWorldMap()
    elseif action == "tracking" then
        OpenTrackingMenu(frame)
    elseif action == "calendar" then
        ToggleCalendar()
    elseif action == "time" then
        TimeManager_Toggle()
    elseif action == "addons" then
        self:ApplyAddonCompartment()
        self:ToggleCompartmentFlyout()
    end
end

-- Expose on plugin so other files can use them without re-declaring
Plugin.GetBlizzardMinimap = GetBlizzardMinimap

-- [ BLIZZARD ART STRIPPING ]-------------------------------------------------------------------------
-- Reparent (vs :Hide()/SetAlpha(0)) so the original ring can't reappear on rotateMinimap toggle, zone change, or other internal mutations.
local _hiddenHolder
local function GetHiddenHolder()
    if not _hiddenHolder then
        _hiddenHolder = CreateFrame("Frame", "OrbitMinimapHiddenHolder", UIParent)
        _hiddenHolder:Hide()
    end
    return _hiddenHolder
end

local function StripBlizzardArt()
    local cluster = GetBlizzardCluster()
    if not cluster then return end

    -- Hide the entire cluster (takes BorderTop, ZoneTextButton, Tracking, IndicatorFrame, InstanceDifficulty)
    OrbitEngine.NativeFrame:Hide(cluster, { unregisterEvents = false, clearScripts = false })

    local holder = GetHiddenHolder()
    if MinimapBackdrop and MinimapBackdrop.SetParent then MinimapBackdrop:SetParent(holder) end
    if MinimapCompassTexture and MinimapCompassTexture.SetParent then MinimapCompassTexture:SetParent(holder) end

    -- Suppress Blizzard's edit mode selection on the minimap cluster
    if cluster.Selection then cluster.Selection:SetAlpha(0); cluster.Selection:EnableMouse(false) end

    -- Hide Blizzard's native zoom buttons and hover area (we provide our own)
    local minimap = GetBlizzardMinimap()
    if minimap then
        if minimap.ZoomIn then
            minimap.ZoomIn:Hide()
            minimap.ZoomIn:SetScript("OnShow", minimap.ZoomIn.Hide)
        end
        if minimap.ZoomOut then
            minimap.ZoomOut:Hide()
            minimap.ZoomOut:SetScript("OnShow", minimap.ZoomOut.Hide)
        end
        if minimap.ZoomHitArea then
            minimap.ZoomHitArea:Hide()
            minimap.ZoomHitArea:EnableMouse(false)
        end
    end
end

-- [ BLIZZARD COMPONENT REPARENTING ]-----------------------------------------------------------------
function Plugin:ReparentBlizzardComponents()
    local overlay = self.frame.Overlay

    -- Instance Difficulty indicator
    local difficulty = MinimapCluster and MinimapCluster.InstanceDifficulty
    if difficulty then
        self._origDifficultyParent = difficulty:GetParent()
        local iconFrame = self.frame.DifficultyIcon
        if not iconFrame then
            iconFrame = CreateFrame("Frame", nil, overlay)
            iconFrame:SetPoint("CENTER", self.frame, "TOPLEFT", CORNER_INSET, -CORNER_INSET)
            iconFrame.orbitOriginalWidth = DIFFICULTY_DEFAULT_ICON_SIZE
            iconFrame.orbitOriginalHeight = DIFFICULTY_DEFAULT_ICON_SIZE
            self.frame.DifficultyIcon = iconFrame
        end

        local textFrame = self.frame.DifficultyText
        if not textFrame then
            textFrame = CreateFrame("Frame", nil, overlay)
            textFrame:SetPoint("CENTER", self.frame, "TOPLEFT", CORNER_INSET, -CORNER_INSET)
            textFrame.Text = textFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            textFrame.Text:SetPoint("CENTER")
            textFrame.Text:SetJustifyH("CENTER")
            textFrame.visual = textFrame.Text
            textFrame.orbitHandleMinWidth = 0
            textFrame.orbitHandleMinHeight = 0
            textFrame.orbitHideHandleHeader = true
            textFrame.orbitHandleHeaderMinWidth = 0
            self.frame.DifficultyText = textFrame
        end

        difficulty:SetParent(iconFrame)
        difficulty:ClearAllPoints()
        difficulty:SetPoint("CENTER", iconFrame, "CENTER", 0, 0)
        
        -- Strip Blizzard guild-banner Background/Border chrome — keep only the difficulty icon texture.
        for _, sub in ipairs({ difficulty.Default, difficulty.Guild, difficulty.ChallengeMode }) do
            if sub then
                if sub.Background then sub.Background:SetAlpha(0) end
                if sub.Border then sub.Border:SetAlpha(0) end
            end
        end

        if not difficulty.Icon then
            difficulty.Icon = difficulty:CreateTexture(nil, "ARTWORK")
            difficulty.Icon:SetSize(DIFFICULTY_DEFAULT_ICON_SIZE, DIFFICULTY_DEFAULT_ICON_SIZE)
            difficulty.Icon:SetPoint("CENTER")
            difficulty.Icon:SetAlpha(0)
        end
        iconFrame.Default = difficulty.Default
        iconFrame.Guild = difficulty.Guild
        iconFrame.ChallengeMode = difficulty.ChallengeMode
        iconFrame.Icon = difficulty.Icon
        SyncDifficultyPreviewIcon(difficulty)
        UpdateDifficultyBounds(difficulty)
        iconFrame.orbitOriginalWidth = difficulty.orbitOriginalWidth or DIFFICULTY_DEFAULT_ICON_SIZE
        iconFrame.orbitOriginalHeight = difficulty.orbitOriginalHeight or DIFFICULTY_DEFAULT_ICON_SIZE
        iconFrame:SetSize(iconFrame.orbitOriginalWidth, iconFrame.orbitOriginalHeight)
        if self.UpdateDifficultyVisuals then self:UpdateDifficultyVisuals() end

        if not difficulty._orbitPreviewHooksInstalled then
            local function RefreshDifficultyPreview()
                SyncDifficultyPreviewIcon(difficulty)
                UpdateDifficultyBounds(difficulty)
                iconFrame.orbitOriginalWidth = difficulty.orbitOriginalWidth or DIFFICULTY_DEFAULT_ICON_SIZE
                iconFrame.orbitOriginalHeight = difficulty.orbitOriginalHeight or DIFFICULTY_DEFAULT_ICON_SIZE
                iconFrame:SetSize(iconFrame.orbitOriginalWidth, iconFrame.orbitOriginalHeight)
                if self.UpdateDifficultyVisuals then self:UpdateDifficultyVisuals() end
            end

            for _, sub in ipairs({ difficulty.Default, difficulty.Guild, difficulty.ChallengeMode }) do
                if sub then
                    sub:HookScript("OnShow", RefreshDifficultyPreview)
                    sub:HookScript("OnHide", RefreshDifficultyPreview)
                end
            end

            difficulty:HookScript("OnShow", RefreshDifficultyPreview)
            difficulty._orbitPreviewHooksInstalled = true
        end
        self.frame.Difficulty = difficulty
    end

    -- Expansion Landing Page (Missions) button
    local missions = ExpansionLandingPageMinimapButton
    if missions then
        self._origMissionsParent = missions:GetParent()
        missions:SetParent(overlay)
        missions:ClearAllPoints()
        missions:SetPoint("CENTER", self.frame, "BOTTOMLEFT", CORNER_INSET, CORNER_INSET)
        missions:SetSize(MISSIONS_BASE_SIZE, MISSIONS_BASE_SIZE)
        -- Size to MISSIONS_BASE_SIZE so CyclingAtlas's GetSourceSize feeds correct dimensions to the crossfade preview.
        if not missions.Icon then
            missions.Icon = missions:CreateTexture(nil, "ARTWORK")
            missions.Icon:SetPoint("CENTER")
            missions.Icon:SetAlpha(0)
        end
        missions.Icon:SetSize(MISSIONS_BASE_SIZE, MISSIONS_BASE_SIZE)
        self.frame.Missions = missions
    end

    -- New Mail indicator
    local mail = MinimapCluster and MinimapCluster.IndicatorFrame and MinimapCluster.IndicatorFrame.MailFrame
    if mail then
        self._origMailParent = mail:GetParent()
        mail:SetParent(overlay)
        mail:ClearAllPoints()
        mail:SetPoint("CENTER", self.frame, "TOPRIGHT", -CORNER_INSET, -CORNER_INSET)
        mail:Show()
        -- Call via mixin so NewMailAnim/MailReminderAnim flipbook animations play — manual MailIcon visibility bypasses them.
        if mail.TryPlayMailNotification and HasNewMail and HasNewMail() then mail:TryPlayMailNotification() end
        if not mail.Icon then
            mail.Icon = mail:CreateTexture(nil, "ARTWORK")
            mail.Icon:SetSize(MAIL_ICON_SIZE, MAIL_ICON_SIZE)
            mail.Icon:SetPoint("CENTER")
            mail.Icon:SetAlpha(0)
        end
        self.frame.Mail = mail
    end

    -- Crafting Order indicator
    local craftingOrder = MinimapCluster and MinimapCluster.IndicatorFrame and MinimapCluster.IndicatorFrame.CraftingOrderFrame
    if craftingOrder then
        self._origCraftingOrderParent = craftingOrder:GetParent()
        craftingOrder:SetParent(overlay)
        craftingOrder:ClearAllPoints()
        craftingOrder:SetPoint("CENTER", self.frame, "TOPRIGHT", -CORNER_INSET, CRAFTING_ORDER_OFFSET_Y)
        
        -- Native texture has a baked-in border + tight scaling → low-res; hide it and use a borderless high-res anvil atlas.
        if MiniMapCraftingOrderIcon then
            MiniMapCraftingOrderIcon:SetAlpha(0)
        end
        
        if not craftingOrder.Icon then
            craftingOrder.Icon = craftingOrder:CreateTexture(nil, "ARTWORK")
            craftingOrder.Icon:SetSize(CRAFTING_ICON_SIZE, CRAFTING_ICON_SIZE)
            craftingOrder.Icon:SetPoint("CENTER")
            craftingOrder.Icon:SetAtlas("UI-HUD-Minimap-CraftingOrder-Over-2x", true)
            
            -- Ignore native scaling so the atlas stays crisp
            craftingOrder.Icon:SetScale(1)
        end
        self.frame.CraftingOrder = craftingOrder
    end
end

-- [ CAPTURE ]----------------------------------------------------------------------------------------
function Plugin:CaptureBlizzardMinimap()
    local minimap = GetBlizzardMinimap()

    StripBlizzardArt()

    -- Parent to whichever host matches the current view so a re-capture mid-HUD doesn't snap us back.
    local view = self:GetSetting(C.SYSTEM_ID, "View") or "minimap"
    local host = (view == "hud" and self.hudFrame) or self.frame
    minimap:SetParent(host)
    minimap:ClearAllPoints()
    minimap:SetPoint("CENTER", host, "CENTER", 0, 0)
    minimap:SetSize(host:GetWidth(), host:GetHeight())

    minimap:EnableMouse(true)
    if minimap.RegisterForClicks then
        minimap:RegisterForClicks("AnyUp")
    end
    minimap:SetArchBlobRingScalar(0)
    minimap:SetQuestBlobRingScalar(0)

    -- Apply the correct mask immediately at capture time (ApplyShape will reconcile if needed).
    local shape = view == "hud" and "splatter" or (self:GetSetting(C.SYSTEM_ID, "Shape") or "square")
    local isMasked = shape == "round" or shape == "splatter"
    minimap:SetMaskTexture(isMasked and self:GetRoundMaskSource() or self:GetSquareMaskSource())

    OrbitEngine.FrameGuard:Protect(minimap, host)
    OrbitEngine.FrameGuard:UpdateProtection(minimap, host, function() self:ApplySettings() end, { enforceShow = true })

    -- Clicks are handled by OrbitMinimapClickCapture (created in OnLoad) — a MEDIUM-strata propagating Button above the surface but below HIGH-strata plugin frames.

    -- FarmHud hook suspends FrameGuard protection so FarmHud can freely reparent the minimap surface.
    self:HookFarmHud()

    -- Update zoom button state after scroll-wheel zoom
    if not minimap._orbitScrollHooked then
        minimap:HookScript("OnMouseWheel", function()
            self:UpdateZoomState()
            self:StartAutoZoomOut()
        end)
        minimap._orbitScrollHooked = true
    end

    self._captured = true
end

-- [ FARMHUD COMPATIBILITY ]--------------------------------------------------------------------------
-- Suspend FrameGuard and surface-sync while FarmHud owns the Minimap. See readme for details.

function Plugin:HookFarmHud()
    if self._farmHudHooked then return end

    -- Deferred: FarmHud may load after Orbit.
    C_Timer.After(0, function()
        if not FarmHud then return end

        FarmHud:RegisterForeignAddOnObject(self.frame, "Orbit")

        -- Hook OnShow/OnHide only once.
        if not self._farmHudHooked then
            FarmHud:HookScript("OnShow", function() self:OnFarmHudShow() end)
            FarmHud:HookScript("OnHide", function() self:OnFarmHudHide() end)
            self._farmHudHooked = true
        end
    end)
end

function Plugin:OnFarmHudShow()
    self._farmHudActive = true

    local minimap = self:GetBlizzardMinimap()
    if minimap then
        OrbitEngine.FrameGuard:Suspend(minimap)
    end
end

function Plugin:OnFarmHudHide()
    self._farmHudActive = nil

    local minimap = self:GetBlizzardMinimap()
    if minimap then
        OrbitEngine.FrameGuard:Resume(minimap)
    end

    -- Re-apply settings to recapture the minimap surface and restore our layout.
    self:ApplySettings()
end

