local addonName, privateDB = ...

-- ============================================================================
-- HDG_LumberHUD MODULE
-- Temporal Radar style HUD showing recent lumber gathering locations
-- Based on architecture doc: "Architectural Synthesis of Spatiotemporal HUDs"
-- Section 4.1: Concept A - Circular radar with player at center
-- ============================================================================

local HDG_LumberHUD = {}
privateDB.HDG_LumberHUD = HDG_LumberHUD

-- ============================================================================
-- CONFIGURATION
-- ============================================================================
local CONFIG = {
    RADAR_SIZE = 200,           -- Base size of the radar frame in pixels
    RANGE_YARDS = 450,          -- Radius of the radar in game yards
    RESPAWN_TIME = 600,         -- Time in seconds until a node "respawns" (10 mins)
    MAX_BLIPS = 50,             -- Maximum history to track
    UPDATE_FREQUENCY = 0.05,    -- Default 20fps (configurable via settings)
    RING_DISTANCES = {150, 300}, -- Distance rings in yards
}

-- Get configurable values from saved settings
local function GetUpdateFrequency()
    local fps = HDG_Settings and HDG_Settings.lumberHUDFPS or 20
    return 1 / fps
end

local function GetRadarScale()
    return HDG_Settings and HDG_Settings.lumberHUDScale or 1.0
end

-- ============================================================================
-- DISTANCE CALCULATION (matches TomTom/HereBeDragons approach)
-- ============================================================================
-- Convert map coordinates (0-1) to world coordinates (yards)
-- This is the same approach HereBeDragons uses for accurate distances
local function GetWorldCoordsFromMap(mapX, mapY, mapID)
    local vector = CreateVector2D(mapX, mapY)
    local instance, worldPos = C_Map.GetWorldPosFromMapPos(mapID, vector)
    if not worldPos then return nil, nil, nil end
    local x, y = worldPos:GetXY()
    return x, y, instance
end

-- Time-based decay thresholds (Section 6.1)
-- Red = Dead (just cut), Yellow = Warming up, Green = Ready to return, Grey = Old
local DECAY_THRESHOLDS = {
    FRESH = 0.5,      -- 0-50% of respawn: Red
    RESPAWNING = 0.9, -- 50-90% of respawn: Yellow
    -- 90%+ to 1 hour: Green (ready)
    OLD = 3600,       -- After 1 hour: Grey
}

-- ============================================================================
-- STATE
-- ============================================================================
local radarFrame = nil
local isVisible = false
local blipsByNodeKey = {}  -- Persistent blips keyed by node identifier (mapID_x_y)
local updateTimer = 0
local cardinalLabels = {} -- Store references for rotation

-- ============================================================================
-- BLIP MANAGEMENT (Persistent - no recycling to avoid flickering)
-- ============================================================================
local function GetNodeKey(node)
    -- Create a unique key for each node based on its coordinates
    return string.format("%d_%.6f_%.6f", node.mapID, node.x, node.y)
end

local function GetOrCreateBlip(nodeKey)
    if blipsByNodeKey[nodeKey] then
        return blipsByNodeKey[nodeKey]
    end

    local blip = CreateFrame("Frame", nil, radarFrame)
    blip:SetSize(4, 4)

    -- The dot texture
    blip.dot = blip:CreateTexture(nil, "ARTWORK")
    blip.dot:SetAllPoints()
    blip.dot:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask")

    -- Distance label (removed - keeping for potential future use but hidden)
    blip.label = blip:CreateFontString(nil, "OVERLAY")
    blip.label:SetFont("Fonts\\FRIZQT__.TTF", 7, "OUTLINE")
    blip.label:SetPoint("TOP", blip, "BOTTOM", 0, 0)
    blip.label:SetTextColor(0.8, 0.8, 0.8, 0.9)
    blip.label:Hide()  -- Hidden by default now

    blipsByNodeKey[nodeKey] = blip
    return blip
end

-- ============================================================================
-- UI CREATION
-- ============================================================================
local function CreateRadar()
    if radarFrame then return end

    radarFrame = CreateFrame("Frame", "HDG_LumberRadarFrame", UIParent)
    radarFrame:SetSize(CONFIG.RADAR_SIZE, CONFIG.RADAR_SIZE)
    radarFrame:EnableMouse(true)
    radarFrame:SetMovable(true)
    radarFrame:RegisterForDrag("LeftButton")

    -- Load saved position or default (center, slightly down)
    local pos = HDG_Settings and HDG_Settings.lumberHUDPosition
    if pos then
        radarFrame:SetPoint(pos.point, UIParent, pos.relativePoint, pos.x, pos.y)
    else
        radarFrame:SetPoint("CENTER", UIParent, "CENTER", 0, -100)
    end

    -- Dragging
    radarFrame:SetScript("OnDragStart", function(self)
        self:StartMoving()
    end)
    radarFrame:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        local point, _, relativePoint, x, y = self:GetPoint()
        if HDG_Settings then
            HDG_Settings.lumberHUDPosition = {
                point = point,
                relativePoint = relativePoint,
                x = x,
                y = y
            }
        end
    end)

    -- Circular background with subtle green tint
    local bg = radarFrame:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask")
    bg:SetVertexColor(0, 0.02, 0, 0.65)  -- Very subtle green tint

    -- Outer edge glow/ring for a more "radar" look
    local outerGlow = radarFrame:CreateTexture(nil, "BACKGROUND", nil, 1)
    outerGlow:SetSize(CONFIG.RADAR_SIZE + 4, CONFIG.RADAR_SIZE + 4)
    outerGlow:SetPoint("CENTER")
    outerGlow:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask")
    outerGlow:SetVertexColor(0, 0.25, 0, 0.35)  -- Subtle green outer ring

    -- Distance Rings (using stacked circles to create ring outlines - no filled textures)
    for _, radiusYards in ipairs(CONFIG.RING_DISTANCES) do
        local size = (radiusYards / CONFIG.RANGE_YARDS) * CONFIG.RADAR_SIZE
        local ringThickness = 2  -- pixels

        -- Outer circle (green)
        local outerRing = radarFrame:CreateTexture(nil, "BORDER")
        outerRing:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask")
        outerRing:SetVertexColor(0, 0.4, 0, 0.25)  -- Dimmer green
        outerRing:SetSize(size, size)
        outerRing:SetPoint("CENTER")

        -- Inner circle (same as background) to "cut out" the center, leaving just the ring
        local innerCutout = radarFrame:CreateTexture(nil, "BORDER", nil, 1)  -- sublevel 1 = on top of sublevel 0
        innerCutout:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask")
        innerCutout:SetVertexColor(0, 0.02, 0, 0.65)  -- Match background color
        innerCutout:SetSize(size - ringThickness * 2, size - ringThickness * 2)
        innerCutout:SetPoint("CENTER")

        -- Distance label (positioned on the ring line)
        local label = radarFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        label:SetPoint("CENTER", radarFrame, "CENTER", 0, -(size / 2))
        label:SetText(radiusYards .. "y")
        label:SetTextColor(0, 1, 0, 0.5)
    end

    -- Player dot at center
    local playerDot = radarFrame:CreateTexture(nil, "OVERLAY")
    playerDot:SetSize(6, 6)
    playerDot:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask")
    playerDot:SetVertexColor(0, 1, 0, 1)
    playerDot:SetPoint("CENTER")

    -- Direction line
    local directionLine = radarFrame:CreateTexture(nil, "ARTWORK")
    directionLine:SetSize(2, 20)
    directionLine:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask")
    directionLine:SetVertexColor(0, 1, 0, 0.8)
    directionLine:SetPoint("BOTTOM", radarFrame, "CENTER", 0, 3)

    -- Cardinal direction labels (will be repositioned each frame based on facing)
    local function CreateDirectionLabel(text, angle)
        local label = radarFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        label:SetText(text)
        label:SetTextColor(0.7, 0.7, 0.7, 0.8)
        return { label = label, baseAngle = angle }
    end

    local labelOffset = (CONFIG.RADAR_SIZE / 2) - 12
    -- Store with their "world" angles (0 = North, pi/2 = East, pi = South, -pi/2 = West)
    cardinalLabels.N = CreateDirectionLabel("N", 0)
    cardinalLabels.E = CreateDirectionLabel("E", math.pi / 2)
    cardinalLabels.S = CreateDirectionLabel("S", math.pi)
    cardinalLabels.W = CreateDirectionLabel("W", -math.pi / 2)
    cardinalLabels.offset = labelOffset

    -- Apply initial scale from settings
    local initialScale = GetRadarScale()
    radarFrame:SetScale(initialScale)

    -- OnUpdate for rendering
    radarFrame:SetScript("OnUpdate", function(self, elapsed)
        if not isVisible then return end

        updateTimer = updateTimer + elapsed
        if updateTimer < GetUpdateFrequency() then return end
        updateTimer = 0

        HDG_LumberHUD.UpdateRadar()
    end)

    radarFrame:Hide()
end

-- ============================================================================
-- RADAR RENDERING (Section 4.1: Polar to Cartesian)
-- ============================================================================
local visibleBlipKeys = {} -- Track which blips should be visible this frame

function HDG_LumberHUD.UpdateRadar()
    -- Pre-calculate pulse values once per frame (optimization)
    local currentTime = GetTime()
    local pulseAlpha = 0.5 + 0.5 * math.sin(currentTime * 3) -- Slower pulse
    local pulseScale = 1.0 + 0.2 * math.sin(currentTime * 3)  -- Smaller scale variation

    -- Get Player Position (Section 7.1: Handle nil gracefully)
    local mapID = C_Map.GetBestMapForUnit("player")
    if not mapID then return end

    local pos = C_Map.GetPlayerMapPosition(mapID, "player")
    if not pos then
        -- Instance handling: position unavailable in instances
        return
    end

    local playerFacing = GetPlayerFacing()
    if not playerFacing then return end

    -- Update cardinal direction labels (rotate with player facing)
    local function UpdateCardinalLabel(data)
        local relAngle = data.baseAngle - playerFacing
        local x = -math.sin(relAngle) * cardinalLabels.offset
        local y = math.cos(relAngle) * cardinalLabels.offset
        data.label:ClearAllPoints()
        data.label:SetPoint("CENTER", radarFrame, "CENTER", x, y)
    end
    UpdateCardinalLabel(cardinalLabels.N)
    UpdateCardinalLabel(cardinalLabels.E)
    UpdateCardinalLabel(cardinalLabels.S)
    UpdateCardinalLabel(cardinalLabels.W)

    -- Get player world coordinates (using HereBeDragons-style conversion)
    local pWorldX, pWorldY, pInstance = GetWorldCoordsFromMap(pos.x, pos.y, mapID)
    if not pWorldX then return end

    -- Get coordinate history from SavedVariables
    local history = HDG_Settings and HDG_Settings.lumberCoordinates or {}
    local now = GetServerTime()

    -- Track which blips are visible this frame
    wipe(visibleBlipKeys)

    for _, node in ipairs(history) do
        -- Only track nodes in current zone
        if node.mapID == mapID then
            local age = now - (node.timestamp or node.time or 0)

            -- Get node world coordinates
            local nWorldX, nWorldY, nInstance = GetWorldCoordsFromMap(node.x, node.y, node.mapID)

            -- Skip if can't get coords or different instance
            if nWorldX and nInstance == pInstance then
                -- Calculate distance in yards using world coordinates
                local dx = nWorldX - pWorldX
                local dy = nWorldY - pWorldY
                local dist = math.sqrt(dx * dx + dy * dy)

                if dist < CONFIG.RANGE_YARDS then
                    -- Calculate angle to node
                    local angleToNode = math.atan2(dy, dx)

                    -- Relative angle: difference between node direction and player facing
                    local relativeAngle = angleToNode - playerFacing

                    -- Normalize to [-pi, pi]
                    while relativeAngle > math.pi do relativeAngle = relativeAngle - (2 * math.pi) end
                    while relativeAngle < -math.pi do relativeAngle = relativeAngle + (2 * math.pi) end

                    -- Convert polar to screen coords (Section 4.1)
                    local screenRadius = (dist / CONFIG.RANGE_YARDS) * (CONFIG.RADAR_SIZE / 2)
                    local blipX = -math.sin(relativeAngle) * screenRadius
                    local blipY = math.cos(relativeAngle) * screenRadius

                    -- Get or create persistent blip for this node
                    local nodeKey = GetNodeKey(node)
                    visibleBlipKeys[nodeKey] = true
                    local blip = GetOrCreateBlip(nodeKey)

                    -- Update position (this is the key - reposition, don't recreate)
                    blip:ClearAllPoints()
                    blip:SetPoint("CENTER", radarFrame, "CENTER", blipX, blipY)
                    blip:Show()

                    -- Heatmap Coloring (Section 6.1: Time-Based Decay)
                    local ageRatio = age / CONFIG.RESPAWN_TIME
                    local isPulsing = false
                    if age > DECAY_THRESHOLDS.OLD then
                        -- Grey: Old node (over 1 hour) - more transparent
                        blip.dot:SetVertexColor(0.5, 0.5, 0.5, 0.3)
                    elseif ageRatio < DECAY_THRESHOLDS.FRESH then
                        -- Red: Dead - don't return yet
                        blip.dot:SetVertexColor(1, 0.2, 0.2, 1)
                    elseif ageRatio < DECAY_THRESHOLDS.RESPAWNING then
                        -- Yellow: Warming up
                        blip.dot:SetVertexColor(1, 1, 0, 0.9)
                    else
                        -- Green: Ready to return (gentle pulse)
                        isPulsing = true
                        blip.dot:SetVertexColor(0, 1, 0, 0.7 + pulseAlpha * 0.3)
                    end

                    -- Scale blips: closer = larger
                    local baseScale = 1.0 + (1.0 - dist / CONFIG.RANGE_YARDS) * 0.3
                    local dotScale = baseScale
                    if isPulsing then
                        dotScale = baseScale * pulseScale
                    end
                    -- Keep blip frame at base size (for stable text), only scale the dot
                    blip:SetSize(4 * baseScale, 4 * baseScale)
                    blip.dot:ClearAllPoints()
                    blip.dot:SetPoint("CENTER")
                    blip.dot:SetSize(4 * dotScale, 4 * dotScale)
                end
            end
        end
    end

    -- Hide blips that are no longer visible (out of range or different zone)
    for nodeKey, blip in pairs(blipsByNodeKey) do
        if not visibleBlipKeys[nodeKey] then
            blip:Hide()
        end
    end
end

-- ============================================================================
-- PUBLIC API
-- ============================================================================
function HDG_LumberHUD.Show()
    if not radarFrame then CreateRadar() end
    radarFrame:Show()
    isVisible = true
    if HDG_Settings then
        HDG_Settings.lumberHUDVisible = true
    end
end

function HDG_LumberHUD.Hide()
    if radarFrame then
        radarFrame:Hide()
    end
    isVisible = false
    if HDG_Settings then
        HDG_Settings.lumberHUDVisible = false
    end
end

function HDG_LumberHUD.Toggle()
    if isVisible then
        HDG_LumberHUD.Hide()
        print("|cFF14b8a6[Housing Decor Guide]|r Lumber radar HUD hidden")
    else
        HDG_LumberHUD.Show()
        print("|cFF14b8a6[Housing Decor Guide]|r Lumber radar HUD shown")
    end
end

function HDG_LumberHUD.IsVisible()
    return isVisible
end

function HDG_LumberHUD.IsEnabled()
    return HDG_Settings and HDG_Settings.lumberHUDEnabled ~= false
end

-- Update radar scale (called from config)
function HDG_LumberHUD.UpdateScale(scale)
    if radarFrame then
        radarFrame:SetScale(scale)
    end
end

-- Update radar framerate (called from config) - no action needed, reads from settings dynamically
function HDG_LumberHUD.UpdateFramerate(fps)
    -- Framerate is read dynamically via GetUpdateFrequency(), no action needed
end

-- Called when lumber is collected (integration point)
function HDG_LumberHUD.OnLocationAdded(x, y, mapID)
    -- Auto-show HUD when farming starts (if enabled)
    if HDG_LumberHUD.IsEnabled() and not isVisible then
        HDG_LumberHUD.Show()
    end
end

-- Initialize on addon load
function HDG_LumberHUD.Initialize()
    CreateRadar()

    -- Restore visibility state
    if HDG_Settings and HDG_Settings.lumberHUDVisible then
        HDG_LumberHUD.Show()
    end
end

-- ============================================================================
-- DEBUG COMMANDS
-- ============================================================================
SLASH_LUMBERHUD1 = "/lumberhud"
SlashCmdList["LUMBERHUD"] = function(msg)
    if msg == "test" then
        -- Add a test node slightly offset from current location
        local mapID = C_Map.GetBestMapForUnit("player")
        if not mapID then
            print("|cFFFF0000[LumberHUD]|r Cannot get player map position")
            return
        end
        local pos = C_Map.GetPlayerMapPosition(mapID, "player")
        if not pos then
            print("|cFFFF0000[LumberHUD]|r Cannot get player position (instanced?)")
            return
        end

        -- Add to lumberCoordinates - offset by 0.005 (roughly 50 yards depending on zone)
        if not HDG_Settings then HDG_Settings = {} end
        if not HDG_Settings.lumberCoordinates then HDG_Settings.lumberCoordinates = {} end

        local testNode = {
            x = pos.x + 0.005,
            y = pos.y - 0.005, -- Offset to the north-east
            mapID = mapID,
            timestamp = GetServerTime(),
            lumberId = 0,
            zone = GetZoneText() or "Unknown"
        }
        table.insert(HDG_Settings.lumberCoordinates, testNode)

        print(string.format("|cFF14b8a6[LumberHUD]|r Added test node: x=%.4f y=%.4f mapID=%d", testNode.x, testNode.y, mapID))
        print(string.format("|cFF14b8a6[LumberHUD]|r Player pos: x=%.4f y=%.4f", pos.x, pos.y))
        print(string.format("|cFF14b8a6[LumberHUD]|r Total nodes in history: %d", #HDG_Settings.lumberCoordinates))

        HDG_LumberHUD.OnLocationAdded(pos.x, pos.y, mapID)

    elseif msg == "show" then
        -- Just show the radar
        HDG_LumberHUD.Show()

    elseif msg == "testdist" then
        -- Add test nodes at exact ring distances using world coordinates
        local mapID = C_Map.GetBestMapForUnit("player")
        if not mapID then
            print("|cFFFF0000[LumberHUD]|r Cannot get player map position")
            return
        end
        local pos = C_Map.GetPlayerMapPosition(mapID, "player")
        if not pos then
            print("|cFFFF0000[LumberHUD]|r Cannot get player position (instanced?)")
            return
        end

        -- Get player world coordinates
        local pWorldX, pWorldY, pInstance = GetWorldCoordsFromMap(pos.x, pos.y, mapID)
        if not pWorldX then
            print("|cFFFF0000[LumberHUD]|r Cannot get world coordinates")
            return
        end

        -- Get map size to convert back from world to map coords
        local widthYards, heightYards = C_Map.GetMapWorldSize(mapID)
        if not widthYards or widthYards == 0 then
            print("|cFFFF0000[LumberHUD]|r Cannot get map world size")
            return
        end

        if not HDG_Settings then HDG_Settings = {} end
        if not HDG_Settings.lumberCoordinates then HDG_Settings.lumberCoordinates = {} end

        -- Place blips at ring distances (matching CONFIG.RING_DISTANCES)
        local testDistances = CONFIG.RING_DISTANCES
        local now = GetServerTime()

        print("|cFF14b8a6[LumberHUD]|r Testing distance calibration (world coord method)...")
        print(string.format("  Player world coords: X=%.1f Y=%.1f", pWorldX, pWorldY))

        for _, distYards in ipairs(testDistances) do
            -- In world coords, moving "north" typically increases Y
            -- We need to figure out which direction by testing
            -- For now, offset the world Y coordinate directly
            local targetWorldY = pWorldY + distYards

            -- Convert target world position back to map coordinates
            -- We need to find map coords that produce target world coords
            -- Since world = left - width*mapX and world = top - height*mapY
            -- We can approximate by using the ratio
            local offsetY = -distYards / heightYards -- Approximate

            local testNode = {
                x = pos.x,
                y = pos.y + offsetY,
                mapID = mapID,
                timestamp = now - 550, -- Make them green (ready) so they pulse
                lumberId = 0,
                zone = GetZoneText() or "Test"
            }
            table.insert(HDG_Settings.lumberCoordinates, testNode)

            -- Verify the distance
            local nWorldX, nWorldY = GetWorldCoordsFromMap(testNode.x, testNode.y, mapID)
            local actualDist = nWorldX and math.sqrt((nWorldX - pWorldX)^2 + (nWorldY - pWorldY)^2) or 0

            print(string.format("  Added %dy test node: actual distance=%.1fy", distYards, actualDist))
        end

        print("|cFF14b8a6[LumberHUD]|r Face north and blips should align with distance rings")
        print("|cFF14b8a6[LumberHUD]|r Commands: /lumberhud dist, /lumberhud clear")

        HDG_LumberHUD.Show()

    elseif msg == "clear" then
        -- Clear only test nodes (lumberId = 0), preserve real farming data
        if HDG_Settings and HDG_Settings.lumberCoordinates then
            local newCoords = {}
            local removed = 0
            for _, node in ipairs(HDG_Settings.lumberCoordinates) do
                if node.lumberId and node.lumberId ~= 0 then
                    table.insert(newCoords, node)
                else
                    removed = removed + 1
                end
            end
            HDG_Settings.lumberCoordinates = newCoords
            print(string.format("|cFF14b8a6[LumberHUD]|r Cleared %d test nodes, kept %d real nodes", removed, #newCoords))
        else
            print("|cFF14b8a6[LumberHUD]|r No coordinates to clear")
        end

    elseif msg == "clearall" then
        -- Clear ALL lumber coordinates (including real farming data)
        if HDG_Settings and HDG_Settings.lumberCoordinates then
            local count = #HDG_Settings.lumberCoordinates
            HDG_Settings.lumberCoordinates = {}
            print(string.format("|cFFFF9900[LumberHUD]|r Cleared ALL %d lumber coordinates (including saved farming data)", count))
        else
            print("|cFF14b8a6[LumberHUD]|r No coordinates to clear")
        end

    elseif msg == "dist" then
        -- Show live distance to all nodes (useful for verifying accuracy with TomTom)
        local mapID = C_Map.GetBestMapForUnit("player")
        if not mapID then
            print("|cFFFF0000[LumberHUD]|r Cannot get map position")
            return
        end
        local pos = C_Map.GetPlayerMapPosition(mapID, "player")
        if not pos then
            print("|cFFFF0000[LumberHUD]|r Cannot get player position")
            return
        end

        -- Get player world coordinates (same method as radar uses)
        local pWorldX, pWorldY, pInstance = GetWorldCoordsFromMap(pos.x, pos.y, mapID)
        if not pWorldX then
            print("|cFFFF0000[LumberHUD]|r Cannot get world coordinates")
            return
        end

        local history = HDG_Settings and HDG_Settings.lumberCoordinates or {}
        local now = GetServerTime()

        print("|cFF14b8a6[LumberHUD Distance Check]|r (World coord method - matches TomTom)")
        local count = 0
        for i, node in ipairs(history) do
            if node.mapID == mapID then
                local age = now - (node.timestamp or node.time or 0)
                if age < 660 then
                    -- Get node world coordinates
                    local nWorldX, nWorldY, nInstance = GetWorldCoordsFromMap(node.x, node.y, node.mapID)
                    if nWorldX and nInstance == pInstance then
                        local dx = nWorldX - pWorldX
                        local dy = nWorldY - pWorldY
                        local dist = math.sqrt(dx * dx + dy * dy)

                        local nodeType = (node.lumberId == 0) and "|cFFFFFF00TEST|r" or "|cFF00FF00REAL|r"
                        print(string.format("  %s Node %d: |cFFFFFFFF%.1f yards|r away", nodeType, i, dist))
                        count = count + 1
                        if count >= 5 then
                            print("  ... (showing first 5)")
                            break
                        end
                    end
                end
            end
        end
        if count == 0 then
            print("  No nodes in current zone")
        end

    elseif msg == "trace" then
        -- Detailed trace of the math for the nearest node
        local mapID = C_Map.GetBestMapForUnit("player")
        if not mapID then print("No mapID") return end
        local pos = C_Map.GetPlayerMapPosition(mapID, "player")
        if not pos then print("No position") return end
        local facing = GetPlayerFacing()
        if not facing then print("No facing") return end

        -- Get player world coordinates
        local pWorldX, pWorldY, pInstance = GetWorldCoordsFromMap(pos.x, pos.y, mapID)
        if not pWorldX then print("Cannot get world coordinates") return end

        local history = HDG_Settings and HDG_Settings.lumberCoordinates or {}
        local now = GetServerTime()

        print("|cFF14b8a6[LumberHUD TRACE]|r (World coord method)")
        print(string.format("  Player map: x=%.4f y=%.4f facing=%.2f rad (%.1f deg)", pos.x, pos.y, facing, math.deg(facing)))
        print(string.format("  Player world: X=%.1f Y=%.1f", pWorldX, pWorldY))

        local nodeCount = 0
        for i, node in ipairs(history) do
            if node.mapID == mapID then
                local age = now - (node.timestamp or node.time or 0)
                if age < 660 then
                    -- Get node world coordinates
                    local nWorldX, nWorldY, nInstance = GetWorldCoordsFromMap(node.x, node.y, node.mapID)
                    if nWorldX and nInstance == pInstance then
                        local dx = nWorldX - pWorldX
                        local dy = nWorldY - pWorldY
                        local dist = math.sqrt(dx * dx + dy * dy)

                        local angleToNode = math.atan2(-dx, -dy)
                        local relativeAngle = angleToNode - facing

                        while relativeAngle > math.pi do relativeAngle = relativeAngle - (2 * math.pi) end
                        while relativeAngle < -math.pi do relativeAngle = relativeAngle + (2 * math.pi) end

                        local screenRadius = (dist / CONFIG.RANGE_YARDS) * (CONFIG.RADAR_SIZE / 2)
                        local blipX = -math.sin(relativeAngle) * screenRadius
                        local blipY = math.cos(relativeAngle) * screenRadius

                        print(string.format("  Node %d:", i))
                        print(string.format("    Map: x=%.4f y=%.4f", node.x, node.y))
                        print(string.format("    World: X=%.1f Y=%.1f", nWorldX, nWorldY))
                        print(string.format("    Delta: dx=%.1f dy=%.1f dist=%.1f yards", dx, dy, dist))
                        print(string.format("    angleToNode=%.2f rad (%.1f deg)", angleToNode, math.deg(angleToNode)))
                        print(string.format("    relativeAngle=%.2f rad (%.1f deg)", relativeAngle, math.deg(relativeAngle)))
                        print(string.format("    Screen: blipX=%.1f blipY=%.1f screenRadius=%.1f", blipX, blipY, screenRadius))

                        nodeCount = nodeCount + 1
                        if nodeCount >= 3 then break end -- Only show first 3
                    end
                end
            end
        end

    elseif msg == "debug" then
        -- Show debug info
        local mapID = C_Map.GetBestMapForUnit("player")
        local pos = C_Map.GetPlayerMapPosition(mapID, "player")
        local facing = GetPlayerFacing()

        print("|cFF14b8a6[LumberHUD Debug]|r")
        print(string.format("  Player mapID: %s", tostring(mapID)))
        print(string.format("  Player pos: x=%.4f y=%.4f", pos and pos.x or 0, pos and pos.y or 0))
        print(string.format("  Player facing: %.2f rad (%.1f deg)", facing or 0, facing and math.deg(facing) or 0))
        print(string.format("  HUD visible: %s", tostring(isVisible)))
        print(string.format("  HUD enabled: %s", tostring(HDG_Settings and HDG_Settings.lumberHUDEnabled)))

        -- Show world size for current map
        if mapID then
            local widthYards, heightYards = C_Map.GetMapWorldSize(mapID)
            if widthYards and heightYards then
                print(string.format("  Map world size: %.1f x %.1f yards", widthYards, heightYards))
            end
        end

        local history = HDG_Settings and HDG_Settings.lumberCoordinates or {}
        print(string.format("  Lumber coordinates: %d entries", #history))

        local now = GetServerTime()
        local inZone = 0
        for i, node in ipairs(history) do
            if node.mapID == mapID then
                inZone = inZone + 1
                local age = now - (node.timestamp or node.time or 0)
                print(string.format("    Node %d: x=%.4f y=%.4f age=%ds", i, node.x, node.y, age))
            end
        end
        print(string.format("  Nodes in current zone: %d", inZone))

    else
        HDG_LumberHUD.Toggle()
    end
end
