--------------------------------------------------------------------------------
-- Nanami-UI: WorldMap Module (WorldMap.lua)
-- Full Nanami-UI themed world map: windowed, dark pink-purple skin,
-- hides all Blizzard decorations, custom title bar + close button,
-- brute-force tooltip theming while the map is open.
--------------------------------------------------------------------------------

SFrames = SFrames or {}
SFrames.WorldMap = {}
local WM = SFrames.WorldMap

--------------------------------------------------------------------------------
-- Nanami theme constants (matches SocialUI / ConfigUI / QuestUI)
--------------------------------------------------------------------------------
local PANEL_BACKDROP = {
    bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    tile = true, tileSize = 16, edgeSize = 14,
    insets = { left = 3, right = 3, top = 3, bottom = 3 },
}
local _A = SFrames.ActiveTheme

local TOOLTIP_BACKDROP = {
    bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    tile = true, tileSize = 16, edgeSize = 14,
    insets = { left = 3, right = 3, top = 3, bottom = 3 },
}

--------------------------------------------------------------------------------
-- Config
--------------------------------------------------------------------------------
function WM:GetConfig()
    SFramesDB = SFramesDB or {}
    if type(SFramesDB.WorldMap) ~= "table" then
        SFramesDB.WorldMap = { enabled = true, scale = 0.85 }
    end
    if SFramesDB.WorldMap.enabled == nil then
        SFramesDB.WorldMap.enabled = true
    end
    return SFramesDB.WorldMap
end

--------------------------------------------------------------------------------
-- HookScript helper
--------------------------------------------------------------------------------
local function HookScript(frame, script, fn)
    local prev = frame:GetScript(script)
    frame:SetScript(script, function(a1,a2,a3,a4,a5,a6,a7,a8,a9)
        if prev then prev(a1,a2,a3,a4,a5,a6,a7,a8,a9) end
        fn(a1,a2,a3,a4,a5,a6,a7,a8,a9)
    end)
end

--------------------------------------------------------------------------------
-- 1. Hide Blizzard Decorations
-- Called at init AND on every WorldMapFrame:OnShow to counter Blizzard resets
--------------------------------------------------------------------------------
local function NukeFrame(f)
    if not f then return end
    f:Hide()
    f:SetAlpha(0)
    f:EnableMouse(false)
    f:ClearAllPoints()
    f:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -9999, 9999)
    f:SetWidth(1)
    f:SetHeight(1)
    if not f._nanamiNuked then
        f._nanamiNuked = true
        f.Show = function(self) self:Hide() end
        if f.SetScript then
            f:SetScript("OnShow", function() this:Hide() end)
        end
    end
end

local function HideAllRegions(frame)
    if not frame or not frame.GetRegions then return end
    local regions = { frame:GetRegions() }
    for _, r in ipairs(regions) do
        if r and r.Hide then r:Hide() end
    end
end

local blizzHooked = false

local function NukeBorderChildren(border)
    if not border or not border.GetChildren then return end
    local children = { border:GetChildren() }
    for _, child in ipairs(children) do
        local name = child.GetName and child:GetName() or ""
        local isDropDown = name ~= "" and string.find(name, "DropDown")
        if not isDropDown then
            NukeFrame(child)
        end
    end
end

local function HideBlizzardDecorations()
    if BlackoutWorld then BlackoutWorld:Hide() end

    local _G = getfenv(0)

    local framesToNuke = {
        "WorldMapFrameCloseButton",
        "WorldMapFrameSizeUpButton",
        "WorldMapFrameSizeDownButton",
    }
    for _, name in ipairs(framesToNuke) do
        NukeFrame(_G[name])
    end

    HideAllRegions(WorldMapFrame)

    local borderNames = {
        "WorldMapFrameMiniBorderLeft",
        "WorldMapFrameMiniBorderRight",
    }
    for _, name in ipairs(borderNames) do
        local border = _G[name]
        if border then
            HideAllRegions(border)
            NukeBorderChildren(border)
            border:EnableMouse(false)

            if not blizzHooked then
                local oldShow = border.Show
                if oldShow then
                    border.Show = function(self)
                        oldShow(self)
                        HideAllRegions(self)
                        NukeBorderChildren(self)
                        self:EnableMouse(false)
                    end
                end
            end
        end
    end

    if WorldMapFrameTitle then
        WorldMapFrameTitle:Hide()
    end

    for _, n in ipairs({"WorldMapFrameSizeUpButton", "WorldMapFrameSizeDownButton"}) do
        local f = _G[n]
        if f then
            f:Hide()
            f:SetAlpha(0)
            f:EnableMouse(false)
            if f.ClearAllPoints then
                f:ClearAllPoints()
                f:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -9999, 9999)
            end
            if f.GetRegions then
                local regs = { f:GetRegions() }
                for _, r in ipairs(regs) do
                    if r and r.Hide then r:Hide() end
                end
            end
        end
    end

    if WorldMapFrame.GetChildren then
        local wmCh = { WorldMapFrame:GetChildren() }
        for _, c in ipairs(wmCh) do
            if c and not c._nanamiNuked and not c._nanamiSkinned then
                local cn = c.GetName and c:GetName() or ""
                if cn ~= "" and (
                    string.find(cn, "SizeUp") or
                    string.find(cn, "SizeDown") or
                    string.find(cn, "Resize") or
                    string.find(cn, "Maximize") or
                    string.find(cn, "Minimize")
                ) then
                    NukeFrame(c)
                elseif cn == "" then
                    local w = c.GetWidth and c:GetWidth() or 999
                    local h = c.GetHeight and c:GetHeight() or 999
                    if w > 0 and w < 30 and h > 0 and h < 30 then
                        NukeFrame(c)
                    end
                end
            end
        end
    end

    blizzHooked = true
end

--------------------------------------------------------------------------------
-- 2. Create Nanami Skin
--------------------------------------------------------------------------------
local skinFrame, titleBar, titleText, closeBtn, coordText, mouseCoordText, coordOverlay

local function CreateNanamiSkin()
    if skinFrame then return end

    local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"

    skinFrame = CreateFrame("Frame", "NanamiWorldMapSkin", WorldMapFrame)
    skinFrame:SetFrameLevel(WorldMapFrame:GetFrameLevel())
    skinFrame:SetAllPoints(WorldMapFrame)
    skinFrame:SetBackdrop(PANEL_BACKDROP)
    skinFrame:SetBackdropColor(unpack(_A.panelBg))
    skinFrame:SetBackdropBorderColor(unpack(_A.panelBorder))

    titleBar = CreateFrame("Frame", nil, skinFrame)
    titleBar:SetHeight(26)
    titleBar:SetPoint("TOPLEFT", skinFrame, "TOPLEFT", 4, -4)
    titleBar:SetPoint("TOPRIGHT", skinFrame, "TOPRIGHT", -4, -4)
    titleBar:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    titleBar:SetBackdropColor(unpack(_A.headerBg))
    titleBar:SetBackdropBorderColor(unpack(_A.panelBorder))

    local titleIco = SFrames:CreateIcon(titleBar, "worldmap", 14)
    titleIco:SetDrawLayer("OVERLAY")
    titleIco:SetPoint("LEFT", titleBar, "LEFT", 10, 0)
    titleIco:SetVertexColor(unpack(_A.title))

    titleText = titleBar:CreateFontString(nil, "OVERLAY")
    titleText:SetFont(font, 13, "OUTLINE")
    titleText:SetPoint("LEFT", titleIco, "RIGHT", 5, 0)
    titleText:SetTextColor(unpack(_A.title))
    titleText:SetText("世界地图")

    local hintText = titleBar:CreateFontString(nil, "OVERLAY")
    hintText:SetFont(font, 10, "")
    hintText:SetPoint("RIGHT", titleBar, "RIGHT", -40, 0)
    hintText:SetTextColor(unpack(_A.dimText))
    hintText:SetText("Ctrl+滚轮缩放 | Shift+滚轮透明度 | Ctrl+左键标记")

    closeBtn = CreateFrame("Button", nil, titleBar)
    closeBtn:SetWidth(22)
    closeBtn:SetHeight(22)
    closeBtn:SetPoint("RIGHT", titleBar, "RIGHT", -2, 0)
    closeBtn:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    closeBtn:SetBackdropColor(unpack(_A.buttonDownBg))
    closeBtn:SetBackdropBorderColor(unpack(_A.btnBorder))

    local closeIcon = closeBtn:CreateTexture(nil, "OVERLAY")
    closeIcon:SetTexture("Interface\\AddOns\\Nanami-UI\\img\\icon")
    closeIcon:SetTexCoord(0.25, 0.375, 0, 0.125)
    closeIcon:SetPoint("CENTER", 0, 0)
    closeIcon:SetWidth(14)
    closeIcon:SetHeight(14)

    closeBtn:SetScript("OnClick", function()
        WorldMapFrame:Hide()
    end)
    closeBtn:SetScript("OnEnter", function()
        this:SetBackdropColor(unpack(_A.btnHoverBg))
        this:SetBackdropBorderColor(unpack(_A.btnHoverBd))
    end)
    closeBtn:SetScript("OnLeave", function()
        this:SetBackdropColor(unpack(_A.buttonDownBg))
        this:SetBackdropBorderColor(unpack(_A.btnBorder))
    end)

    -- High-level overlay for coordinates so they render above map content
    coordOverlay = CreateFrame("Frame", "NanamiWorldMapCoordOverlay", WorldMapFrame)
    coordOverlay:SetAllPoints(skinFrame)
    coordOverlay:SetFrameStrata("TOOLTIP")
    coordOverlay:SetFrameLevel(250)
    coordOverlay:EnableMouse(false)

    coordText = coordOverlay:CreateFontString(nil, "OVERLAY")
    coordText:SetFont(font, 12, "OUTLINE")
    coordText:SetPoint("BOTTOMLEFT", skinFrame, "BOTTOMLEFT", 12, 6)
    coordText:SetTextColor(unpack(_A.title))
    coordText:SetText("")

    mouseCoordText = coordOverlay:CreateFontString(nil, "OVERLAY")
    mouseCoordText:SetFont(font, 12, "OUTLINE")
    mouseCoordText:SetPoint("BOTTOMRIGHT", skinFrame, "BOTTOMRIGHT", -12, 6)
    mouseCoordText:SetTextColor(unpack(_A.dimText))
    mouseCoordText:SetText("")
end

local function UpdateTitle()
    if not titleText then return end
    local zoneName = GetZoneText and GetZoneText() or ""
    local subZone = GetSubZoneText and GetSubZoneText() or ""
    local display = zoneName
    if subZone ~= "" and subZone ~= zoneName then
        display = zoneName .. " - " .. subZone
    end
    if display == "" then display = "世界地图" end
    titleText:SetText(display)
end

local function UpdateCoords()
    if not coordText then return end
    local x, y = GetPlayerMapPosition("player")
    if x and y and (x > 0 or y > 0) then
        coordText:SetText(string.format("玩家: %.1f, %.1f", x * 100, y * 100))
    else
        coordText:SetText("")
    end
end

local function UpdateMouseCoords()
    if not mouseCoordText or not WorldMapButton then return end
    if not WorldMapFrame or not WorldMapFrame:IsVisible() then
        mouseCoordText:SetText("")
        return
    end

    local cx, cy = GetCursorPosition()
    if not cx or not cy then
        mouseCoordText:SetText("")
        return
    end

    local scale = WorldMapButton:GetEffectiveScale()
    local left  = WorldMapButton:GetLeft()
    local top   = WorldMapButton:GetTop()
    local w     = WorldMapButton:GetWidth()
    local h     = WorldMapButton:GetHeight()

    if not left or not top or not w or not h or w == 0 or h == 0 then
        mouseCoordText:SetText("")
        return
    end

    local mx = (cx / scale - left) / w
    local my = (top - cy / scale) / h

    if mx >= 0 and mx <= 1 and my >= 0 and my <= 1 then
        mouseCoordText:SetText(string.format("鼠标: %.1f, %.1f", mx * 100, my * 100))
    else
        mouseCoordText:SetText("")
    end
end

--------------------------------------------------------------------------------
-- 3. Tooltip theming
-- SetBackdrop on scaled child frames is unreliable in WoW 1.12.
-- Approach: use a SEPARATE frame parented to UIParent (outside the
-- WorldMapFrame scale chain) positioned exactly behind whichever tooltip
-- is currently visible. This frame has its own backdrop at TOOLTIP strata.
-- Also hook WorldMapTooltip:Show to try SetBackdrop as secondary attempt.
--------------------------------------------------------------------------------
local ttBG

local TT_PAD = 4

local function CreateTooltipBG()
    if ttBG then return end
    ttBG = CreateFrame("Frame", "NanamiMapTTBG", UIParent)
    ttBG:SetFrameStrata("FULLSCREEN_DIALOG")
    ttBG:SetFrameLevel(255)
    ttBG:SetBackdrop({
        bgFile   = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 16,
        insets = { left = 4, right = 4, top = 4, bottom = 4 },
    })
    ttBG:SetBackdropColor(unpack(_A.panelBg))
    ttBG:SetBackdropBorderColor(unpack(_A.panelBorder))
    ttBG:Hide()
end

local function PositionTTBG(tip)
    if not ttBG then return end
    if not tip or not tip:IsVisible() then
        ttBG:Hide()
        return
    end

    local l = tip:GetLeft()
    local r = tip:GetRight()
    local t = tip:GetTop()
    local b = tip:GetBottom()
    if not l or not r or not t or not b then
        ttBG:Hide()
        return
    end

    local tipScale = tip:GetEffectiveScale()
    local bgScale  = ttBG:GetEffectiveScale()
    local s = tipScale / bgScale

    ttBG:ClearAllPoints()
    ttBG:SetPoint("TOPLEFT",     UIParent, "BOTTOMLEFT", l * s - TT_PAD, t * s + TT_PAD)
    ttBG:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMLEFT", r * s + TT_PAD, b * s - TT_PAD)
    ttBG:Show()
end

local function HideTTBG()
    if ttBG then ttBG:Hide() end
end

local function RaiseTooltipStrata(tip)
    if not tip then return end
    tip:SetFrameStrata("TOOLTIP")
end

local function HookTooltipShow(tip)
    if not tip or tip._nanamiShowHooked then return end
    tip._nanamiShowHooked = true
    local origShow = tip.Show
    tip.Show = function(self)
        origShow(self)
        if WorldMapFrame and WorldMapFrame:IsVisible() then
            self:SetFrameStrata("TOOLTIP")
            self:SetBackdrop(TOOLTIP_BACKDROP)
            self:SetBackdropColor(unpack(_A.panelBg))
            self:SetBackdropBorderColor(unpack(_A.panelBorder))
        end
    end
end

local RepositionWorldMapControls

--------------------------------------------------------------------------------
-- 4. Per-frame updater
--------------------------------------------------------------------------------
local elapsed = 0
local function OnFrameUpdate()
    if not WorldMapFrame or not WorldMapFrame:IsShown() then
        HideTTBG()
        return
    end

    local activeTip = nil
    if WorldMapTooltip and WorldMapTooltip:IsVisible() then
        activeTip = WorldMapTooltip
    elseif GameTooltip and GameTooltip:IsVisible() then
        activeTip = GameTooltip
    end

    if activeTip then
        PositionTTBG(activeTip)
        RaiseTooltipStrata(activeTip)
        activeTip:SetBackdrop(TOOLTIP_BACKDROP)
        activeTip:SetBackdropColor(unpack(_A.panelBg))
        activeTip:SetBackdropBorderColor(unpack(_A.panelBorder))
    else
        HideTTBG()
    end

    UpdateMouseCoords()

    elapsed = elapsed + (arg1 or 0)
    if elapsed > 0.1 then
        elapsed = 0
        UpdateTitle()
        UpdateCoords()
        HideBlizzardDecorations()
        RepositionWorldMapControls()
    end
end

--------------------------------------------------------------------------------
-- 5. Layout
--------------------------------------------------------------------------------
local function ApplyLayout(cfg)
    local s = cfg.scale or 0.85
    WorldMapFrame:SetMovable(true)
    WorldMapFrame:EnableMouse(true)
    WorldMapFrame:SetScale(s)
    WorldMapFrame:ClearAllPoints()
    WorldMapFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 30)
    WorldMapFrame:SetWidth(WorldMapButton:GetWidth() + 12)
    WorldMapFrame:SetHeight(WorldMapButton:GetHeight() + 40)

    WorldMapDetailFrame:ClearAllPoints()
    WorldMapDetailFrame:SetPoint("TOPLEFT", WorldMapFrame, "TOPLEFT", 6, -32)
    WorldMapButton:ClearAllPoints()
    WorldMapButton:SetPoint("TOPLEFT", WorldMapDetailFrame, "TOPLEFT", 0, 0)
    WorldMapButton:SetPoint("BOTTOMRIGHT", WorldMapDetailFrame, "BOTTOMRIGHT", 0, 0)

    if BlackoutWorld then
        BlackoutWorld:Hide()
    end
end

local blizzLabelsKilled = false
local function KillBlizzardDropDownLabels()
    if blizzLabelsKilled then return end
    local _G = getfenv(0)
    local targets = {
        _G["WorldMapContinentDropDown"],
        _G["WorldMapZoneDropDown"],
        _G["WorldMapZoneMinimapDropDown"],
        _G["WorldMapFrameMiniBorderLeft"],
        _G["WorldMapFrameMiniBorderRight"],
        WorldMapFrame,
    }
    local found = 0
    for _, parent in ipairs(targets) do
        if parent and parent.GetRegions then
            local regions = { parent:GetRegions() }
            for _, r in ipairs(regions) do
                if r and not r._nanamiCustom and r.GetObjectType
                   and r:GetObjectType() == "FontString" then
                    local t = r.GetText and r:GetText() or ""
                    if t == "大陆" or t == "地区"
                       or t == "Continent" or t == "Zone" then
                        r:SetText("")
                        r:SetAlpha(0)
                        r:Hide()
                        r.Show = function() end
                        r.SetText = function() end
                        r.SetAlpha = function() end
                        found = found + 1
                    end
                end
            end
        end
    end
    for _, n in ipairs({"WorldMapContinentDropDownLabel", "WorldMapZoneDropDownLabel", "WorldMapZoneMinimapDropDownLabel"}) do
        local lbl = _G[n]
        if lbl then
            lbl:SetAlpha(0)
            lbl:Hide()
            lbl.Show = function() end
            lbl.SetText = function() end
            found = found + 1
        end
    end
    if found > 0 then blizzLabelsKilled = true end
end

RepositionWorldMapControls = function()
    if not skinFrame or not titleBar then return end
    local _G = getfenv(0)

    if not blizzLabelsKilled then KillBlizzardDropDownLabels() end

    if WorldMapDetailFrame then
        WorldMapDetailFrame:ClearAllPoints()
        WorldMapDetailFrame:SetPoint("TOPLEFT", WorldMapFrame, "TOPLEFT", 6, -32)
    end
    if WorldMapButton and WorldMapDetailFrame then
        WorldMapButton:ClearAllPoints()
        WorldMapButton:SetPoint("TOPLEFT", WorldMapDetailFrame, "TOPLEFT", 0, 0)
        WorldMapButton:SetPoint("BOTTOMRIGHT", WorldMapDetailFrame, "BOTTOMRIGHT", 0, 0)
    end

    local cDD = _G["WorldMapContinentDropDown"]
    local zDD = _G["WorldMapZoneDropDown"]
    local zBtn = _G["WorldMapZoomOutButton"]

    if cDD then
        cDD:ClearAllPoints()
        cDD:SetPoint("TOPLEFT", skinFrame, "TOPLEFT", 200, -3)
    end
    if zDD then
        zDD:ClearAllPoints()
        zDD:SetPoint("LEFT", cDD, "RIGHT", 0, 0)
    end
    if zBtn then
        zBtn:ClearAllPoints()
        zBtn:SetPoint("LEFT", zDD, "RIGHT", -14, 2)
        zBtn:SetWidth(48)
        zBtn:SetHeight(20)
    end
end

--------------------------------------------------------------------------------
-- 6. Window mode setup
--------------------------------------------------------------------------------
function WM:SetupWindowMode()
    if Cartographer or METAMAP_TITLE then return end

    local cfg = self:GetConfig()

    table.insert(UISpecialFrames, "WorldMapFrame")

    local _G = getfenv(0)
    _G.ToggleWorldMap = function()
        if WorldMapFrame:IsShown() then
            WorldMapFrame:Hide()
        else
            WorldMapFrame:Show()
        end
    end

    UIPanelWindows["WorldMapFrame"] = { area = "center" }

    HookScript(WorldMapFrame, "OnShow", function()
        this:EnableKeyboard(false)
        this:EnableMouseWheel(1)
        local c = WM:GetConfig()
        WorldMapFrame:SetScale(c.scale or 0.85)
        WorldMapFrame:SetAlpha(1)
        WorldMapFrame:SetFrameStrata("FULLSCREEN_DIALOG")
        if BlackoutWorld then BlackoutWorld:Hide() end
        HideBlizzardDecorations()
        RepositionWorldMapControls()
    end)

    HookScript(WorldMapFrame, "OnHide", function()
        HideTTBG()
    end)

    HookScript(WorldMapFrame, "OnMouseWheel", function()
        if IsShiftKeyDown() then
            local a = WorldMapFrame:GetAlpha() + arg1 / 10
            if a < 0.2 then a = 0.2 end
            if a > 1 then a = 1 end
            WorldMapFrame:SetAlpha(a)
        elseif IsControlKeyDown() then
            local oldScale = WorldMapFrame:GetScale()
            local newScale = oldScale + arg1 / 10
            if newScale < 0.4 then newScale = 0.4 end
            if newScale > 1.5 then newScale = 1.5 end

            local eff = WorldMapFrame:GetEffectiveScale()
            local fw  = WorldMapFrame:GetWidth()
            local fh  = WorldMapFrame:GetHeight()
            local fl  = WorldMapFrame:GetLeft()
            local ft  = WorldMapFrame:GetTop()

            if fl and ft and fw and fh and eff and eff > 0 then
                local scrCX = (fl + fw / 2) * eff
                local scrCY = (ft - fh / 2) * eff
                WorldMapFrame:SetScale(newScale)
                local newEff = newScale * eff / oldScale
                WorldMapFrame:ClearAllPoints()
                WorldMapFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT",
                    scrCX / newEff - fw / 2,
                    scrCY / newEff + fh / 2)
            else
                WorldMapFrame:SetScale(newScale)
            end

            local c = WM:GetConfig()
            c.scale = newScale
        end
    end)

    HookScript(WorldMapFrame, "OnMouseDown", function()
        WorldMapFrame:StartMoving()
    end)

    HookScript(WorldMapFrame, "OnMouseUp", function()
        WorldMapFrame:StopMovingOrSizing()
    end)

    ApplyLayout(cfg)

    local _G2 = getfenv(0)
    local origMaximize = _G2.WorldMapFrame_Maximize
    local origMinimize = _G2.WorldMapFrame_Minimize
    _G2.WorldMapFrame_Maximize = function()
        if origMaximize then origMaximize() end
        ApplyLayout(WM:GetConfig())
        HideBlizzardDecorations()
        RepositionWorldMapControls()
    end
    _G2.WorldMapFrame_Minimize = function()
        if origMinimize then origMinimize() end
        ApplyLayout(WM:GetConfig())
        HideBlizzardDecorations()
        RepositionWorldMapControls()
    end
end

--------------------------------------------------------------------------------
-- 7. Skin World Map Native Controls (Dropdowns, Buttons)
--------------------------------------------------------------------------------

local function SkinDropDown(dropdownName)
    local _G = getfenv(0)
    local dd = _G[dropdownName]
    if not dd or dd._nanamiSkinned then return end
    dd._nanamiSkinned = true

    for _, suffix in ipairs({ "Left", "Middle", "Right" }) do
        local tex = _G[dropdownName .. suffix]
        if tex then tex:SetAlpha(0) end
    end

    local bg = CreateFrame("Frame", nil, dd)
    bg:SetPoint("TOPLEFT", dd, "TOPLEFT", 16, -2)
    bg:SetPoint("BOTTOMRIGHT", dd, "BOTTOMRIGHT", -16, 6)
    bg:SetFrameLevel(dd:GetFrameLevel())
    bg:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    bg:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
    bg:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])
    dd._nanamiBG = bg

    local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"

    local text = _G[dropdownName .. "Text"]
    if text then
        text:SetFont(font, 11, "OUTLINE")
        text:SetTextColor(_A.btnText[1], _A.btnText[2], _A.btnText[3])
    end

    local origLabel = _G[dropdownName .. "Label"]
    local labelText = ""
    if origLabel then
        labelText = origLabel:GetText() or ""
        origLabel:Hide()
        origLabel:SetAlpha(0)
        origLabel:SetText("")
        origLabel:ClearAllPoints()
        origLabel:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -9999, 9999)
        origLabel.Show = function() end
        origLabel.SetText = function() end
        origLabel.SetPoint = function() end
        origLabel.ClearAllPoints = function() end
    end
    if labelText == "" then
        if string.find(dropdownName, "Continent") then labelText = "大陆"
        elseif string.find(dropdownName, "ZoneMinimap") then labelText = ""
        elseif string.find(dropdownName, "Zone") then labelText = "地区"
        end
    end
    if labelText ~= "" then
        local newLabel = bg:CreateFontString(nil, "OVERLAY")
        newLabel._nanamiCustom = true
        newLabel:SetFont(font, 11, "OUTLINE")
        newLabel:SetTextColor(_A.dimText[1], _A.dimText[2], _A.dimText[3])
        newLabel:SetPoint("RIGHT", bg, "LEFT", -4, 0)
        newLabel:SetText(labelText)
        dd._nanamiLabel = newLabel
    end

    local btn = _G[dropdownName .. "Button"]
    if btn then
        local nt = btn:GetNormalTexture()
        if nt then nt:SetVertexColor(_A.title[1], _A.title[2], _A.title[3]) end
        local pt = btn:GetPushedTexture()
        if pt then pt:SetVertexColor(_A.title[1], _A.title[2], _A.title[3]) end
        local dt = btn:GetDisabledTexture()
        if dt then dt:SetVertexColor(_A.dimText[1], _A.dimText[2], _A.dimText[3]) end
        local ht = btn:GetHighlightTexture()
        if ht then ht:SetVertexColor(_A.title[1], _A.title[2], _A.title[3], 0.3) end
    end
end

local function SkinDropDownLists()
    local _G = getfenv(0)
    for i = 1, 3 do
        local listName = "DropDownList" .. i
        local backdrop = _G[listName .. "Backdrop"]
        if backdrop then
            backdrop:SetBackdrop(PANEL_BACKDROP)
            backdrop:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], _A.panelBg[4])
            backdrop:SetBackdropBorderColor(_A.panelBorder[1], _A.panelBorder[2], _A.panelBorder[3], _A.panelBorder[4])
        end
        local menuBackdrop = _G[listName .. "MenuBackdrop"]
        if menuBackdrop then
            menuBackdrop:SetBackdrop(PANEL_BACKDROP)
            menuBackdrop:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], _A.panelBg[4])
            menuBackdrop:SetBackdropBorderColor(_A.panelBorder[1], _A.panelBorder[2], _A.panelBorder[3], _A.panelBorder[4])
        end
        for j = 1, 30 do
            local hlTex = _G[listName .. "Button" .. j .. "Highlight"]
            if hlTex and hlTex.SetVertexColor then
                hlTex:SetVertexColor(_A.panelBorder[1], _A.panelBorder[2], _A.panelBorder[3], 0.35)
            end
        end
    end
end

local function SkinNativeButton(btnName, labelOverride)
    local _G = getfenv(0)
    local btn = _G[btnName]
    if not btn or btn._nanamiSkinned then return end
    btn._nanamiSkinned = true

    local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"

    local regions = { btn:GetRegions() }
    for _, r in ipairs(regions) do
        if r and r.GetObjectType and r:GetObjectType() == "Texture" then
            local tex = r:GetTexture()
            if tex and type(tex) == "string" and (string.find(tex, "UI%-Panel") or string.find(tex, "UI%-DialogBox")) then
                r:Hide()
            end
        end
    end

    local nt = btn.GetNormalTexture and btn:GetNormalTexture()
    if nt then nt:SetAlpha(0) end
    local pt = btn.GetPushedTexture and btn:GetPushedTexture()
    if pt then pt:SetAlpha(0) end
    local ht = btn.GetHighlightTexture and btn:GetHighlightTexture()
    if ht then ht:SetAlpha(0) end

    btn:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    btn:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
    btn:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])

    if labelOverride then
        local fs = btn:GetFontString() or btn:CreateFontString(nil, "OVERLAY")
        fs:SetFont(font, 11, "OUTLINE")
        fs:SetText(labelOverride)
        fs:SetTextColor(_A.btnText[1], _A.btnText[2], _A.btnText[3])
        fs:SetPoint("CENTER", 0, 0)
    elseif btn.GetFontString and btn:GetFontString() then
        local fs = btn:GetFontString()
        fs:SetFont(font, 11, "OUTLINE")
        fs:SetTextColor(_A.btnText[1], _A.btnText[2], _A.btnText[3])
    end

    local prevEnter = btn:GetScript("OnEnter")
    local prevLeave = btn:GetScript("OnLeave")
    btn:SetScript("OnEnter", function()
        if prevEnter then prevEnter() end
        this:SetBackdropColor(_A.btnHoverBg[1], _A.btnHoverBg[2], _A.btnHoverBg[3], _A.btnHoverBg[4])
        this:SetBackdropBorderColor(_A.btnHoverBd[1], _A.btnHoverBd[2], _A.btnHoverBd[3], _A.btnHoverBd[4])
    end)
    btn:SetScript("OnLeave", function()
        if prevLeave then prevLeave() end
        this:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
        this:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])
    end)
end

local ddListHooked = false
local function SkinWorldMapControls()
    SkinDropDown("WorldMapContinentDropDown")
    SkinDropDown("WorldMapZoneDropDown")
    SkinDropDown("WorldMapZoneMinimapDropDown")

    SkinNativeButton("WorldMapZoomOutButton", "缩小")

    SkinDropDownLists()
    RepositionWorldMapControls()

    if not ddListHooked then
        ddListHooked = true
        local _G = getfenv(0)
        for i = 1, 3 do
            local list = _G["DropDownList" .. i]
            if list then
                local oldShow = list.Show
                if oldShow then
                    list.Show = function(self)
                        oldShow(self)
                        SkinDropDownLists()
                    end
                end
            end
        end
    end
end

--------------------------------------------------------------------------------
-- 8. Waypoint / Map Pin System
--------------------------------------------------------------------------------
local waypoint = { active = false, continent = 0, zone = 0, x = 0, y = 0, name = "" }
local pinFrame, pinTexture, pinLabel, pinShareBtn, pinClearBtn

local function GetMapCursorPos()
    if not WorldMapButton then return nil, nil end
    local cx, cy = GetCursorPosition()
    if not cx or not cy then return nil, nil end
    local scale = WorldMapButton:GetEffectiveScale()
    local left  = WorldMapButton:GetLeft()
    local top   = WorldMapButton:GetTop()
    local w     = WorldMapButton:GetWidth()
    local h     = WorldMapButton:GetHeight()
    if not left or not top or not w or not h or w == 0 or h == 0 then return nil, nil end
    local mx = (cx / scale - left) / w
    local my = (top - cy / scale) / h
    if mx >= 0 and mx <= 1 and my >= 0 and my <= 1 then
        return mx, my
    end
    return nil, nil
end

local function GetCurrentMapName()
    local _G = getfenv(0)
    local zoneText = _G["WorldMapFrameAreaLabel"]
    if zoneText and zoneText.GetText then
        local t = zoneText:GetText()
        if t and t ~= "" then return t end
    end
    local c = GetCurrentMapContinent and GetCurrentMapContinent() or 0
    local z = GetCurrentMapZone and GetCurrentMapZone() or 0
    if z > 0 and GetMapZones then
        local zones = { GetMapZones(c) }
        if zones[z] then return zones[z] end
    end
    if c > 0 and GetMapContinents then
        local continents = { GetMapContinents() }
        if continents[c] then return continents[c] end
    end
    return "未知区域"
end

local function CreateWaypointPin()
    if pinFrame then return end
    local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"

    pinFrame = CreateFrame("Frame", "NanamiWorldMapPin", WorldMapButton)
    pinFrame:SetWidth(24)
    pinFrame:SetHeight(38)
    pinFrame:SetFrameStrata("FULLSCREEN_DIALOG")
    pinFrame:SetFrameLevel(200)

    local stem = pinFrame:CreateTexture(nil, "ARTWORK")
    stem:SetTexture("Interface\\Buttons\\WHITE8X8")
    stem:SetWidth(2)
    stem:SetHeight(14)
    stem:SetPoint("BOTTOM", pinFrame, "BOTTOM", 0, 0)
    stem:SetVertexColor(_A.panelBorder[1], _A.panelBorder[2], _A.panelBorder[3], 0.85)

    local glow = pinFrame:CreateTexture(nil, "BACKGROUND")
    glow:SetTexture("Interface\\Buttons\\WHITE8X8")
    glow:SetWidth(20)
    glow:SetHeight(20)
    glow:SetPoint("BOTTOM", stem, "TOP", 0, -6)
    glow:SetVertexColor(_A.accent[1], _A.accent[2], _A.accent[3], 0.30)

    pinTexture = pinFrame:CreateTexture(nil, "ARTWORK")
    pinTexture:SetTexture("Interface\\Buttons\\WHITE8X8")
    pinTexture:SetWidth(12)
    pinTexture:SetHeight(12)
    pinTexture:SetPoint("BOTTOM", stem, "TOP", 0, -1)
    pinTexture:SetVertexColor(_A.accent[1], _A.accent[2], _A.accent[3], 1)

    local dot = pinFrame:CreateTexture(nil, "OVERLAY")
    dot:SetTexture("Interface\\Buttons\\WHITE8X8")
    dot:SetWidth(4)
    dot:SetHeight(4)
    dot:SetPoint("CENTER", pinTexture, "CENTER", 0, 0)
    dot:SetVertexColor(_A.title[1], _A.title[2], _A.title[3], 1)

    pinLabel = pinFrame:CreateFontString(nil, "OVERLAY")
    pinLabel:SetFont(font, 11, "OUTLINE")
    pinLabel:SetPoint("TOP", pinFrame, "BOTTOM", 0, -2)
    pinLabel:SetTextColor(_A.title[1], _A.title[2], _A.title[3])
    pinLabel:SetText("")

    local btnW, btnH = 52, 18

    pinShareBtn = CreateFrame("Button", nil, pinFrame)
    pinShareBtn:SetWidth(btnW)
    pinShareBtn:SetHeight(btnH)
    pinShareBtn:SetPoint("TOPLEFT", pinFrame, "BOTTOMLEFT", -10, -8)
    pinShareBtn:SetBackdrop(PANEL_BACKDROP)
    pinShareBtn:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
    pinShareBtn:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])
    local shareFS = pinShareBtn:CreateFontString(nil, "OVERLAY")
    shareFS:SetFont(font, 10, "OUTLINE")
    shareFS:SetPoint("CENTER", 0, 0)
    shareFS:SetText("分享")
    shareFS:SetTextColor(_A.btnText[1], _A.btnText[2], _A.btnText[3])
    pinShareBtn:SetScript("OnEnter", function()
        this:SetBackdropColor(_A.btnHoverBg[1], _A.btnHoverBg[2], _A.btnHoverBg[3], _A.btnHoverBg[4])
    end)
    pinShareBtn:SetScript("OnLeave", function()
        this:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
    end)
    pinShareBtn:SetScript("OnClick", function()
        WM:ShareWaypoint()
    end)

    pinClearBtn = CreateFrame("Button", nil, pinFrame)
    pinClearBtn:SetWidth(btnW)
    pinClearBtn:SetHeight(btnH)
    pinClearBtn:SetPoint("LEFT", pinShareBtn, "RIGHT", 4, 0)
    pinClearBtn:SetBackdrop(PANEL_BACKDROP)
    pinClearBtn:SetBackdropColor(_A.buttonDownBg[1], _A.buttonDownBg[2], _A.buttonDownBg[3], _A.buttonDownBg[4])
    pinClearBtn:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])
    local clearFS = pinClearBtn:CreateFontString(nil, "OVERLAY")
    clearFS:SetFont(font, 10, "OUTLINE")
    clearFS:SetPoint("CENTER", 0, 0)
    clearFS:SetText("清除")
    clearFS:SetTextColor(_A.dimText[1], _A.dimText[2], _A.dimText[3])
    pinClearBtn:SetScript("OnEnter", function()
        this:SetBackdropColor(_A.btnHoverBg[1], _A.btnHoverBg[2], _A.btnHoverBg[3], _A.btnHoverBg[4])
    end)
    pinClearBtn:SetScript("OnLeave", function()
        this:SetBackdropColor(_A.buttonDownBg[1], _A.buttonDownBg[2], _A.buttonDownBg[3], _A.buttonDownBg[4])
    end)
    pinClearBtn:SetScript("OnClick", function()
        WM:ClearWaypoint()
    end)

    pinFrame:Hide()
end

local function UpdatePinPosition()
    if not pinFrame or not waypoint.active then
        if pinFrame then pinFrame:Hide() end
        return
    end
    if not WorldMapButton or not WorldMapFrame:IsVisible() then
        pinFrame:Hide()
        return
    end
    local c = GetCurrentMapContinent and GetCurrentMapContinent() or 0
    local z = GetCurrentMapZone and GetCurrentMapZone() or 0
    if c ~= waypoint.continent or z ~= waypoint.zone then
        pinFrame:Hide()
        return
    end
    local w = WorldMapButton:GetWidth()
    local h = WorldMapButton:GetHeight()
    if not w or not h or w == 0 or h == 0 then
        pinFrame:Hide()
        return
    end
    pinFrame:ClearAllPoints()
    pinFrame:SetPoint("BOTTOM", WorldMapButton, "TOPLEFT", w * waypoint.x, -h * waypoint.y)
    pinFrame:Show()
end

function WM:SetWaypoint(continent, zone, x, y, name)
    waypoint.active = true
    waypoint.continent = continent
    waypoint.zone = zone
    waypoint.x = x
    waypoint.y = y
    waypoint.name = name or "标记点"

    CreateWaypointPin()
    local xPct = string.format("%.1f", x * 100)
    local yPct = string.format("%.1f", y * 100)
    pinLabel:SetText(waypoint.name .. " (" .. xPct .. ", " .. yPct .. ")")
    UpdatePinPosition()

    if SFrames and SFrames.Print then
        SFrames:Print("地图标记已放置: " .. waypoint.name .. " (" .. xPct .. ", " .. yPct .. ")")
    end
end

function WM:ClearWaypoint()
    waypoint.active = false
    waypoint.continent = 0
    waypoint.zone = 0
    waypoint.x = 0
    waypoint.y = 0
    waypoint.name = ""
    if pinFrame then pinFrame:Hide() end
end

function WM:ShareWaypoint()
    if not waypoint.active then return end
    local xPct = string.format("%.1f", waypoint.x * 100)
    local yPct = string.format("%.1f", waypoint.y * 100)
    local shareText = "<npin:" .. waypoint.continent .. ":" .. waypoint.zone .. ":" .. xPct .. ":" .. yPct .. ":" .. waypoint.name .. ">"
    if ChatFrameEditBox then
        if not ChatFrameEditBox:IsVisible() then
            ChatFrameEditBox:Show()
        end
        ChatFrameEditBox:SetFocus()
        ChatFrameEditBox:SetText(shareText .. " " .. waypoint.name .. " (" .. xPct .. ", " .. yPct .. ")")
    end
end

function WM:HandleWaypointLink(data)
    local _, _, cs, zs, xs, ys = string.find(data, "^(%d+):(%d+):([%d%.]+):([%d%.]+)")
    if not cs then return end
    local c = tonumber(cs) or 0
    local z = tonumber(zs) or 0
    local x = (tonumber(xs) or 0) / 100
    local y = (tonumber(ys) or 0) / 100

    local name = "标记点"
    if z > 0 and GetMapZones then
        local zones = { GetMapZones(c) }
        if zones[z] then name = zones[z] end
    elseif c > 0 and GetMapContinents then
        local continents = { GetMapContinents() }
        if continents[c] then name = continents[c] end
    end

    if not WorldMapFrame:IsVisible() then
        WorldMapFrame:Show()
    end

    self:SetWaypoint(c, z, x, y, name)

    local pending = { continent = c, zone = z, x = x, y = y, name = name }
    local timer = CreateFrame("Frame")
    local waited = 0
    timer:SetScript("OnUpdate", function()
        waited = waited + (arg1 or 0)
        if waited >= 0.05 then
            timer:SetScript("OnUpdate", nil)
            if SetMapZoom then
                SetMapZoom(pending.continent, pending.zone)
            end
            WM:SetWaypoint(pending.continent, pending.zone, pending.x, pending.y, pending.name)
        end
    end)
end

--------------------------------------------------------------------------------
-- 8b. Darkmoon Faire Map Markers (暗月马戏团世界地图标记)
--------------------------------------------------------------------------------
local DMF_ICON    = "Interface\\Icons\\INV_Misc_Orb_02"
local DMF_SIZE      = 24
local DMF_SIZE_CONT = 16
local DMF_LOCATIONS = {
    {   zone = "ElwynnForest",  cont = 2,
        zx = 0.41, zy = 0.69,          -- zone-level coordinates
        cx = 0.453, cy = 0.721,        -- continent-level coordinates (Eastern Kingdoms)
        name = "闪金镇", fullName = "闪金镇 (艾尔文森林)",
    },
    {   zone = "Mulgore",       cont = 1,
        zx = 0.36, zy = 0.38,          -- zone-level coordinates
        cx = 0.463, cy = 0.595,        -- continent-level coordinates (Kalimdor)
        name = "莫高雷", fullName = "莫高雷",
    },
}
local DMF_REF_LOC = 1  -- ref Monday (2026-03-09) week → Goldshire
local dmfPins = {}
local dmfPulse = 0
local dmfRefJDN = nil

local function DiscoverDmfZoneIndices()
    local savedC = GetCurrentMapContinent and GetCurrentMapContinent() or 0
    local savedZ = GetCurrentMapZone and GetCurrentMapZone() or 0
    for i = 1, 2 do
        local loc = DMF_LOCATIONS[i]
        if not loc.zoneIdx then
            local target = string.lower(loc.zone)
            local zones = { GetMapZones(loc.cont) }
            for idx = 1, table.getn(zones) do
                SetMapZoom(loc.cont, idx)
                local mf = GetMapInfo and GetMapInfo() or ""
                if mf ~= "" then
                    if mf == loc.zone then
                        loc.zoneIdx = idx
                        break
                    elseif string.find(string.lower(mf), target) then
                        loc.zoneIdx = idx
                        loc.zone = mf
                        break
                    elseif string.find(target, string.lower(mf)) then
                        loc.zoneIdx = idx
                        loc.zone = mf
                        break
                    end
                end
            end
        end
    end
    if savedZ > 0 then
        SetMapZoom(savedC, savedZ)
    elseif savedC > 0 then
        SetMapZoom(savedC, 0)
    else
        if SetMapToCurrentZone then SetMapToCurrentZone() end
    end
end

local function DmfJDN(y, m, d)
    local a  = math.floor((14 - m) / 12)
    local y2 = y + 4800 - a
    local m2 = m + 12 * a - 3
    return d + math.floor((153 * m2 + 2) / 5) + 365 * y2
         + math.floor(y2 / 4) - math.floor(y2 / 100) + math.floor(y2 / 400) - 32045
end

local function GetCETJDN()
    local ts = time()
    local daysSinceEpoch = math.floor(ts / 86400)
    local utcJDN = 2440588 + daysSinceEpoch  -- Jan 1 1970 = JDN 2440588
    local utcHour = math.mod(math.floor(ts / 3600), 24)

    local ok, utc = pcall(date, "!*t")
    local utcYear
    if ok and utc and utc.year then
        utcYear = utc.year
    else
        local lt = date("*t")
        utcYear = lt.year
    end

    local mar31dow = math.mod(DmfJDN(utcYear, 3, 31), 7)
    local lastSunMar = 31 - math.mod(mar31dow + 1, 7)
    local oct31dow = math.mod(DmfJDN(utcYear, 10, 31), 7)
    local lastSunOct = 31 - math.mod(oct31dow + 1, 7)

    local utcMonth = math.floor((daysSinceEpoch - 10957) / 30.44) + 1
    if ok and utc and utc.month then utcMonth = utc.month end
    local utcDay = 0
    if ok and utc then utcDay = utc.day or 0 end

    local afterSpring = (utcMonth > 3)
        or (utcMonth == 3 and utcDay > lastSunMar)
        or (utcMonth == 3 and utcDay == lastSunMar and utcHour >= 1)
    local beforeAutumn = (utcMonth < 10)
        or (utcMonth == 10 and utcDay < lastSunOct)
        or (utcMonth == 10 and utcDay == lastSunOct and utcHour < 1)

    local offset = (afterSpring and beforeAutumn) and 2 or 1 -- CEST=2, CET=1
    local cetJDN = utcJDN
    if utcHour + offset >= 24 then cetJDN = cetJDN + 1 end
    return cetJDN
end

local function GetDmfSchedule()
    local todayJDN = GetCETJDN()
    local dow = math.mod(todayJDN, 7) -- 0=Mon 1=Tue 2=Wed 3=Thu 4=Fri 5=Sat 6=Sun
    local isWed = (dow == 2)

    -- Week runs Sun(6)-Sat(5); ref Sunday March 8 = Goldshire
    if not dmfRefJDN then dmfRefJDN = DmfJDN(2026, 3, 8) end
    local daysSinceSun = math.mod(dow + 1, 7) -- Sun=0 Mon=1 Tue=2 Wed=3 Thu=4 Fri=5 Sat=6
    local thisSunJDN = todayJDN - daysSinceSun
    local weeksDiff = math.floor((thisSunJDN - dmfRefJDN) / 7)
    if weeksDiff < 0 then weeksDiff = -weeksDiff end
    local ai = math.mod(weeksDiff, 2) == 0 and DMF_REF_LOC or (3 - DMF_REF_LOC)

    local daysLeft = 6 - daysSinceSun   -- days until Saturday (0 on Sat itself)
    local daysUntilNext = daysLeft + 1   -- days until next Sunday

    return ai, isWed, daysLeft, daysUntilNext
end

local function CreateDmfPin(i)
    if dmfPins[i] then return end
    if not WorldMapButton then return end
    local f = CreateFrame("Button", "NanamiDMFPin" .. i, WorldMapButton)
    f:SetWidth(DMF_SIZE); f:SetHeight(DMF_SIZE)
    f:SetFrameStrata("FULLSCREEN_DIALOG")
    f:SetFrameLevel(200)

    local ico = f:CreateTexture(nil, "OVERLAY")
    ico:SetTexture(DMF_ICON)
    ico:SetWidth(DMF_SIZE); ico:SetHeight(DMF_SIZE)
    ico:SetPoint("CENTER", f, "CENTER", 0, 0)
    f.icon = ico

    local gl = f:CreateTexture(nil, "OVERLAY")
    gl:SetTexture("Interface\\Buttons\\UI-ActionButton-Border")
    gl:SetBlendMode("ADD")
    gl:SetWidth(DMF_SIZE * 2.2); gl:SetHeight(DMF_SIZE * 2.2)
    gl:SetPoint("CENTER", f, "CENTER", 0, 0)
    local _glc = SFrames.ActiveTheme and SFrames.ActiveTheme.accentLight or { 1, 0.84, 0 }
    gl:SetVertexColor(_glc[1], _glc[2], _glc[3], 0.35)
    f.glow = gl

    f.locIdx = i
    f:SetScript("OnEnter", function()
        local loc   = DMF_LOCATIONS[this.locIdx]
        local other = DMF_LOCATIONS[3 - this.locIdx]
        local ai, iw, dl, ds = GetDmfSchedule()
        local here = (ai == this.locIdx)
        GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
        GameTooltip:AddLine("暗月马戏团", 1, 0.84, 0)
        GameTooltip:AddLine(loc.fullName, 0.7, 0.7, 0.7)
        GameTooltip:AddLine(" ")
        if here then
            if iw then
                GameTooltip:AddLine("今天是马戏团休息日 (周三)", 1, 0.3, 0.3)
                GameTooltip:AddLine("马戏团在此处，明天恢复营业", 0.4, 1, 0.4)
                GameTooltip:AddLine(" ")
                GameTooltip:AddLine("本轮还剩 " .. dl .. " 天 (周六结束)", 0.8, 0.8, 0.8)
            else
                GameTooltip:AddLine("马戏团正在此处营业中!", 0.4, 1, 0.4)
                GameTooltip:AddLine(" ")
                if dl > 1 then
                    GameTooltip:AddLine("剩余 " .. dl .. " 天 (周六结束)", 0.8, 0.8, 0.8)
                elseif dl == 1 then
                    GameTooltip:AddLine("明天是本轮最后一天", 1, 0.84, 0)
                else
                    GameTooltip:AddLine("今天是本轮最后一天!", 1, 0.84, 0)
                    GameTooltip:AddLine("之后将转移至 " .. other.fullName, 0.6, 0.8, 1)
                end
            end
        else
            GameTooltip:AddLine("马戏团目前不在此处", 0.6, 0.6, 0.6)
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("当前位置: " .. other.fullName, 0.6, 0.8, 1)
            if iw then
                GameTooltip:AddLine("今天对方休息，" .. ds .. " 天后来此", 1, 0.84, 0)
            else
                GameTooltip:AddLine(ds .. " 天后到来", 1, 0.84, 0)
            end
        end
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("点击打开Buff指引", 0.5, 0.5, 0.5)
        GameTooltip:Show()
    end)
    f:SetScript("OnLeave", function() GameTooltip:Hide() end)
    f:SetScript("OnClick", function()
        if SFrames.DarkmoonGuide and SFrames.DarkmoonGuide.Toggle then
            SFrames.DarkmoonGuide:Toggle()
        end
    end)
    f:RegisterForClicks("LeftButtonUp")
    f:Hide()
    dmfPins[i] = f
end

local function UpdateDarkmoonPins()
    if not WorldMapButton or not WorldMapFrame:IsVisible() then
        for i = 1, 2 do if dmfPins[i] then dmfPins[i]:Hide() end end
        return
    end

    local mapFile = GetMapInfo and GetMapInfo() or ""
    local curC = GetCurrentMapContinent and GetCurrentMapContinent() or 0
    local curZ = GetCurrentMapZone and GetCurrentMapZone() or 0
    local ai, iw = GetDmfSchedule()
    local w = WorldMapButton:GetWidth()
    local h = WorldMapButton:GetHeight()
    if not w or not h or w == 0 or h == 0 then return end

    for i = 1, 2 do
        CreateDmfPin(i)
        local pin = dmfPins[i]
        local loc = DMF_LOCATIONS[i]
        local px, py
        local show = false
        local isContinent = false

        if curZ > 0 and curC == loc.cont and loc.zoneIdx and curZ == loc.zoneIdx then
            show = true
            px, py = loc.zx, loc.zy
        elseif mapFile ~= "" and mapFile == loc.zone then
            show = true
            px, py = loc.zx, loc.zy
        elseif curZ == 0 and curC == loc.cont then
            show = true
            isContinent = true
            px, py = loc.cx, loc.cy
        end

        if show then
            local sz = isContinent and DMF_SIZE_CONT or DMF_SIZE
            pin:SetWidth(sz); pin:SetHeight(sz)
            pin.icon:SetWidth(sz); pin.icon:SetHeight(sz)
            pin.glow:SetWidth(sz * 2.2); pin.glow:SetHeight(sz * 2.2)
            pin:ClearAllPoints()
            pin:SetPoint("CENTER", WorldMapButton, "TOPLEFT", w * px, -h * py)
            if ai == i then
                if iw then
                    pin.icon:SetVertexColor(0.75, 0.75, 0.75)
                    pin:SetAlpha(0.85)
                    pin.glow:Hide()
                else
                    pin.icon:SetVertexColor(1, 1, 1)
                    pin:SetAlpha(1)
                    pin.glow:Show()
                end
            else
                pin.icon:SetVertexColor(0.45, 0.45, 0.45)
                pin:SetAlpha(0.7)
                pin.glow:Hide()
            end
            pin:Show()
        else
            pin:Hide()
        end
    end

    dmfPulse = dmfPulse + 0.03
    if dmfPulse > 6.2832 then dmfPulse = dmfPulse - 6.2832 end
    for i = 1, 2 do
        local p = dmfPins[i]
        if p and p:IsShown() and p.glow and p.glow:IsShown() then
            p.glow:SetAlpha(0.25 + 0.20 * math.sin(dmfPulse * 2))
        end
    end
end

SLASH_DMFMAP1 = "/dmfmap"
SlashCmdList["DMFMAP"] = function(msg)
    local cf = DEFAULT_CHAT_FRAME
    if msg == "scan" then
        local savedC = GetCurrentMapContinent and GetCurrentMapContinent() or 0
        local savedZ = GetCurrentMapZone and GetCurrentMapZone() or 0
        for c = 1, 2 do
            local cname = c == 1 and "Kalimdor" or "Eastern Kingdoms"
            cf:AddMessage("|cffffcc66[DMF Scan] " .. cname .. " (cont=" .. c .. "):|r")
            local zones = { GetMapZones(c) }
            for idx = 1, table.getn(zones) do
                SetMapZoom(c, idx)
                local mf = GetMapInfo and GetMapInfo() or "(nil)"
                local zname = zones[idx] or "?"
                if string.find(string.lower(mf), "elwynn") or string.find(string.lower(zname), "elwynn")
                   or string.find(string.lower(mf), "mulgore") or string.find(string.lower(zname), "mulgore") then
                    cf:AddMessage("  |cff00ff00>>|r idx=" .. idx .. " file=" .. tostring(mf) .. " name=" .. tostring(zname))
                else
                    cf:AddMessage("  idx=" .. idx .. " file=" .. tostring(mf) .. " name=" .. tostring(zname))
                end
            end
        end
        if savedZ > 0 then SetMapZoom(savedC, savedZ)
        elseif savedC > 0 then SetMapZoom(savedC, 0)
        else if SetMapToCurrentZone then SetMapToCurrentZone() end end
        return
    end
    local ai, iw, dl, ds = GetDmfSchedule()
    local n = DMF_LOCATIONS[ai] and DMF_LOCATIONS[ai].name or "?"
    local mf = GetMapInfo and GetMapInfo() or "(nil)"
    local curC = GetCurrentMapContinent and GetCurrentMapContinent() or 0
    local curZ = GetCurrentMapZone and GetCurrentMapZone() or 0
    local cetJDN = GetCETJDN()
    local dow = math.mod(cetJDN, 7)
    local dayNames = { [0]="Mon", [1]="Tue", [2]="Wed", [3]="Thu", [4]="Fri", [5]="Sat", [6]="Sun" }
    local localT = date("%H:%M")
    local ts = time()
    local utcH = math.mod(math.floor(ts / 3600), 24)
    local utcM = math.mod(math.floor(ts / 60), 60)
    cf:AddMessage("|cffffcc66[暗月马戏团]|r 本周: " .. n
        .. " | CET " .. (dayNames[dow] or "?") .. (iw and "(休息)" or "")
        .. " | 剩余" .. dl .. "天 | " .. ds .. "天后轮换")
    cf:AddMessage("  本地=" .. localT .. " UTC=" .. string.format("%02d:%02d", utcH, utcM)
        .. " | map=" .. tostring(mf)
        .. " | cont=" .. curC .. " zone=" .. curZ)
    for i = 1, 2 do
        local p = dmfPins[i]
        local loc = DMF_LOCATIONS[i]
        cf:AddMessage("  " .. loc.name .. ": "
            .. (p and ("shown=" .. tostring(p:IsShown())) or "未创建")
            .. " | zoneIdx=" .. tostring(loc.zoneIdx or "未发现")
            .. " | file=" .. loc.zone .. " cont=" .. loc.cont)
    end
end

--------------------------------------------------------------------------------
-- 9. Chat Link Integration: transform <npin:...> and handle nmwp: clicks
--------------------------------------------------------------------------------
local function TransformMapLinks(text)
    if not text or type(text) ~= "string" then return text end
    if not string.find(text, "<npin:", 1, true) then return text end
    local result = string.gsub(text, "<npin:(%d+):(%d+):([%d%.]+):([%d%.]+):([^>]*)>",
        function(c, z, x, y, name)
            local display = name
            if not display or display == "" then display = "地图标记" end
            local _wh = (SFrames.Theme and SFrames.Theme:GetAccentHex()) or "ffFFB3D9"
            return "|c" .. _wh .. "|Hnmwp:" .. c .. ":" .. z .. ":" .. x .. ":" .. y .. "|h[" .. display .. " (" .. x .. ", " .. y .. ")]|h|r"
        end)
    return result
end

local function HookChatFrameForMapLinks(cf)
    if not cf or not cf.AddMessage or cf._nanamiMapLinkHooked then return end
    cf._nanamiMapLinkHooked = true
    local orig = cf.AddMessage
    cf.AddMessage = function(self, text, r, g, b, alpha, holdTime)
        if text and type(text) == "string" then
            text = TransformMapLinks(text)
        end
        orig(self, text, r, g, b, alpha, holdTime)
    end
end

local function HookAllChatFramesForMapLinks()
    local _G = getfenv(0)
    for i = 1, 10 do
        local cf = _G["ChatFrame" .. i]
        if cf then HookChatFrameForMapLinks(cf) end
    end
end

local function HookSetItemRefForWaypoints()
    local _G = getfenv(0)
    local origSIR = _G.SetItemRef
    _G.SetItemRef = function(link, text, button)
        if link and string.sub(link, 1, 5) == "nmwp:" then
            local data = string.sub(link, 6)
            WM:HandleWaypointLink(data)
            return
        end
        if origSIR then
            origSIR(link, text, button)
        end
    end
end

--------------------------------------------------------------------------------
-- 10. WorldMapButton Click Hook for Placing Waypoints
--------------------------------------------------------------------------------
local function HookWorldMapButtonClick()
    if not WorldMapButton then return end
    local prevOnClick = WorldMapButton:GetScript("OnClick")
    WorldMapButton:SetScript("OnClick", function()
        if arg1 == "LeftButton" and IsControlKeyDown() then
            local mx, my = GetMapCursorPos()
            if mx and my then
                local c = GetCurrentMapContinent and GetCurrentMapContinent() or 0
                local z = GetCurrentMapZone and GetCurrentMapZone() or 0
                local name = GetCurrentMapName()
                WM:SetWaypoint(c, z, mx, my, name)
            end
            return
        end
        if prevOnClick then prevOnClick() end
    end)
end

--------------------------------------------------------------------------------
-- 11. Navigation Map (迷你全区域地图 - 类似正式服 Shift+M)
-- Shows the entire zone map scaled down.  All state in table N.
--------------------------------------------------------------------------------
local N = {
    MW = 1002, MH = 668,
    CW = { 256, 256, 256, 234 },
    RH = { 256, 256, 156 },
    DEF_W = 350, MIN_W = 200, MAX_W = 600,
    curMap = "", pulse = 0,
    overlays = {}, overlayCount = 0,
}
local NC = {
    PBG = _A.panelBg, PBD = _A.panelBorder, TC = _A.title, DT = _A.dimText,
}

local function GetNavConfig()
    local cfg = WM:GetConfig()
    if type(cfg.nav) ~= "table" then
        cfg.nav = { enabled = false, width = N.DEF_W, alpha = 0.70, locked = false }
    end
    if not cfg.nav.width then cfg.nav.width = N.DEF_W end
    if cfg.nav.width < N.MIN_W then cfg.nav.width = N.MIN_W end
    if cfg.nav.width > N.MAX_W then cfg.nav.width = N.MAX_W end
    return cfg.nav
end

local function SaveNavPos()
    if not N.frame then return end
    local nc = GetNavConfig()
    local pt, _, rp, x, y = N.frame:GetPoint()
    nc.aF = pt; nc.aT = rp; nc.pX = x; nc.pY = y
end

local function NavNextPow2(n)
    local p = 16
    while p < n do p = p * 2 end
    return p
end


local function NavApplySize(w)
    if not N.frame or not N.tiles then return end
    local h = w * N.MH / N.MW
    local s = w / N.MW
    N.frame:SetWidth(w); N.frame:SetHeight(h)

    local idx = 0
    for row = 1, 3 do
        for col = 1, 4 do
            idx = idx + 1
            local t = N.tiles[idx]
            t:SetWidth(N.CW[col] * s); t:SetHeight(N.RH[row] * s)
            local xO, yO = 0, 0
            for c = 1, col - 1 do xO = xO + N.CW[c] end
            for r = 1, row - 1 do yO = yO + N.RH[r] end
            t:ClearAllPoints()
            t:SetPoint("TOPLEFT", N.frame, "TOPLEFT", xO * s, -yO * s)
        end
    end

    for i = 1, N.overlayCount do
        if N.overlays[i] and N.ovData and N.ovData[i] then
            local d = N.ovData[i]
            local tex = N.overlays[i]
            tex:SetWidth(d.pw * s); tex:SetHeight(d.ph * s)
            tex:ClearAllPoints()
            tex:SetPoint("TOPLEFT", N.frame, "TOPLEFT", d.ox * s, -d.oy * s)
        end
    end
end

local function UpdateNavOverlays(mapFile)
    if not N.frame or not mapFile or mapFile == "" then return end

    local db = MapOverlayData or LibMapOverlayData or zMapOverlayData or mapOverlayData
    if not db then
        for i = 1, N.overlayCount do if N.overlays[i] then N.overlays[i]:Hide() end end
        return
    end

    local zoneData = db[mapFile]
    if not zoneData then
        for i = 1, N.overlayCount do if N.overlays[i] then N.overlays[i]:Hide() end end
        return
    end

    local nc = GetNavConfig()
    local s = (nc.width or N.DEF_W) / N.MW
    local prefix = "Interface\\WorldMap\\" .. mapFile .. "\\"
    local texIdx = 0
    if not N.ovData then N.ovData = {} end

    for idx = 1, table.getn(zoneData) do
        local entry = zoneData[idx]
        local _, _, oName, sW, sH, sX, sY = string.find(entry, "^(%u+):(%d+):(%d+):(%d+):(%d+)$")
        if not oName then
            _, _, oName, sW, sH, sX, sY = string.find(entry, "^([^:]+):(%d+):(%d+):(%d+):(%d+)$")
        end
        if oName then
            local tw  = tonumber(sW)
            local th  = tonumber(sH)
            local ox  = tonumber(sX)
            local oy  = tonumber(sY)
            local tName = prefix .. oName
            local numH = math.ceil(tw / 256)
            local numV = math.ceil(th / 256)

            for row = 1, numV do
                local pxH, fileH
                if row < numV then
                    pxH = 256; fileH = 256
                else
                    pxH = math.mod(th, 256)
                    if pxH == 0 then pxH = 256 end
                    fileH = NavNextPow2(pxH)
                end
                for col = 1, numH do
                    texIdx = texIdx + 1
                    local pxW, fileW
                    if col < numH then
                        pxW = 256; fileW = 256
                    else
                        pxW = math.mod(tw, 256)
                        if pxW == 0 then pxW = 256 end
                        fileW = NavNextPow2(pxW)
                    end

                    if not N.overlays[texIdx] then
                        N.overlays[texIdx] = N.frame:CreateTexture(nil, "OVERLAY")
                    end
                    local tex = N.overlays[texIdx]
                    local realOx = ox + 256 * (col - 1)
                    local realOy = oy + 256 * (row - 1)

                    N.ovData[texIdx] = { pw = pxW, ph = pxH, ox = realOx, oy = realOy }

                    tex:SetWidth(pxW * s); tex:SetHeight(pxH * s)
                    tex:SetTexCoord(0, pxW / fileW, 0, pxH / fileH)
                    tex:ClearAllPoints()
                    tex:SetPoint("TOPLEFT", N.frame, "TOPLEFT", realOx * s, -realOy * s)

                    local tileIndex = ((row - 1) * numH) + col
                    tex:SetTexture(tName .. tileIndex)
                    tex:SetVertexColor(1, 1, 1, 1)
                    tex:Show()
                end
            end
        end
    end

    for i = texIdx + 1, N.overlayCount do
        if N.overlays[i] then N.overlays[i]:Hide() end
    end
    if texIdx > N.overlayCount then N.overlayCount = texIdx end
end

local function CreateNavMap()
    if N.frame then return end
    local nc   = GetNavConfig()
    local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
    local w    = nc.width or N.DEF_W
    local h    = w * N.MH / N.MW
    local s    = w / N.MW

    local f = CreateFrame("Frame", "NanamiNavMap", UIParent)
    N.frame = f
    f:SetWidth(w); f:SetHeight(h)
    f:SetAlpha(nc.alpha or 0.70)
    f:SetMovable(true); f:EnableMouse(true); f:EnableMouseWheel(1)
    f:SetClampedToScreen(true)
    f:SetFrameStrata("LOW"); f:SetFrameLevel(10)
    f:Hide()

    f:ClearAllPoints()
    if nc.aF and nc.pX and nc.pY then
        f:SetPoint(nc.aF, UIParent, nc.aT or nc.aF, nc.pX, nc.pY)
    else
        f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
    end

    N.tiles = {}
    local idx = 0
    for row = 1, 3 do
        for col = 1, 4 do
            idx = idx + 1
            local oW = N.CW[col]
            local oH = N.RH[row]
            local tile = f:CreateTexture("NanamiNavTile" .. idx, "ARTWORK")
            tile:SetWidth(oW * s); tile:SetHeight(oH * s)
            local xO, yO = 0, 0
            for c = 1, col - 1 do xO = xO + N.CW[c] end
            for r = 1, row - 1 do yO = yO + N.RH[r] end
            tile:SetPoint("TOPLEFT", f, "TOPLEFT", xO * s, -yO * s)
            local tcR = (oW >= 256) and 1 or (oW / 256)
            local tcB = (oH >= 256) and 1 or (oH / 256)
            tile:SetTexCoord(0, tcR, 0, tcB)
            N.tiles[idx] = tile
        end
    end

    -- thin border
    local borderFrame = CreateFrame("Frame", nil, f)
    borderFrame:SetAllPoints(f)
    borderFrame:SetFrameLevel(f:GetFrameLevel() + 5)
    borderFrame:SetBackdrop({
        edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 },
    })
    borderFrame:SetBackdropBorderColor(0, 0, 0, 0.50)

    -- gradient fade overlay (soft vignette at edges)
    local fade = CreateFrame("Frame", nil, f)
    fade:SetAllPoints(f)
    fade:SetFrameLevel(f:GetFrameLevel() + 6)
    local FD = 30

    local fadeL = fade:CreateTexture(nil, "OVERLAY")
    fadeL:SetTexture("Interface\\Buttons\\WHITE8X8")
    fadeL:SetWidth(FD)
    fadeL:SetPoint("TOPLEFT", fade, "TOPLEFT", 0, 0)
    fadeL:SetPoint("BOTTOMLEFT", fade, "BOTTOMLEFT", 0, 0)
    fadeL:SetGradientAlpha("HORIZONTAL", 0,0,0,0.25, 0,0,0,0)

    local fadeR = fade:CreateTexture(nil, "OVERLAY")
    fadeR:SetTexture("Interface\\Buttons\\WHITE8X8")
    fadeR:SetWidth(FD)
    fadeR:SetPoint("TOPRIGHT", fade, "TOPRIGHT", 0, 0)
    fadeR:SetPoint("BOTTOMRIGHT", fade, "BOTTOMRIGHT", 0, 0)
    fadeR:SetGradientAlpha("HORIZONTAL", 0,0,0,0, 0,0,0,0.25)

    local fadeT = fade:CreateTexture(nil, "OVERLAY")
    fadeT:SetTexture("Interface\\Buttons\\WHITE8X8")
    fadeT:SetHeight(FD)
    fadeT:SetPoint("TOPLEFT", fade, "TOPLEFT", 0, 0)
    fadeT:SetPoint("TOPRIGHT", fade, "TOPRIGHT", 0, 0)
    fadeT:SetGradientAlpha("VERTICAL", 0,0,0,0, 0,0,0,0.25)

    local fadeB = fade:CreateTexture(nil, "OVERLAY")
    fadeB:SetTexture("Interface\\Buttons\\WHITE8X8")
    fadeB:SetHeight(FD)
    fadeB:SetPoint("BOTTOMLEFT", fade, "BOTTOMLEFT", 0, 0)
    fadeB:SetPoint("BOTTOMRIGHT", fade, "BOTTOMRIGHT", 0, 0)
    fadeB:SetGradientAlpha("VERTICAL", 0,0,0,0.25, 0,0,0,0)

    -- player indicator (arrow + ripple)
    local dotFrame = CreateFrame("Frame", nil, f)
    N.dotFrame = dotFrame
    dotFrame:SetWidth(1); dotFrame:SetHeight(1)
    dotFrame:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
    dotFrame:SetFrameLevel(f:GetFrameLevel() + 7)

    local CIRCLE = "Interface\\Minimap\\UI-Minimap-Background"
    N.ripples = {}
    for i = 1, 3 do
        local rip = dotFrame:CreateTexture(nil, "ARTWORK")
        rip:SetTexture(CIRCLE)
        rip:SetWidth(8); rip:SetHeight(8)
        rip:SetPoint("CENTER", dotFrame, "CENTER", 0, 0)
        rip:SetVertexColor(0.0, 0.85, 1.0, 0.35)
        rip:SetBlendMode("ADD")
        N.ripples[i] = rip
    end

    local CLASS_ICON = "Interface\\AddOns\\Nanami-UI\\img\\UI-Classes-Circles"
    N.dotTex = dotFrame:CreateTexture(nil, "OVERLAY")
    N.dotTex:SetTexture(CLASS_ICON)
    N.dotTex:SetWidth(14); N.dotTex:SetHeight(14)
    N.dotTex:SetPoint("CENTER", dotFrame, "CENTER", 0, 0)
    local _, pClass = UnitClass("player")
    if pClass and SFrames.CLASS_ICON_TCOORDS and SFrames.CLASS_ICON_TCOORDS[pClass] then
        local tc = SFrames.CLASS_ICON_TCOORDS[pClass]
        N.dotTex:SetTexCoord(tc[1], tc[2], tc[3], tc[4])
    end

    N.dirTip = dotFrame:CreateTexture(nil, "OVERLAY")
    N.dirTip:SetTexture(CIRCLE)
    N.dirTip:SetWidth(4); N.dirTip:SetHeight(4)
    N.dirTip:SetPoint("CENTER", dotFrame, "CENTER", 0, 10)
    N.dirTip:SetVertexColor(1, 1, 1, 0.90)

    N.dirTail = dotFrame:CreateTexture(nil, "OVERLAY")
    N.dirTail:SetTexture(CIRCLE)
    N.dirTail:SetWidth(2); N.dirTail:SetHeight(2)
    N.dirTail:SetPoint("CENTER", dotFrame, "CENTER", 0, -7)
    N.dirTail:SetVertexColor(0.6, 0.6, 0.6, 0.50)

    -- info bar (zone + coords below map)
    local infoFrame = CreateFrame("Frame", nil, f)
    infoFrame:SetWidth(w); infoFrame:SetHeight(24)
    infoFrame:SetPoint("TOP", f, "BOTTOM", 0, 0)
    infoFrame:SetFrameLevel(f:GetFrameLevel() + 6)

    N.zoneFS = infoFrame:CreateFontString(nil, "OVERLAY")
    N.zoneFS:SetFont(font, 9, "OUTLINE")
    N.zoneFS:SetPoint("LEFT", infoFrame, "LEFT", 4, 0)
    N.zoneFS:SetTextColor(NC.TC[1], NC.TC[2], NC.TC[3])

    N.coordFS = infoFrame:CreateFontString(nil, "OVERLAY")
    N.coordFS:SetFont(font, 9, "OUTLINE")
    N.coordFS:SetPoint("RIGHT", infoFrame, "RIGHT", -4, 0)
    N.coordFS:SetTextColor(0.85, 0.85, 0.85)

    -- close button
    local cbtn = CreateFrame("Button", nil, f)
    cbtn:SetWidth(14); cbtn:SetHeight(14)
    cbtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -3, -3)
    cbtn:SetFrameLevel(f:GetFrameLevel() + 8)
    local cbFS = cbtn:CreateFontString(nil, "OVERLAY")
    cbFS:SetFont(font, 11, "OUTLINE"); cbFS:SetPoint("CENTER", 0, 0)
    cbFS:SetText("x"); cbFS:SetTextColor(_A.dimText[1], _A.dimText[2], _A.dimText[3])
    cbtn:SetScript("OnClick", function() WM:ToggleNav() end)
    cbtn:SetScript("OnEnter", function() cbFS:SetTextColor(1, 0.3, 0.3) end)
    cbtn:SetScript("OnLeave", function() cbFS:SetTextColor(_A.dimText[1], _A.dimText[2], _A.dimText[3]) end)

    -- hover toolbar (shown on mouse enter)
    local toolbar = CreateFrame("Frame", nil, f)
    toolbar:SetWidth(w); toolbar:SetHeight(22)
    toolbar:SetPoint("BOTTOM", f, "TOP", 0, 2)
    toolbar:SetFrameLevel(f:GetFrameLevel() + 9)
    toolbar:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 } })
    toolbar:SetBackdropColor(_A.headerBg[1], _A.headerBg[2], _A.headerBg[3], 0.85)
    toolbar:SetBackdropBorderColor(0, 0, 0, 0.50)
    toolbar:EnableMouse(true)
    toolbar:Hide()
    N.toolbar = toolbar

    local tipFS = toolbar:CreateFontString(nil, "OVERLAY")
    tipFS:SetFont(font, 9, "OUTLINE")
    tipFS:SetPoint("CENTER", toolbar, "CENTER", 0, 0)
    tipFS:SetTextColor(_A.dimText[1], _A.dimText[2], _A.dimText[3])
    tipFS:SetText("拖动移位 | 滚轮缩放 | Ctrl+滚轮透明度")

    local lockBtn = CreateFrame("Button", nil, toolbar)
    lockBtn:SetWidth(28); lockBtn:SetHeight(16)
    lockBtn:SetPoint("RIGHT", toolbar, "RIGHT", -4, 0)
    lockBtn:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 } })
    lockBtn:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], 0.90)
    lockBtn:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.6)
    local lockFS = lockBtn:CreateFontString(nil, "OVERLAY")
    lockFS:SetFont(font, 8, "OUTLINE"); lockFS:SetPoint("CENTER", 0, 0)
    N.lockFS = lockFS

    local function UpdateLockText()
        local lk = GetNavConfig().locked
        if lk then
            lockFS:SetText("解锁"); lockFS:SetTextColor(1.0, 0.5, 0.5)
        else
            lockFS:SetText("锁定"); lockFS:SetTextColor(0.5, 1.0, 0.5)
        end
    end
    UpdateLockText()

    lockBtn:SetScript("OnClick", function()
        local cfg = GetNavConfig()
        cfg.locked = not cfg.locked
        UpdateLockText()
    end)
    lockBtn:SetScript("OnEnter", function()
        this:SetBackdropBorderColor(_A.btnHoverBd[1], _A.btnHoverBd[2], _A.btnHoverBd[3], _A.btnHoverBd[4])
    end)
    lockBtn:SetScript("OnLeave", function()
        this:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.6)
    end)

    N.hideTimer = 0
    f:SetScript("OnEnter", function()
        N.hideTimer = 0
        if N.toolbar then N.toolbar:Show() end
        if cbtn then cbtn:Show() end
    end)
    f:SetScript("OnLeave", function()
        N.hideTimer = 1.5
    end)
    toolbar:SetScript("OnEnter", function()
        N.hideTimer = 0
    end)
    toolbar:SetScript("OnLeave", function()
        N.hideTimer = 1.5
    end)
    cbtn:Hide()

    -- dragging
    f:SetScript("OnMouseDown", function()
        if arg1 == "LeftButton" and not GetNavConfig().locked then this:StartMoving() end
    end)
    f:SetScript("OnMouseUp", function() this:StopMovingOrSizing(); SaveNavPos() end)

    -- scroll: resize from center / alpha
    f:SetScript("OnMouseWheel", function()
        local cfg = GetNavConfig()
        if IsControlKeyDown() then
            local a = (cfg.alpha or this:GetAlpha()) + arg1 * 0.10
            if a < 0.15 then a = 0.15 end; if a > 1 then a = 1 end
            cfg.alpha = a; this:SetAlpha(a)
        else
            local oldW = this:GetWidth()
            local oldH = this:GetHeight()
            local nw = cfg.width + arg1 * 25
            if nw < N.MIN_W then nw = N.MIN_W end
            if nw > N.MAX_W then nw = N.MAX_W end
            local nh = nw * N.MH / N.MW
            local dw = (nw - oldW) / 2
            local dh = (nh - oldH) / 2

            local pt, rel, rp, px, py = this:GetPoint()
            if pt and px and py then
                this:ClearAllPoints()
                this:SetPoint(pt, rel, rp, px - dw, py + dh)
            end

            cfg.width = nw
            NavApplySize(nw)
            if N.toolbar then N.toolbar:SetWidth(nw) end
            SaveNavPos()
        end
    end)
end

local function UpdateNavMap()
    if not N.frame or not N.tiles or not N.frame:IsVisible() then return end
    if WorldMapFrame and WorldMapFrame:IsVisible() then return end

    if SetMapToCurrentZone then SetMapToCurrentZone() end

    local mapFile = GetMapInfo and GetMapInfo() or ""
    if mapFile ~= "" and mapFile ~= N.curMap then
        N.curMap = mapFile
        for i = 1, 12 do
            N.tiles[i]:SetTexture("Interface\\WorldMap\\" .. mapFile .. "\\" .. mapFile .. i)
        end
        UpdateNavOverlays(mapFile)
    end

    if mapFile ~= "" then
        local px, py = GetPlayerMapPosition("player")
        if px and py and (px > 0 or py > 0) then
            local fw = N.frame:GetWidth()
            local fh = N.frame:GetHeight()
            if N.dotFrame then
                N.dotFrame:ClearAllPoints()
                N.dotFrame:SetPoint("TOPLEFT", N.frame, "TOPLEFT", px * fw, -py * fh)
            end

            if GetPlayerFacing and N.dirTip then
                local facing = GetPlayerFacing()
                if facing then
                    local r = 10
                    local tipX = -math.sin(facing) * r
                    local tipY =  math.cos(facing) * r
                    N.dirTip:ClearAllPoints()
                    N.dirTip:SetPoint("CENTER", N.dotFrame, "CENTER", tipX, tipY)
                    if N.dirTail then
                        local tr = 7
                        N.dirTail:ClearAllPoints()
                        N.dirTail:SetPoint("CENTER", N.dotFrame, "CENTER", -tipX / r * tr, -tipY / r * tr)
                    end
                end
            end

            local coordStr = string.format("%.1f, %.1f", px * 100, py * 100)
            if N.coordFS then N.coordFS:SetText(coordStr) end
        end
    end

    local zn = GetZoneText and GetZoneText() or ""
    if zn ~= "" and N.zoneFS then N.zoneFS:SetText(zn) end
end

local function UpdateNavPulse(dt)
    if not N.frame or not N.ripples or not N.frame:IsVisible() then return end
    N.pulse = (N.pulse or 0) + 0.05
    if N.pulse > 6.2832 then N.pulse = N.pulse - 6.2832 end

    local PHASE_OFF = 2.0944
    for i = 1, 3 do
        local rip = N.ripples[i]
        if rip then
            local t = math.mod(N.pulse + (i - 1) * PHASE_OFF, 6.2832) / 6.2832
            local sz = 6 + 28 * t
            local a  = 0.40 * (1 - t)
            rip:SetWidth(sz); rip:SetHeight(sz)
            rip:SetVertexColor(0.0, 0.85, 1.0, a)
        end
    end

    if N.hideTimer and N.hideTimer > 0 then
        N.hideTimer = N.hideTimer - (dt or 0.04)
        if N.hideTimer <= 0 then
            N.hideTimer = 0
            if N.toolbar then N.toolbar:Hide() end
        end
    end
end

function WM:ToggleNav()
    local nc = GetNavConfig()
    nc.enabled = not nc.enabled
    CreateNavMap()
    if nc.enabled then
        N.frame:Show(); N.curMap = ""
        UpdateNavMap()
        if SFrames and SFrames.Print then
            SFrames:Print("导航地图: |cff00ff00已开启|r (滚轮缩放 | Ctrl+滚轮透明度 | 拖动)")
        end
    else
        N.frame:Hide()
        if SFrames and SFrames.Print then
            SFrames:Print("导航地图: |cffff0000已关闭|r")
        end
    end
end

function WM:ShowNav()
    local nc = GetNavConfig()
    CreateNavMap()
    if not nc.enabled then nc.enabled = true end
    if WorldMapFrame and WorldMapFrame:IsVisible() then
        WorldMapFrame:Hide()
    end
    N.frame:Show(); N.curMap = ""
    UpdateNavMap()
end

local function CreateNavToggleBtn()
    if not skinFrame or not coordOverlay then return end
    if N.toggleBtn then return end
    local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
    local b = CreateFrame("Button", nil, coordOverlay)
    N.toggleBtn = b
    b:SetWidth(70); b:SetHeight(20)
    b:SetPoint("BOTTOMRIGHT", skinFrame, "BOTTOMRIGHT", -12, 22)
    b:SetFrameLevel(coordOverlay:GetFrameLevel() + 1)
    b:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 } })
    b:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
    b:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])
    local fs = b:CreateFontString(nil, "OVERLAY")
    fs:SetFont(font, 10, "OUTLINE"); fs:SetPoint("CENTER", 0, 0)
    fs:SetText("缩略地图"); fs:SetTextColor(_A.btnText[1], _A.btnText[2], _A.btnText[3])
    b:SetScript("OnClick", function() WM:ShowNav() end)
    b:SetScript("OnEnter", function()
        this:SetBackdropColor(_A.btnHoverBg[1], _A.btnHoverBg[2], _A.btnHoverBg[3], _A.btnHoverBg[4])
        this:SetBackdropBorderColor(_A.btnHoverBd[1], _A.btnHoverBd[2], _A.btnHoverBd[3], _A.btnHoverBd[4])
    end)
    b:SetScript("OnLeave", function()
        this:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
        this:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])
    end)
end

local function InitNavMap()
    CreateNavMap()
    CreateNavToggleBtn()

    local nc = GetNavConfig()
    if nc.enabled then
        N.frame:Show(); N.curMap = ""
    end

    local mapEl, animEl = 0, 0
    local u = CreateFrame("Frame", nil, UIParent)
    u:SetScript("OnUpdate", function()
        local dt = arg1 or 0
        mapEl  = mapEl + dt
        animEl = animEl + dt
        if mapEl >= 0.10 then mapEl = 0; UpdateNavMap() end
        if animEl >= 0.04 then
            local elapsed = animEl; animEl = 0; UpdateNavPulse(elapsed)
        end
    end)
end

--------------------------------------------------------------------------------
-- 12. Initialize
--------------------------------------------------------------------------------
function WM:Initialize()
    local cfg = self:GetConfig()
    if not cfg.enabled then return end

    HideBlizzardDecorations()
    CreateNanamiSkin()
    CreateTooltipBG()
    self:SetupWindowMode()

    SkinWorldMapControls()
    CreateWaypointPin()
    HookWorldMapButtonClick()

    HookTooltipShow(WorldMapTooltip)
    HookTooltipShow(GameTooltip)

    HookAllChatFramesForMapLinks()
    HookSetItemRefForWaypoints()

    local origUpdater = OnFrameUpdate
    local function NewOnFrameUpdate()
        origUpdater()
        UpdatePinPosition()
        UpdateDarkmoonPins()
    end

    local updater = CreateFrame("Frame", nil, UIParent)
    updater:SetScript("OnUpdate", NewOnFrameUpdate)

    InitNavMap()
    DiscoverDmfZoneIndices()

    self.initialized = true
    local _hex = (SFrames.Theme and SFrames.Theme:GetAccentHex()) or "ffffb3d9"
    DEFAULT_CHAT_FRAME:AddMessage("|c" .. _hex .. "[Nanami-UI]|r 世界地图模块已加载 (Nanami主题 | Ctrl+左键放置标记 | /nui nav 导航地图)")
end
