--------------------------------------------------------------------------------
-- Nanami-UI: Tweaks -- Ported from ShaguTweaks
-- 1. Auto Stance      - auto switch warrior/druid stance on spell cast
-- 2. SuperWoW         - GUID-based cast/channel data for SuperWoW client
-- 3. Turtle Compat    - hide TW's overlapping target HP text, etc.
-- 4. Cooldown Numbers - show remaining cooldown time as text overlay
-- 5. Dark UI          - darken the entire interface
-- 6. WorldMap Window  - turn fullscreen map into a movable/scalable window
-- 7. Auto Dismount    - cancel shapeshift/mount when casting incompatible spells
--------------------------------------------------------------------------------

SFrames.Tweaks = SFrames.Tweaks or {}
local Tweaks = SFrames.Tweaks

SFrames.castdb = SFrames.castdb or {}

local function GetTweaksCfg()
    if not SFramesDB or type(SFramesDB.Tweaks) ~= "table" then
        return { autoStance = true, superWoW = true, turtleCompat = true,
                 cooldownNumbers = true, darkUI = false, worldMapWindow = false }
    end
    return SFramesDB.Tweaks
end

local function strsplit(delimiter, str)
    local result = {}
    local pattern = "([^" .. delimiter .. "]+)"
    for match in string.gfind(str, pattern) do
        table.insert(result, match)
    end
    return unpack(result)
end

-- hooksecurefunc polyfill for WoW 1.12 (vanilla)
local _hooks = {}
local _hooksecurefunc = hooksecurefunc
if not _hooksecurefunc then
    _hooksecurefunc = function(tbl, name, func)
        if type(tbl) == "string" then
            func = name
            name = tbl
            tbl = getfenv(0)
        end
        if not tbl or not tbl[name] then return end
        local key = tostring(func)
        _hooks[key] = {}
        _hooks[key].old = tbl[name]
        _hooks[key].new = func
        tbl[name] = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
            local r1,r2,r3,r4,r5 = _hooks[key].old(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
            pcall(_hooks[key].new, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
            return r1,r2,r3,r4,r5
        end
    end
end

--------------------------------------------------------------------------------
-- Auto Stance
-- When a spell fails because you're in the wrong stance/form, automatically
-- switch to the required one. Works for warriors, druids, etc.
--------------------------------------------------------------------------------
local function InitAutoStance()
    local frame = CreateFrame("Frame", "NanamiAutoStance")
    local scanString = string.gsub(SPELL_FAILED_ONLY_SHAPESHIFT, "%%s", "(.+)")

    frame:RegisterEvent("UI_ERROR_MESSAGE")
    frame:SetScript("OnEvent", function()
        for stances in string.gfind(arg1, scanString) do
            for _, stance in pairs({ strsplit(",", stances) }) do
                CastSpellByName(string.gsub(stance, "^%s*(.-)%s*$", "%1"))
            end
        end
    end)
end

--------------------------------------------------------------------------------
-- Auto Dismount / Cancel Shapeshift
-- When casting a spell that fails because you are mounted or shapeshifted,
-- automatically cancel the mount/shapeshift buff so the next cast succeeds.
--------------------------------------------------------------------------------
local function InitAutoDismount()
    local dismount = CreateFrame("Frame", "NanamiAutoDismount")

    local scanner = CreateFrame("GameTooltip", "NanamiDismountScan", nil, "GameTooltipTemplate")
    scanner:SetOwner(WorldFrame, "ANCHOR_NONE")
    scanner:SetAlpha(0)
    scanner:Hide()

    local mountStrings = {
        "^Increases speed by (.+)%%",
        "^Erhöht Tempo um (.+)%%",
        "^Aumenta la velocidad en un (.+)%%",
        "^Augmente la vitesse de (.+)%%",
        "^Скорость увеличена на (.+)%%",
        "^이동 속도 (.+)%%만큼 증가",
        "^速度提高(.+)%%",
        "speed based on", "Slow and steady...", "Riding",
        "Lento y constante...", "Aumenta la velocidad según tu habilidad de Montar.",
        "根据您的骑行技能提高速度。", "根据骑术技能提高速度。", "又慢又稳......",
    }

    local shapeshiftIcons = {
        "ability_racial_bearform", "ability_druid_catform",
        "ability_druid_travelform", "spell_nature_forceofnature",
        "ability_druid_aquaticform", "spell_nature_spiritwolf",
        "ability_druid_treeoflife", "ability_druid_stagform",
    }

    local errorStrings = {}
    local errorGlobals = {
        "SPELL_FAILED_NOT_MOUNTED", "ERR_ATTACK_MOUNTED", "ERR_TAXIPLAYERALREADYMOUNTED",
        "SPELL_FAILED_NOT_SHAPESHIFT", "SPELL_FAILED_NO_ITEMS_WHILE_SHAPESHIFTED",
        "SPELL_NOT_SHAPESHIFTED", "SPELL_NOT_SHAPESHIFTED_NOSPACE",
        "ERR_CANT_INTERACT_SHAPESHIFTED", "ERR_NOT_WHILE_SHAPESHIFTED",
        "ERR_NO_ITEMS_WHILE_SHAPESHIFTED", "ERR_TAXIPLAYERSHAPESHIFTED",
        "ERR_MOUNT_SHAPESHIFTED",
    }
    for _, name in pairs(errorGlobals) do
        local val = getfenv(0)[name]
        if val then table.insert(errorStrings, val) end
    end

    dismount:RegisterEvent("UI_ERROR_MESSAGE")
    dismount:SetScript("OnEvent", function()
        if arg1 == SPELL_FAILED_NOT_STANDING then
            SitOrStand()
            return
        end

        local matched = false
        for _, err in pairs(errorStrings) do
            if arg1 == err then matched = true break end
        end
        if not matched then return end

        for i = 0, 31 do
            scanner:ClearLines()
            scanner:SetPlayerBuff(i)
            for line = 1, scanner:NumLines() do
                local text = getfenv(0)["NanamiDismountScanTextLeft" .. line]
                if text and text:GetText() then
                    for _, str in pairs(mountStrings) do
                        if string.find(text:GetText(), str) then
                            CancelPlayerBuff(i)
                            return
                        end
                    end
                end
            end

            local buff = GetPlayerBuffTexture(i)
            if buff then
                for _, icon in pairs(shapeshiftIcons) do
                    if string.find(string.lower(buff), icon) then
                        CancelPlayerBuff(i)
                        return
                    end
                end
            end
        end
    end)
end

--------------------------------------------------------------------------------
-- SuperWoW Compatibility
-- Provides GUID-based cast/channel data when SuperWoW client mod is active.
-- Data stored in SFrames.castdb[guid] for consumption by castbar features.
--------------------------------------------------------------------------------
local function InitSuperWoW()
    if not GetPlayerBuffID or not CombatLogAdd or not SpellInfo then return end

    local castdb = SFrames.castdb

    local frame = CreateFrame("Frame", "NanamiSuperWoW")
    frame:RegisterEvent("UNIT_CASTEVENT")
    frame:SetScript("OnEvent", function()
        if arg3 == "START" or arg3 == "CAST" or arg3 == "CHANNEL" then
            local guid = arg1
            local spell, icon, _
            if SpellInfo and SpellInfo(arg4) then
                spell, _, icon = SpellInfo(arg4)
            end
            spell = spell or UNKNOWN
            icon = icon or "Interface\\Icons\\INV_Misc_QuestionMark"

            if not castdb[guid] then castdb[guid] = {} end
            castdb[guid].cast     = spell
            castdb[guid].rank     = nil
            castdb[guid].start    = GetTime()
            castdb[guid].casttime = arg5
            castdb[guid].icon     = icon
            castdb[guid].channel  = (arg3 == "CHANNEL") or false

            SFrames.superwow_active = true
        elseif arg3 == "FAIL" then
            local guid = arg1
            if castdb[guid] then
                castdb[guid].cast     = nil
                castdb[guid].rank     = nil
                castdb[guid].start    = nil
                castdb[guid].casttime = nil
                castdb[guid].icon     = nil
                castdb[guid].channel  = nil
            end
        end
    end)
end

--------------------------------------------------------------------------------
-- Turtle WoW Compatibility
-- Hides TW's built-in target HP text that overlaps with Nanami-UI frames,
-- and applies other TW-specific fixes.
--------------------------------------------------------------------------------
local function ApplyWorldMapWindowLayout()
    WorldMapFrame:SetMovable(true)
    WorldMapFrame:EnableMouse(true)
    WorldMapFrame:SetScale(.85)
    WorldMapFrame:ClearAllPoints()
    WorldMapFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 30)
    WorldMapFrame:SetWidth(WorldMapButton:GetWidth() + 15)
    WorldMapFrame:SetHeight(WorldMapButton:GetHeight() + 55)
    if WorldMapFrameTitle then
        WorldMapFrameTitle:SetPoint("TOP", WorldMapFrame, 0, 17)
    end
    BlackoutWorld:Hide()
end

local function InitTurtleCompat()
    if not TargetHPText or not TargetHPPercText then return end

    TargetHPText:Hide()
    TargetHPText.Show = function() return end

    TargetHPPercText:Hide()
    TargetHPPercText.Show = function() return end

    if WorldMapFrame_Maximize then
        local origMaximize = WorldMapFrame_Maximize
        WorldMapFrame_Maximize = function()
            origMaximize()
            local cfg = GetTweaksCfg()
            if cfg.worldMapWindow ~= false then
                ApplyWorldMapWindowLayout()
            elseif WorldMapFrameTitle then
                WorldMapFrameTitle:SetPoint("TOP", WorldMapFrame, 0, 17)
            end
        end
    end
end

--------------------------------------------------------------------------------
-- WorldMap Window
-- Turn the fullscreen world map into a movable, scalable window.
-- Ctrl+Scroll to zoom, Shift+Scroll to change transparency, drag to move.
--------------------------------------------------------------------------------
local function HookScript(frame, script, func)
    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
        func(a1,a2,a3,a4,a5,a6,a7,a8,a9)
    end)
end

local function InitWorldMapWindow()
    if Cartographer or METAMAP_TITLE then return end

    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)
        WorldMapFrame:SetScale(.85)
        WorldMapFrame:SetAlpha(1)
        WorldMapFrame:SetFrameStrata("FULLSCREEN_DIALOG")
    end)

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

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

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

    ApplyWorldMapWindowLayout()

    -- WorldMapTooltip: raw textures on a child frame (SetBackdrop is unreliable)
    if WorldMapTooltip and not WorldMapTooltip._nanamiBG then
        WorldMapTooltip._nanamiBG = true

        local wmtBgFrame = CreateFrame("Frame", nil, WorldMapTooltip)
        wmtBgFrame:SetAllPoints(WorldMapTooltip)
        wmtBgFrame:SetFrameLevel(math.max(0, WorldMapTooltip:GetFrameLevel()))

        local bg = wmtBgFrame:CreateTexture(nil, "BACKGROUND")
        bg:SetTexture("Interface\\Buttons\\WHITE8X8")
        bg:SetVertexColor(0.05, 0.05, 0.05, 1)
        bg:SetAllPoints(wmtBgFrame)

        local function MakeEdge(p1, r1, p2, r2, w, h)
            local t = wmtBgFrame:CreateTexture(nil, "BORDER")
            t:SetTexture("Interface\\Buttons\\WHITE8X8")
            t:SetVertexColor(0.25, 0.25, 0.25, 1)
            t:SetPoint(p1, WorldMapTooltip, r1)
            t:SetPoint(p2, WorldMapTooltip, r2)
            if w then t:SetWidth(w) end
            if h then t:SetHeight(h) end
        end
        MakeEdge("TOPLEFT","TOPLEFT","TOPRIGHT","TOPRIGHT", nil, 1)
        MakeEdge("BOTTOMLEFT","BOTTOMLEFT","BOTTOMRIGHT","BOTTOMRIGHT", nil, 1)
        MakeEdge("TOPLEFT","TOPLEFT","BOTTOMLEFT","BOTTOMLEFT", 1, nil)
        MakeEdge("TOPRIGHT","TOPRIGHT","BOTTOMRIGHT","BOTTOMRIGHT", 1, nil)
    end
end

--------------------------------------------------------------------------------
-- Cooldown Numbers
-- Display remaining duration as text on every cooldown frame (>= 2 sec).
--------------------------------------------------------------------------------
local function TimeConvert(remaining)
    local color = "|cffffffff"
    if remaining < 5 then
        color = "|cffff5555"
    elseif remaining < 10 then
        color = "|cffffff55"
    end

    if remaining < 60 then
        return color .. math.ceil(remaining)
    elseif remaining < 3600 then
        return color .. math.ceil(remaining / 60) .. "m"
    elseif remaining < 86400 then
        return color .. math.ceil(remaining / 3600) .. "h"
    else
        return color .. math.ceil(remaining / 86400) .. "d"
    end
end

local function CooldownOnUpdate()
    local parent = this:GetParent()
    if not parent then this:Hide() return end

    if not this.tick then this.tick = GetTime() + 0.1 end
    if this.tick > GetTime() then return end
    this.tick = GetTime() + 0.1

    this:SetAlpha(parent:GetAlpha())

    if this.start < GetTime() then
        local remaining = this.duration - (GetTime() - this.start)
        if remaining > 0 then
            this.text:SetText(TimeConvert(remaining))
        else
            this:Hide()
        end
    else
        local time = time()
        local startupTime = time - GetTime()
        local cdTime = (2 ^ 32) / 1000 - this.start
        local cdStartTime = startupTime - cdTime
        local cdEndTime = cdStartTime + this.duration
        local remaining = cdEndTime - time
        if remaining >= 0 then
            this.text:SetText(TimeConvert(remaining))
        else
            this:Hide()
        end
    end
end

local function IsActionBarButtonName(name)
    if not name then return false end
    return string.find(name, "^ActionButton%d+$")
        or string.find(name, "^BonusActionButton%d+$")
        or string.find(name, "^MultiBarBottomLeftButton%d+$")
        or string.find(name, "^MultiBarBottomRightButton%d+$")
        or string.find(name, "^MultiBarLeftButton%d+$")
        or string.find(name, "^MultiBarRightButton%d+$")
        or string.find(name, "^PetActionButton%d+$")
        or string.find(name, "^ShapeshiftButton%d+$")
end

local function UpdateActionBarCooldownMask(cooldown)
    if not cooldown then return end
    if cooldown.cooldownmask then
        cooldown.cooldownmask:SetAllPoints(cooldown)
        cooldown.cooldownmask:SetFrameStrata(cooldown:GetFrameStrata())
        cooldown.cooldownmask:SetFrameLevel(cooldown:GetFrameLevel() + 1)
    end
    if cooldown.cooldowntext then
        cooldown.cooldowntext:SetAllPoints(cooldown)
        cooldown.cooldowntext:SetFrameStrata(cooldown:GetFrameStrata())
        cooldown.cooldowntext:SetFrameLevel(cooldown:GetFrameLevel() + 2)
    end
end

local function CreateCoolDown(cooldown, start, duration)
    if not cooldown then return end
    local parent = cooldown:GetParent()
    if not parent then return end
    if cooldown.readable then return end

    local parentname = parent and parent.GetName and parent:GetName()
    parentname = parentname or "UnknownCooldownFrame"

    cooldown.cooldowntext = CreateFrame("Frame", parentname .. "NanamiCDText", cooldown)
    cooldown.cooldowntext:SetAllPoints(cooldown)
    cooldown.cooldowntext:SetFrameStrata(cooldown:GetFrameStrata())
    cooldown.cooldowntext:SetFrameLevel(cooldown:GetFrameLevel() + 2)
    cooldown.cooldowntext.text = cooldown.cooldowntext:CreateFontString(
        parentname .. "NanamiCDFont", "OVERLAY")

    local isActionBar = IsActionBarButtonName(parentname)
    local size = parent:GetHeight() or 0
    size = size > 0 and size * 0.64 or 12
    size = size > 14 and 14 or size

    if isActionBar then
        local bigSize = size * 1.22
        if bigSize < 13 then bigSize = 13 end
        if bigSize > 18 then bigSize = 18 end
        cooldown.cooldowntext.text:SetFont(STANDARD_TEXT_FONT, bigSize, "THICKOUTLINE")
    else
        cooldown.cooldowntext.text:SetFont(STANDARD_TEXT_FONT, size, "OUTLINE")
    end
    cooldown.cooldowntext.text:SetDrawLayer("OVERLAY", 7)

    if isActionBar then
        cooldown.cooldownmask = CreateFrame("Frame", parentname .. "NanamiCDMask", cooldown)
        cooldown.cooldownmask:SetAllPoints(cooldown)
        cooldown.cooldownmask:SetFrameStrata(cooldown:GetFrameStrata())
        cooldown.cooldownmask:SetFrameLevel(cooldown:GetFrameLevel() + 1)
        local mask = cooldown.cooldownmask:CreateTexture(nil, "BACKGROUND")
        mask:SetTexture("Interface\\Buttons\\WHITE8X8")
        mask:SetAllPoints(cooldown.cooldownmask)
        mask:SetDrawLayer("BACKGROUND", 0)
        mask:SetVertexColor(0, 0, 0, 0.45)
        cooldown.cooldownmask.mask = mask
        cooldown.cooldowntext.text:SetPoint("CENTER", cooldown.cooldowntext, "CENTER", 0, 0)
    else
        cooldown.cooldowntext.text:SetPoint("CENTER", cooldown.cooldowntext, "CENTER", 0, 0)
    end

    cooldown.cooldowntext:SetScript("OnUpdate", CooldownOnUpdate)
end

local function SetCooldown(frame, start, duration, enable)
    if not frame then return end
    if frame.noCooldownCount then return end

    if not duration or duration < 2 then
        if frame.cooldowntext then
            frame.cooldowntext:Hide()
        end
        if frame.cooldownmask then
            frame.cooldownmask:Hide()
        end
        return
    end

    if not frame.cooldowntext then
        CreateCoolDown(frame, start, duration)
    end

    if frame.cooldowntext then
        UpdateActionBarCooldownMask(frame)
        if start > 0 and duration > 0 and (not enable or enable > 0) then
            if frame.cooldownmask then frame.cooldownmask:Show() end
            frame.cooldowntext:Show()
        else
            if frame.cooldownmask then frame.cooldownmask:Hide() end
            frame.cooldowntext:Hide()
        end
        frame.cooldowntext.start = start
        frame.cooldowntext.duration = duration
    end
end

local function InitCooldownNumbers()
    _hooksecurefunc("CooldownFrame_SetTimer", SetCooldown)
end

--------------------------------------------------------------------------------
-- Quest Watch Timed Quest Countdown
-- Append remaining time for timed quests in the watched quest list (outside
-- quest detail UI), so players can see countdown directly on the main HUD.
--------------------------------------------------------------------------------
local function FormatQuestCountdown(seconds)
    local s = tonumber(seconds) or 0
    if s <= 0 then return "0s" end

    local h = math.floor(s / 3600)
    local m = math.floor(math.mod(s, 3600) / 60)
    local sec = math.floor(math.mod(s, 60))

    if h > 0 then
        return string.format("%dh %02dm %02ds", h, m, sec)
    elseif m > 0 then
        return string.format("%dm %02ds", m, sec)
    end
    return string.format("%ds", sec)
end

local function InitQuestWatchCountdown()
    if not QuestWatch_Update or not GetNumQuestWatches or not GetQuestLogTitle
        or not GetQuestIndexForWatch or not GetQuestLogTimeLeft then
        return
    end

    local function StripNanamiCountdown(text)
        if not text then return text end
        return string.gsub(text, " |cffffcc00%[剩余: [^%]]+%]|r$", "")
    end

    local applying = false
    local function ApplyQuestWatchCountdown()
        if applying then return end
        applying = true

        local timedByTitle = {}
        local watchCount = tonumber(GetNumQuestWatches()) or 0
        for i = 1, watchCount do
            local questIndex = GetQuestIndexForWatch(i)
            if questIndex and questIndex > 0 then
                local title, level, tag, isHeader = GetQuestLogTitle(questIndex)
                if title and not isHeader then
                    local timeLeft = GetQuestLogTimeLeft(questIndex)
                    if timeLeft and timeLeft > 0 then
                        timedByTitle[title] = FormatQuestCountdown(timeLeft)
                    end
                end
            end
        end

        local lineCount = tonumber(QUESTWATCHLINES) or 0
        for i = 1, lineCount do
            local fs = _G["QuestWatchLine" .. i]
            if fs and fs.GetText and fs.SetText then
                local text = fs:GetText()
                if text and text ~= "" then
                    local clean = StripNanamiCountdown(text)
                    local timerText = timedByTitle[clean]
                    if timerText then
                        fs:SetText(clean .. " |cffffcc00[剩余: " .. timerText .. "]|r")
                    elseif clean ~= text then
                        fs:SetText(clean)
                    end
                end
            end
        end

        applying = false
    end

    _hooksecurefunc("QuestWatch_Update", ApplyQuestWatchCountdown)

    local ticker = CreateFrame("Frame", "NanamiQuestWatchCountdownTicker")
    ticker._e = 0
    ticker:SetScript("OnUpdate", function()
        this._e = (this._e or 0) + (arg1 or 0)
        if this._e < 1.0 then return end
        this._e = 0
        if (tonumber(GetNumQuestWatches()) or 0) > 0 then
            pcall(ApplyQuestWatchCountdown)
        end
    end)
end

--------------------------------------------------------------------------------
-- Dark UI
-- Turns the entire interface into darker colors by applying vertex color
-- tinting to all UI textures recursively.
--------------------------------------------------------------------------------
local function HookAddonOrVariable(addon, func)
    local lurker = CreateFrame("Frame", nil)
    lurker.func = func
    lurker:RegisterEvent("ADDON_LOADED")
    lurker:RegisterEvent("VARIABLES_LOADED")
    lurker:RegisterEvent("PLAYER_ENTERING_WORLD")
    lurker:SetScript("OnEvent", function()
        if IsAddOnLoaded(addon) or getfenv(0)[addon] then
            this:func()
            this:UnregisterAllEvents()
        end
    end)
end

local borderBackdrop = {
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    tile = true, tileSize = 8, edgeSize = 16,
    insets = { left = 0, right = 0, top = 0, bottom = 0 }
}

local function AddBorder(frame, inset, color)
    if not frame then return end
    if frame.NanamiBorder then return frame.NanamiBorder end

    local top, right, bottom, left
    if type(inset) == "table" then
        top, right, bottom, left = unpack(inset)
        left, bottom = -left, -bottom
    end

    frame.NanamiBorder = CreateFrame("Frame", nil, frame)
    frame.NanamiBorder:SetPoint("TOPLEFT", frame, "TOPLEFT",
        (left or -inset), (top or inset))
    frame.NanamiBorder:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT",
        (right or inset), (bottom or -inset))
    frame.NanamiBorder:SetBackdrop(borderBackdrop)
    if color then
        frame.NanamiBorder:SetBackdropBorderColor(color.r, color.g, color.b, 1)
    end
    return frame.NanamiBorder
end

local darkColor = { r = .3, g = .3, b = .3, a = .9 }

local darkBlacklist = {
    ["Solid Texture"] = true, ["WHITE8X8"] = true, ["StatusBar"] = true,
    ["BarFill"] = true, ["Portrait"] = true, ["Button"] = true,
    ["Icon"] = true, ["AddOns"] = true, ["StationeryTest"] = true,
    ["TargetDead"] = true, ["^KeyRing"] = true, ["GossipIcon"] = true,
    ["WorldMap\\(.+)\\"] = true, ["PetHappiness"] = true,
    ["Elite"] = true, ["Rare"] = true, ["ColorPickerWheel"] = true,
    ["ComboPoint"] = true, ["Skull"] = true,
    ["battlenetworking0"] = true, ["damage"] = true,
    ["tank"] = true, ["healer"] = true,
}

local darkRegionSkips = {
    ["ColorPickerFrame"] = { [15] = true }
}

local darkBackgrounds = {
    ["^SpellBookFrame$"] = { 325, 355, 17, -74 },
    ["^ItemTextFrame$"] = { 300, 355, 24, -74 },
}

local darkBorders = {
    ["ShapeshiftButton"] = 3, ["BuffButton"] = 3,
    ["TargetFrameBuff"] = 3, ["TempEnchant"] = 3,
    ["SpellButton"] = 3, ["SpellBookSkillLineTab"] = 3,
    ["ActionButton%d+$"] = 3, ["MultiBar(.+)Button%d+$"] = 3,
    ["KeyRingButton"] = 2,
    ["ActionBarUpButton"] = -3, ["ActionBarDownButton"] = -3,
    ["Character(.+)Slot$"] = 3, ["Inspect(.+)Slot$"] = 3,
    ["ContainerFrame(.+)Item"] = 3, ["MainMenuBarBackpackButton$"] = 3,
    ["CharacterBag(.+)Slot$"] = 3, ["ChatFrame(.+)Button"] = -2,
    ["PetFrameHappiness"] = 2, ["MicroButton"] = { -21, 0, 0, 0 },
}

local darkAddonFrames = {
    ["Blizzard_TalentUI"] = { "TalentFrame" },
    ["Blizzard_AuctionUI"] = { "AuctionFrame", "AuctionDressUpFrame" },
    ["Blizzard_CraftUI"] = { "CraftFrame" },
    ["Blizzard_InspectUI"] = { "InspectPaperDollFrame", "InspectHonorFrame", "InspectFrameTab1", "InspectFrameTab2" },
    ["Blizzard_MacroUI"] = { "MacroFrame", "MacroPopupFrame" },
    ["Blizzard_RaidUI"] = { "ReadyCheckFrame" },
    ["Blizzard_TradeSkillUI"] = { "TradeSkillFrame" },
    -- ClassTrainerFrame replaced by TrainerUI.lua
}

local function IsDarkBlacklisted(texture)
    local name = texture:GetName()
    local tex = texture:GetTexture()
    if not tex then return true end

    if name then
        for entry in pairs(darkBlacklist) do
            if string.find(name, entry, 1) then return true end
        end
    end

    for entry in pairs(darkBlacklist) do
        if string.find(tex, entry, 1) then return true end
    end
    return nil
end

local function AddSpecialBackground(frame, w, h, x, y)
    frame.NanamiMaterial = frame.NanamiMaterial or frame:CreateTexture(nil, "OVERLAY")
    frame.NanamiMaterial:SetTexture("Interface\\Stationery\\StationeryTest1")
    frame.NanamiMaterial:SetWidth(w)
    frame.NanamiMaterial:SetHeight(h)
    frame.NanamiMaterial:SetPoint("TOPLEFT", frame, x, y)
    frame.NanamiMaterial:SetVertexColor(.8, .8, .8)
end

local darkFrameSkips = {
    ["^SFramesChat"] = true,
    ["^SFramesPlayer"] = true,
    ["^SFramesTarget"] = true,
    ["^SFramesParty"] = true,
    ["^SFramesRaid"] = true,
    ["^SFramesMBuff"] = true,
    ["^SFramesMDebuff"] = true,
    ["^GameTooltip"] = true,
}

local function DarkenFrame(frame, r, g, b, a)
    if not r and not g and not b then
        r, g, b, a = darkColor.r, darkColor.g, darkColor.b, darkColor.a
    end

    local fname = frame and frame.GetName and frame:GetName()
    if fname then
        for pattern in pairs(darkFrameSkips) do
            if string.find(fname, pattern) then return end
        end
    end

    if frame and frame.GetChildren then
        for _, child in pairs({ frame:GetChildren() }) do
            DarkenFrame(child, r, g, b, a)
        end
    end

    if frame and frame.GetRegions then
        local name = frame.GetName and frame:GetName()

        if frame.SetBackdropBorderColor then
            frame:SetBackdropBorderColor(darkColor.r, darkColor.g, darkColor.b, darkColor.a)
        end

        for pattern, inset in pairs(darkBackgrounds) do
            if name and string.find(name, pattern) then
                AddSpecialBackground(frame, inset[1], inset[2], inset[3], inset[4])
            end
        end

        for pattern, inset in pairs(darkBorders) do
            if name and string.find(name, pattern) then
                AddBorder(frame, inset, darkColor)
            end
        end

        for id, region in pairs({ frame:GetRegions() }) do
            if region.SetVertexColor and region:GetObjectType() == "Texture" then
                if name and id and darkRegionSkips[name] and darkRegionSkips[name][id] then
                    -- skip
                elseif region.GetBlendMode and region:GetBlendMode() == "ADD" then
                    -- skip blend textures
                elseif IsDarkBlacklisted(region) then
                    -- skip blacklisted
                else
                    region:SetVertexColor(r, g, b, a)
                end
            end
        end
    end
end

--------------------------------------------------------------------------------
-- StaticPopup Theme Skin
--------------------------------------------------------------------------------
local function InitPopupSkin()
    local popupSkinned = {}
    local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARKai_T.ttf"

    local _A = SFrames.ActiveTheme
    local P = {
        bg     = _A.panelBg or { 0.12, 0.06, 0.10, 0.95 },
        border = _A.panelBorder or { 0.55, 0.30, 0.42, 0.9 },
        btnBg  = _A.btnBg or { 0.18, 0.10, 0.15, 0.94 },
        btnBd  = _A.btnBorder or { 0.50, 0.30, 0.40, 0.80 },
        text   = _A.nameText or { 0.90, 0.88, 0.94 },
    }

    local function HidePopupTex(tex)
        if not tex then return end
        if tex.SetTexture then tex:SetTexture(nil) end
        if tex.SetAlpha then tex:SetAlpha(0) end
        if tex.Hide then tex:Hide() end
    end

    local function SkinButton(btn)
        if not btn then return end
        local regions = { btn:GetRegions() }
        for _, r in ipairs(regions) do
            if r and r.SetTexture 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

        HidePopupTex(btn.GetNormalTexture and btn:GetNormalTexture())
        HidePopupTex(btn.GetPushedTexture and btn:GetPushedTexture())
        HidePopupTex(btn.GetHighlightTexture and btn:GetHighlightTexture())
        HidePopupTex(btn.GetDisabledTexture and btn:GetDisabledTexture())

        local btnName = btn:GetName() or ""
        for _, sfx in ipairs({"Left", "Right", "Middle"}) do
            local t = _G[btnName .. sfx]
            if t then t:SetAlpha(0); t:Hide() end
        end

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

        local fs = btn:GetFontString()
        if fs then
            fs:SetFont(font, 12, "OUTLINE")
            fs:SetTextColor(P.text[1], P.text[2], P.text[3])
        end

        if not btn.nanamiPopupStyled then
            btn.nanamiPopupStyled = true
            local origEnter = btn:GetScript("OnEnter")
            local origLeave = btn:GetScript("OnLeave")

            btn:SetScript("OnEnter", function()
                if origEnter then origEnter() end
                this:SetBackdropColor(_A.btnHoverBg[1], _A.btnHoverBg[2], _A.btnHoverBg[3], _A.btnHoverBg[4])
                if _A.btnHoverBorder then
                    this:SetBackdropBorderColor(_A.btnHoverBorder[1], _A.btnHoverBorder[2], _A.btnHoverBorder[3], _A.btnHoverBorder[4])
                elseif _A.btnHoverBd then
                    this:SetBackdropBorderColor(_A.btnHoverBd[1], _A.btnHoverBd[2], _A.btnHoverBd[3], _A.btnHoverBd[4])
                end
                local t = this:GetFontString()
                if t and _A.btnActiveText then t:SetTextColor(_A.btnActiveText[1], _A.btnActiveText[2], _A.btnActiveText[3]) end
            end)
            btn:SetScript("OnLeave", function()
                if origLeave then origLeave() end
                this:SetBackdropColor(P.btnBg[1], P.btnBg[2], P.btnBg[3], P.btnBg[4])
                this:SetBackdropBorderColor(P.btnBd[1], P.btnBd[2], P.btnBd[3], P.btnBd[4])
                local t = this:GetFontString()
                if t then t:SetTextColor(P.text[1], P.text[2], P.text[3]) end
            end)
            btn:SetScript("OnMouseDown", function()
                if _A.btnDownBg then
                    this:SetBackdropColor(_A.btnDownBg[1], _A.btnDownBg[2], _A.btnDownBg[3], _A.btnDownBg[4])
                end
            end)
            btn:SetScript("OnMouseUp", function()
                this:SetBackdropColor(_A.btnHoverBg[1], _A.btnHoverBg[2], _A.btnHoverBg[3], _A.btnHoverBg[4])
            end)
        end
    end

    local function SkinPopupFrame(frame)
        if not frame then return end
        local frameName = frame:GetName()
        if not frameName then return end

        if not popupSkinned[frameName] then
            popupSkinned[frameName] = true

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

            frame:SetBackdrop({
                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 },
            })
            frame:SetBackdropColor(P.bg[1], P.bg[2], P.bg[3], P.bg[4])
            frame:SetBackdropBorderColor(P.border[1], P.border[2], P.border[3], P.border[4])

            local textFS = _G[frameName .. "Text"]
            if textFS and textFS.SetFont then
                textFS:SetFont(font, 13, "OUTLINE")
                textFS:SetTextColor(P.text[1], P.text[2], P.text[3])
            end

            local editBox = _G[frameName .. "EditBox"]
            if editBox then
                editBox:SetBackdrop({
                    bgFile   = "Interface\\Buttons\\WHITE8X8",
                    edgeFile = "Interface\\Buttons\\WHITE8X8",
                    tile = false, edgeSize = 1,
                    insets = { left = 1, right = 1, top = 1, bottom = 1 },
                })
                editBox:SetBackdropColor(0.05, 0.03, 0.05, 0.9)
                editBox:SetBackdropBorderColor(P.btnBd[1], P.btnBd[2], P.btnBd[3], P.btnBd[4])
                editBox:SetFont(font, 12, "OUTLINE")
                editBox:SetTextColor(P.text[1], P.text[2], P.text[3])
            end

            local moneyFrame = _G[frameName .. "MoneyFrame"]
            if moneyFrame then
                local mRegions = { moneyFrame:GetRegions() }
                for _, r in ipairs(mRegions) do
                    if r and r.SetFont then r:SetFont(font, 12, "OUTLINE") end
                end
            end
        end

        for _, suffix in ipairs({"Button1", "Button2", "Button3"}) do
            local btn = _G[frameName .. suffix]
            if btn then SkinButton(btn) end
        end
    end

    for i = 1, 4 do
        local f = _G["StaticPopup" .. i]
        if f then pcall(SkinPopupFrame, f) end
    end

    if StaticPopup_Show then
        local origShow = StaticPopup_Show
        StaticPopup_Show = function(a1, a2, a3, a4)
            local dialog = origShow(a1, a2, a3, a4)
            if dialog then
                pcall(SkinPopupFrame, dialog)
            end
            return dialog
        end
    end
end

local function InitDarkUI()
    local hookBuffButton_Update = BuffButton_Update
    BuffButton_Update = function(buttonName, index, filter)
        hookBuffButton_Update(buttonName, index, filter)
        local name = buttonName and index and buttonName .. index or this:GetName()
        local original = getfenv(0)[name .. "Border"]
        if original and this.NanamiBorder then
            local r, g, b = original:GetVertexColor()
            this.NanamiBorder:SetBackdropBorderColor(r, g, b, 1)
            original:SetAlpha(0)
        end
    end

    TOOLTIP_DEFAULT_COLOR.r = darkColor.r
    TOOLTIP_DEFAULT_COLOR.g = darkColor.g
    TOOLTIP_DEFAULT_COLOR.b = darkColor.b

    TOOLTIP_DEFAULT_BACKGROUND_COLOR.r = darkColor.r
    TOOLTIP_DEFAULT_BACKGROUND_COLOR.g = darkColor.g
    TOOLTIP_DEFAULT_BACKGROUND_COLOR.b = darkColor.b

    DarkenFrame(UIParent)
    DarkenFrame(WorldMapFrame)
    DarkenFrame(DropDownList1)
    DarkenFrame(DropDownList2)
    DarkenFrame(DropDownList3)

    local bars = { "Action", "BonusAction", "MultiBarBottomLeft",
                   "MultiBarBottomRight", "MultiBarLeft", "MultiBarRight", "Shapeshift" }
    for _, prefix in pairs(bars) do
        for i = 1, NUM_ACTIONBAR_BUTTONS do
            local button = getfenv(0)[prefix .. "Button" .. i]
            local texture = getfenv(0)[prefix .. "Button" .. i .. "NormalTexture"]
            if button and texture then
                texture:SetWidth(60)
                texture:SetHeight(60)
                texture:SetPoint("CENTER", 0, 0)
                AddBorder(button, 3)
            end
        end
    end

    for _, button in pairs({ MinimapZoomOut, MinimapZoomIn }) do
        for _, func in pairs({ "GetNormalTexture", "GetDisabledTexture", "GetPushedTexture" }) do
            if button[func] then
                local tex = button[func](button)
                if tex then
                    tex:SetVertexColor(darkColor.r + .2, darkColor.g + .2, darkColor.b + .2, 1)
                end
            end
        end
    end

    for addon, data in pairs(darkAddonFrames) do
        local skip = false
        if SFramesDB and SFramesDB.enableTradeSkill ~= false then
            if addon == "Blizzard_TradeSkillUI" or addon == "Blizzard_CraftUI" then
                skip = true
            end
        end
        if not skip then
            for _, frameName in pairs(data) do
                local fn = frameName
                HookAddonOrVariable(fn, function()
                    DarkenFrame(getfenv(0)[fn])
                end)
            end
        end
    end

    HookAddonOrVariable("Blizzard_TimeManager", function()
        DarkenFrame(TimeManagerClockButton)
    end)

    HookAddonOrVariable("GameTooltipStatusBarBackdrop", function()
        DarkenFrame(getfenv(0)["GameTooltipStatusBarBackdrop"])
    end)
end

--------------------------------------------------------------------------------
-- Module API
--------------------------------------------------------------------------------
function Tweaks:Initialize()
    local cfg = GetTweaksCfg()

    if cfg.autoStance ~= false then
        local ok, err = pcall(InitAutoStance)
        if not ok then
            DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: AutoStance init failed: " .. tostring(err) .. "|r")
        end
    end

    if cfg.autoDismount ~= false then
        local ok, err = pcall(InitAutoDismount)
        if not ok then
            DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: AutoDismount init failed: " .. tostring(err) .. "|r")
        end
    end

    if cfg.superWoW ~= false then
        local ok, err = pcall(InitSuperWoW)
        if not ok then
            DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: SuperWoW init failed: " .. tostring(err) .. "|r")
        end
    end

    if cfg.turtleCompat ~= false then
        local ok, err = pcall(InitTurtleCompat)
        if not ok then
            DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: TurtleCompat init failed: " .. tostring(err) .. "|r")
        end
    end

    if SFrames.WorldMap and SFrames.WorldMap.initialized then
        -- New WorldMap module has taken over; skip legacy code
    elseif cfg.worldMapWindow ~= false then
        local ok, err = pcall(InitWorldMapWindow)
        if not ok then
            DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: WorldMapWindow init failed: " .. tostring(err) .. "|r")
        end
    end

    if cfg.cooldownNumbers ~= false then
        local ok, err = pcall(InitCooldownNumbers)
        if not ok then
            DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: CooldownNumbers init failed: " .. tostring(err) .. "|r")
        end
    end

    do
        local ok, err = pcall(InitQuestWatchCountdown)
        if not ok then
            DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: QuestWatchCountdown init failed: " .. tostring(err) .. "|r")
        end
    end

    if cfg.darkUI then
        local ok, err = pcall(InitDarkUI)
        if not ok then
            DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: DarkUI init failed: " .. tostring(err) .. "|r")
        end
    end

    do
        local ok, err = pcall(InitPopupSkin)
        if not ok then
            DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: PopupSkin init failed: " .. tostring(err) .. "|r")
        end
    end
end
