-------------------------
-- AiB BuffWatcher V2 - NS Shim
-- Stellt alle NaowhQOL ns.* Abhängigkeiten bereit
-------------------------

AiB = AiB or {}
AiB.BWV2NS = AiB.BWV2NS or {}
local ns = AiB.BWV2NS

-- Forward: alle BWV2-Module werden nach dem Laden hier eingehängt
-- (wird in den Moduldateien gesetzt)
ns.BWV2 = nil
ns.BWV2Categories = nil
ns.BWV2Scanner = nil
ns.BWV2Core = nil
ns.BWV2ReportCard = nil
ns.BWV2BuffDropAlert = nil
ns.BWV2Watchers = nil

-------------------------
-- COLORS
-------------------------
ns.COLORS = {
    BLUE        = "990000",   -- aib red (statt blau)
    DARK_BLUE   = "660000",
    ORANGE      = "ffa900",
    DARK_ORANGE = "f48e00",
    WHITE       = "ffffff",
    GRAY        = "aaaaaa",
    LIGHT_GRAY  = "cccccc",
    SUCCESS     = "00ff00",
    ERROR       = "ff4444",
    GREEN       = "00ff00",
    RED         = "ff0000",
}
local C = ns.COLORS

-------------------------
-- LOCALIZATION
-------------------------
ns.L = {
    -- Common
    COMMON_ADD                  = "Add",
    COMMON_SAVE                 = "Save",
    COMMON_CANCEL               = "Cancel",
    COMMON_RESET_DEFAULTS       = "Reset to Defaults",
    COMMON_SET                  = "Set",
    COMMON_YES                  = "Yes",
    COMMON_NO                   = "No",
    COMMON_EDIT                 = "Edit",
    COMMON_CLASS                = "Class",
    COMMON_UNLOCK               = "Unlock",
    COMMON_RELOAD_UI            = "Reload UI",
    COMMON_LATER                = "Later",
    COMMON_LABEL_NAME           = "Name:",
    COMMON_LABEL_SPELLID        = "Spell ID:",
    COMMON_LABEL_ICON_SIZE      = "Icon Size",
    COMMON_LABEL_TEXT_SIZE      = "Text Size",
    COMMON_LABEL_FONT_SIZE      = "Font Size",
    COMMON_LABEL_TEXT_COLOR     = "Text Color",
    COMMON_LABEL_COLOR          = "Color",
    COMMON_LABEL_ALERT_COLOR    = "Alert Color",
    COMMON_LABEL_ALERT_SOUND    = "Alert Sound:",
    COMMON_LABEL_ENABLE_SOUND   = "Enable Sound",
    COMMON_LABEL_PLAY_SOUND     = "Play Sound",
    COMMON_LABEL_SCALE          = "Scale",
    COMMON_LABEL_AUTO_CLOSE     = "Auto-Close",
    COMMON_SECTION_APPEARANCE   = "APPEARANCE",
    COMMON_SECTION_BEHAVIOR     = "BEHAVIOR",
    COMMON_SECTION_DISPLAY      = "DISPLAY",
    COMMON_SECTION_SETTINGS     = "SETTINGS",
    COMMON_SECTION_SOUND        = "SOUND",
    COMMON_THRESHOLD_DUNGEON    = "Dungeon",
    COMMON_THRESHOLD_RAID       = "Raid",
    COMMON_THRESHOLD_OTHER      = "Other",
    COMMON_ON                   = "ON",
    COMMON_OFF                  = "OFF",
    COMMON_ENABLED              = "Enabled",
    COMMON_DISABLED             = "Disabled",
    COMMON_EXPIRED              = "EXPIRED",
    COMMON_MISSING              = "MISSING",
    COMMON_ERR_ENTRY_REQUIRED   = "Entry required.",
    COMMON_ERR_SPELLID_REQUIRED = "Spell ID required.",
    COMMON_DRAG_TO_MOVE         = "Drag to move",
    COMMON_RIGHT_CLICK_SETTINGS = "Right-click for settings",
    -- BWV2
    BWV2_MODULE_NAME            = "Buff Watcher V2",
    BWV2_TITLE                  = "BUFF WATCHER",
    BWV2_SUBTITLE               = "Raid buff scanner triggered on ready check",
    BWV2_ENABLE                 = "Enable Buff Watcher",
    BWV2_SCAN_NOW               = "Scan Now",
    BWV2_SCAN_HINT              = "or use /aibscan",
    BWV2_SCAN_ON_LOGIN          = "Scan on Login",
    BWV2_CHAT_REPORT            = "Print to Chat",
    BWV2_UNKNOWN                = "Unknown",
    BWV2_ENCHANT_ID_LABEL       = "Enchant ID",
    BWV2_DEFAULT_TAG            = "[Default]",
    BWV2_ADD_SPELL_ID           = "Add Spell ID:",
    BWV2_ADD_ITEM_ID            = "Add Item ID:",
    BWV2_RESTORE_DEFAULTS       = "Restore Defaults",
    BWV2_RESTORE                = "Restore",
    BWV2_DEFAULTS_HIDDEN        = "(Some defaults hidden)",
    BWV2_DISABLED               = "(disabled)",
    BWV2_EXCLUSIVE_ONE          = "(exclusive - one required)",
    BWV2_EXCLUSIVE_REQUIRES     = "(exclusive, requires %s)",
    BWV2_FOOD_BUFF_DETECT       = "Detected by buff icon (all food buffs)",
    BWV2_WEAPON_ENCHANT_DETECT  = "Detected via weapon enchant check",
    BWV2_INVENTORY_DESC         = "Checks if you have these items in your bags. Some items only checked when required class is in group.",
    BWV2_YOU                    = "(You)",
    BWV2_GROUPS_COUNT           = "(%d groups)",
    BWV2_TAG_TARGETED           = "[targeted]",
    BWV2_TAG_WEAPON             = "[weapon]",
    BWV2_EXCLUSIVE              = "(exclusive)",
    BWV2_ADD_GROUP              = "+ Add Group",
    BWV2_SECTION_THRESHOLDS     = "DURATION THRESHOLDS",
    BWV2_THRESHOLD_DESC         = "Minimum remaining duration (minutes) for buffs to be considered active.",
    BWV2_DUNGEON                = "Dungeon:",
    BWV2_RAID                   = "Raid:",
    BWV2_OTHER                  = "Other:",
    BWV2_MIN                    = "min",
    BWV2_SECTION_RAID           = "RAID BUFFS",
    BWV2_SECTION_PRESENCE       = "PRESENCE BUFFS",
    BWV2_SECTION_CONSUMABLES    = "CONSUMABLES",
    BWV2_SECTION_INVENTORY      = "INVENTORY CHECK",
    BWV2_SECTION_CLASS          = "CLASS BUFFS",
    BWV2_SECTION_REPORT_CARD    = "REPORT CARD",
    BWV2_SECTION_BUFF_DROP      = "BUFF DROP ALERT",
    BWV2_GLOW_COLOR             = "Glow Color",
    BWV2_MODAL_EDIT_TITLE       = "Edit Buff Group",
    BWV2_MODAL_ADD_TITLE        = "Add Buff Group",
    BWV2_CLASS                  = "Class:",
    BWV2_SELECT_CLASS           = "Select Class",
    BWV2_GROUP_NAME             = "Group Name:",
    BWV2_CHECK_TYPE             = "Check Type:",
    BWV2_TYPE_SELF              = "Self Buff",
    BWV2_TYPE_TARGETED          = "Targeted (on others)",
    BWV2_TYPE_WEAPON            = "Weapon Enchant",
    BWV2_MIN_REQUIRED           = "Min Required:",
    BWV2_MIN_HINT               = "(0 = all, 1+ = min)",
    BWV2_TALENT_CONDITION       = "Talent Condition:",
    BWV2_SELECT_TALENT          = "Select talent...",
    BWV2_FILTER_TALENTS         = "Type to filter...",
    BWV2_MODE_ACTIVATE          = "Activate when talented",
    BWV2_MODE_SKIP              = "Skip when talented",
    BWV2_SPECS                  = "Specs:",
    BWV2_ALL_SPECS              = "All specs",
    BWV2_DURATION_THRESHOLDS    = "Duration Thresholds:",
    BWV2_THRESHOLD_HINT         = "(min, 0=off)",
    BWV2_SPELL_ENCHANT_IDS      = "Spell/Enchant IDs:",
    BWV2_ERR_SELECT_CLASS       = "Please select a class",
    BWV2_ERR_NAME_REQUIRED      = "Group name is required",
    BWV2_ERR_ID_REQUIRED        = "At least one spell/enchant ID is required",
    BWV2_DELETE                 = "Delete",
    BWV2_AUTO_USE_ITEM          = "Auto-use Items:",
    BWV2_REPORT_TITLE           = "Buff Report",
    BWV2_REPORT_NO_DATA         = "Buff Report (No Data)",
    BWV2_NO_ID                  = "No ID",
    BWV2_NO_SPELL_ID_ADDED      = "No spell ID added",
    BWV2_CLASSIC_DISPLAY        = "Classic Display",
    BWV2_BUFF_DROP_REMINDER     = "Buff Drop Reminder",
    BWV2_BUFF_DROP_REMINDER_DESC= "Alert when a buff from your last scan expires. Auto-dismisses on rebuff.",
    BWV2_BUFF_DROP_INSTANCE_ONLY= "Dungeons / Raids Only",
    BWV2_BUFF_DROP_DISABLE_RESTED = "Hide in Rested Areas",
    BWV2_BUFF_DROP_TEXT         = "Duration Text Font",
    BWV2_RAID_BUFF_ALWAYS_CHECK     = "Always Monitor My Raid Buffs",
    BWV2_RAID_BUFF_ALWAYS_CHECK_DESC= "Alerts when a known raid buff isn't active on you.",
    BWV2_CLASS_BUFF_ALWAYS_CHECK    = "Always Monitor My Class Buffs",
    BWV2_CLASS_BUFF_ALWAYS_CHECK_DESC = "Alerts when a class buff or weapon imbue is missing.",
    BWV2_CONSUMABLE_ALWAYS_CHECK    = "Always Monitor My Consumables",
    BWV2_CONSUMABLE_ALWAYS_CHECK_DESC = "Alerts when a tracked consumable buff isn't active.",
    BWV2_INVENTORY_ALWAYS_CHECK     = "Always Monitor My Inventory",
    BWV2_INVENTORY_ALWAYS_CHECK_DESC= "Alerts when you have 0 of a tracked item (e.g. potions, healthstones).",
    BWV2_FADE_TOOLTIP               = "Seconds before fade when passing (0 = disabled)",
}
local L = ns.L

-------------------------
-- LAYOUT
-------------------------
ns.Layout = {
    ROW_HEIGHT       = 50,
    ROW_START        = -10,
    WIDTH            = 500,
    SLIDER_OFFSET    = 0,
    COLOR_OFFSET     = -10,
    CHECKBOX_OFFSET  = 5,
    DROPDOWN_OFFSET  = 0,
}

function ns.Layout:New(columns, opts)
    opts = opts or {}
    local layout = {
        columns   = columns or 2,
        rowHeight = opts.rowHeight or self.ROW_HEIGHT,
        rowStart  = opts.rowStart  or self.ROW_START,
        width     = opts.width     or self.WIDTH,
        gap       = opts.gap       or 20,
    }
    layout.colWidth     = (layout.width - (layout.gap * (layout.columns - 1))) / layout.columns
    layout.colPositions = {}
    for i = 1, layout.columns do
        local offset = (i > 1) and 20 or 0
        layout.colPositions[i] = (i - 1) * (layout.colWidth + layout.gap) + offset
    end
    function layout:Row(n)  return self.rowStart - ((n-1)*self.rowHeight) end
    function layout:Col(n)  return self.colPositions[n] or 0 end
    function layout:Pos(r,c) return self:Col(c), self:Row(r) end
    function layout:SliderY(r)   return self:Row(r) + ns.Layout.SLIDER_OFFSET end
    function layout:ColorY(r)    return self:Row(r) + ns.Layout.COLOR_OFFSET end
    function layout:CheckboxY(r) return self:Row(r) + ns.Layout.CHECKBOX_OFFSET end
    function layout:DropdownY(r) return self:Row(r) + ns.Layout.DROPDOWN_OFFSET end
    function layout:Height(rows) return (rows * self.rowHeight) + 10 end
    return layout
end

-------------------------
-- HELPERS
-------------------------
ns.IsPlayerSpell = IsPlayerSpell or (C_SpellBook and C_SpellBook.PlayerHasSpell) or function() return false end

function ns.DefaultFontPath()
    return "Fonts\\FRIZQT__.TTF"
end

-- ZoneUtil shim
ns.ZoneUtil = {
    _instanceType = "none",
    _inMythicPlus = false,
    _callbacks    = {},
}
function ns.ZoneUtil.IsInInstance()
    return ns.ZoneUtil._instanceType ~= "none"
end
function ns.ZoneUtil.IsInMythicPlus()
    return ns.ZoneUtil._inMythicPlus == true
end
function ns.ZoneUtil.RegisterCallback(key, fn)
    if type(key)=="string" and type(fn)=="function" then
        ns.ZoneUtil._callbacks[key] = fn
    end
end

-- DisplayUtils shim
ns.DisplayUtils = {}
ns.DisplayUtils.FRAME_BACKDROP = {
    bgFile   = [[Interface\Buttons\WHITE8x8]],
    edgeFile = [[Interface\Buttons\WHITE8x8]],
    edgeSize = 1,
    insets   = { left = 1, right = 1, top = 1, bottom = 1 },
}
function ns.DisplayUtils.SetFrameUnlocked(frame, unlocked)
    if not frame then return end
    if unlocked then
        frame:SetBackdrop(ns.DisplayUtils.FRAME_BACKDROP)
        frame:SetBackdropColor(0.1, 0.1, 0.1, 0.95)
        frame:SetBackdropBorderColor(0.6, 0, 0, 1)
    end
end
function ns.DisplayUtils.CanReadAuras()
    local a = C_UnitAuras.GetAuraDataByIndex("player",1,"HELPFUL")
    if not a then return true end
    local ok = pcall(function() return a.spellId==0 end)
    return ok
end
function ns.DisplayUtils.CanReadGroupAuras()
    if not ns.DisplayUtils.CanReadAuras() then return false end
    local n = GetNumGroupMembers()
    if n<=1 then return true end
    for i=1,n do
        local u = IsInRaid() and ("raid"..i) or (i==1 and "player" or ("party"..(i-1)))
        local a = C_UnitAuras.GetAuraDataByIndex(u,1,"HELPFUL")
        if a then
            local ok = pcall(function() return a.spellId==0 end)
            if not ok then return false end
        end
    end
    return true
end

-- Media stub (ersetzt SharedMedia/LibSharedMedia)
ns.Media = {
    ResolveFont = function(name)
        -- Bekannte Font-Namen auf Pfade mappen
        local map = {
            ["Friz Quadrata TT"] = "Fonts\\FRIZQT__.TTF",
            ["Arial Narrow"]     = "Fonts\\ARIALN.TTF",
            ["Morpheus"]         = "Fonts\\MORPHEUS.TTF",
            ["Skurri"]           = "Fonts\\skurri.TTF",
        }
        return map[name] or name or "Fonts\\FRIZQT__.TTF"
    end,
}

-- SettingsIO stub (no-op — aib hat kein SettingsIO)
ns.SettingsIO = { MarkDirty = function() end }

-------------------------
-- WIDGETS
-------------------------
ns.Widgets = {}
local W = ns.Widgets

local BR,BG,BB = 0.6,  0.0,  0.0   -- aib red (border/primary)
local OR,OG,OB = 1.00, 0.66, 0.00  -- orange (hover/active)
local DR,DG,DB = 0.10, 0.10, 0.10  -- dark bg

function W.Colorize(text, hexColor)
    return "|cff" .. hexColor .. text .. "|r"
end

function W.StyleCheckbox(cb)
    if not cb then return end
    local n = cb:GetNormalTexture()
    if n then n:SetVertexColor(OR,OG,OB,1) end
    local h = cb:GetHighlightTexture()
    if h then h:SetVertexColor(OR,OG,OB,0.4) end
end

local function WalkSetEnabled(frame, enabled)
    if frame.Enable and frame.Disable then
        if enabled then frame:Enable() else frame:Disable() end
    end
    for _, child in ipairs({frame:GetChildren()}) do
        WalkSetEnabled(child, enabled)
    end
end

local function RegisterDisableState(parent, fn, apply)
    parent.__disableRegistrations = parent.__disableRegistrations or {}
    tinsert(parent.__disableRegistrations, {fn=fn, apply=apply})
    apply(fn())
end

function W:CreateButton(parent, opts)
    opts = opts or {}
    local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    local lbl = btn:CreateFontString(nil,"OVERLAY",opts.font or "GameFontNormalSmall")
    lbl:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
    lbl:SetPoint("CENTER",0,0)
    lbl:SetText(opts.text or "")
    lbl:SetTextColor(1,1,1,1)
    btn.Text = lbl
    local h = opts.height or 24
    -- Nach SetFont+SetText klappt GetUnboundedStringWidth zuverlässig
    local tw = lbl:GetUnboundedStringWidth()
    local w = opts.width or math.max((tw and tw > 2) and (tw + 24) or (#(opts.text or "") * 7 + 24), 40)
    btn:SetSize(w, h)
    btn:SetBackdrop({ bgFile=[[Interface\Buttons\WHITE8x8]], edgeFile=[[Interface\Buttons\WHITE8x8]], edgeSize=1, insets={left=1,right=1,top=1,bottom=1} })
    local function norm(s) s:SetBackdropColor(DR,DG,DB,0.95); s:SetBackdropBorderColor(BR,BG,BB,0.7); s.Text:SetTextColor(1,1,1,1) end
    local function hover(s) s:SetBackdropColor(0.12,0.10,0.08,0.98); s:SetBackdropBorderColor(OR,OG,OB,0.9); s.Text:SetTextColor(1,0.9,0.8,1) end
    norm(btn)
    btn.isHovered = false
    btn:SetScript("OnEnter", function(s) s.isHovered=true; hover(s) end)
    btn:SetScript("OnLeave", function(s) s.isHovered=false; norm(s) end)
    btn:HookScript("OnDisable", function(s) s:SetBackdropColor(0.1,0.1,0.1,0.6); s:SetBackdropBorderColor(0.3,0.3,0.3,0.5); s.Text:SetTextColor(0.5,0.5,0.5,1) end)
    btn:HookScript("OnEnable", function(s) norm(s) end)
    if opts.onClick then btn:SetScript("OnClick", opts.onClick) end
    if opts.x or opts.y then btn:SetPoint("TOPLEFT", opts.x or 0, opts.y or 0) end
    function btn:SetText(t) self.Text:SetText(t) end
    function btn:GetText() return self.Text:GetText() end
    return btn
end

-- CreateCheckbox (UICheckButtonTemplate — konsistent mit restlichem aib-Theme)
function W:CreateCheckbox(parent, opts)
    opts = opts or {}
    local font = opts.font or (opts.isMaster and "GameFontNormal" or "GameFontNormalSmall")
    local cb = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
    cb:SetSize(24, 24)
    if opts.x or opts.y then cb:SetPoint("TOPLEFT", opts.x or 0, opts.y or 0) end
    local lbl = cb:CreateFontString(nil,"OVERLAY",font)
    lbl:SetPoint("LEFT",cb,"RIGHT",5,0)
    lbl:SetText(opts.label or "")
    lbl:SetTextColor(1,1,1,1)
    cb.Text = lbl
    if opts.db and opts.key then cb:SetChecked(opts.db[opts.key]) end
    if opts.checked ~= nil then cb:SetChecked(opts.checked) end
    cb:SetScript("OnClick", function(s)
        local v = s:GetChecked() and true or false
        if opts.db and opts.key then opts.db[opts.key] = v end
        if opts.onChange then opts.onChange(v) end
        PlaySound(v and SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON or SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF)
    end)
    if opts.tooltip then
        cb:SetScript("OnEnter", function(s)
            GameTooltip:SetOwner(s,"ANCHOR_RIGHT")
            GameTooltip:SetText(opts.label,1,1,1)
            GameTooltip:AddLine(opts.tooltip,nil,nil,nil,true)
            GameTooltip:Show()
        end)
        cb:SetScript("OnLeave", function() GameTooltip:Hide() end)
    end
    if opts.disableif then RegisterDisableState(parent, opts.disableif, function(d) if d then cb:Disable() else cb:Enable() end end) end
    return cb
end

-- CreateSlider (OptionsSliderTemplate — konsistent mit aib-Theme)
function W:CreateSlider(parent, opts)
    opts = opts or {}
    local step = opts.step or 1
    local min  = opts.min  or 0
    local max  = opts.max  or 100
    local width = opts.width or 180

    local f = CreateFrame("Frame", nil, parent)
    f:SetSize(width + 60, 50)
    if opts.x or opts.y then f:SetPoint("TOPLEFT", opts.x or 0, opts.y or 0) end

    if opts.label then
        local t = f:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
        t:SetPoint("TOPLEFT",0,0)
        t:SetText(opts.uppercase~=false and opts.label:upper() or opts.label)
        t:SetTextColor(1,0.8,0,1)
        f.title = t
    end

    local s = CreateFrame("Slider", nil, f, "OptionsSliderTemplate")
    s:SetPoint("TOPLEFT", 0, opts.label and -18 or 0)
    s:SetWidth(width)
    s:SetMinMaxValues(min, max)
    s:SetValueStep(step)
    s:SetObeyStepOnDrag(true)
    s:EnableMouseWheel(true)
    s:SetScript("OnMouseWheel", function(self,d) self:SetValue(math.max(min,math.min(max,self:GetValue()+(d*step)))) end)

    -- Werte-Anzeige
    local vt = f:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    vt:SetPoint("LEFT",s,"RIGHT",8,0)
    f.valueText = vt

    local function FormatVal(v)
        if step < 1 then return string.format("%.2f",v) else return string.format("%.0f",v) end
    end

    s:SetScript("OnValueChanged", function(sl,val)
        val = math.floor(val/step+0.5)*step
        vt:SetText(FormatVal(val))
        if opts.db and opts.key then opts.db[opts.key] = val end
        if opts.onChange then opts.onChange(val) end
    end)

    local iv = (opts.db and opts.key and opts.db[opts.key]) or opts.value or min
    s:SetValue(iv)
    vt:SetText(FormatVal(iv))

    f.slider = s
    f.GetValue = function() return s:GetValue() end
    f.SetValue = function(_,v) s:SetValue(v) end

    if opts.disableif then
        RegisterDisableState(parent, opts.disableif, function(d)
            if d then s:Disable(); if f.title then f.title:SetTextColor(0.4,0.4,0.4,1) end
            else s:Enable(); if f.title then f.title:SetTextColor(1,0.8,0,1) end end
        end)
    end
    return f
end

-- CreateColorPicker
function W:CreateColorPicker(parent, opts)
    local btn = self:CreateButton(parent, { text=opts.label or "Farbe", width=opts.width or 140, height=26 })
    btn:SetPoint("TOPLEFT", opts.x or 40, opts.y or 0)
    local prev = CreateFrame("Frame", nil, parent, "BackdropTemplate"); prev:SetSize(26,26); prev:SetPoint("LEFT",btn,"RIGHT",10,0)
    prev:SetBackdrop({ bgFile=[[Interface\Buttons\WHITE8x8]], edgeFile=[[Interface\Buttons\WHITE8x8]], edgeSize=1 }); prev:SetBackdropBorderColor(0,0,0,1)
    local function upd() prev:SetBackdropColor(opts.db[opts.rKey]or 1, opts.db[opts.gKey]or 1, opts.db[opts.bKey]or 1, 1) end
    upd()
    btn:SetScript("OnClick", function()
        ColorPickerFrame:SetupColorPickerAndShow({
            r=opts.db[opts.rKey]or 1, g=opts.db[opts.gKey]or 1, b=opts.db[opts.bKey]or 1,
            swatchFunc=function() local r,g,b=ColorPickerFrame:GetColorRGB(); opts.db[opts.rKey],opts.db[opts.gKey],opts.db[opts.bKey]=r,g,b; upd(); if opts.onChange then opts.onChange(r,g,b) end end,
            cancelFunc=function(p) opts.db[opts.rKey],opts.db[opts.gKey],opts.db[opts.bKey]=p.r,p.g,p.b; upd(); if opts.onChange then opts.onChange(p.r,p.g,p.b) end end,
        })
    end)
    return btn, prev
end

-- CreateFontPicker (vereinfacht — zeigt aktuellen Font-Namen)
function W:CreateFontPicker(parent, x, y, currentFont, onSelect)
    local frame = CreateFrame("Frame", nil, parent); frame:SetSize(260,28); frame:SetPoint("TOPLEFT",x,y)
    local selBtn = CreateFrame("Button", nil, frame, "BackdropTemplate"); selBtn:SetSize(260,26); selBtn:SetPoint("TOPLEFT")
    selBtn:SetBackdrop({ bgFile=[[Interface\Buttons\WHITE8x8]], edgeFile=[[Interface\Buttons\WHITE8x8]], edgeSize=1 })
    selBtn:SetBackdropColor(DR,DG,DB,0.95); selBtn:SetBackdropBorderColor(BR,BG,BB,0.7)
    local txt = selBtn:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall"); txt:SetPoint("LEFT",8,0); txt:SetPoint("RIGHT",-24,0); txt:SetJustifyH("LEFT")
    local shortName = (currentFont or ""):match("([^\\]+)%.") or (currentFont or "Standard")
    txt:SetText(shortName)
    -- Einfaches Dropdown mit gängigen Fonts
    local fonts = { {path="Fonts\\FRIZQT__.TTF",name="Standard"}, {path="Fonts\\ARIALN.TTF",name="Arial"}, {path="Fonts\\MORPHEUS.TTF",name="Morpheus"}, {path="Fonts\\skurri.TTF",name="Skurri"} }
    local panel = CreateFrame("Frame", nil, UIParent, "BackdropTemplate"); panel:SetSize(260, 4+#fonts*22); panel:SetPoint("TOPLEFT",selBtn,"BOTTOMLEFT",0,-2)
    panel:SetBackdrop({ bgFile=[[Interface\Buttons\WHITE8x8]], edgeFile=[[Interface\Buttons\WHITE8x8]], edgeSize=1 })
    panel:SetBackdropColor(0.06,0.06,0.06,0.98); panel:SetBackdropBorderColor(0,0,0,1); panel:SetFrameStrata("FULLSCREEN_DIALOG"); panel:SetFrameLevel(100); panel:Hide()
    for i, fd in ipairs(fonts) do
        local row = CreateFrame("Button", nil, panel); row:SetSize(252,20); row:SetPoint("TOPLEFT",4,-(2+(i-1)*22))
        local rt = row:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall"); rt:SetPoint("LEFT",4,0); rt:SetText(fd.name); rt:SetTextColor(1,1,1,1)
        row:SetScript("OnEnter", function() rt:SetTextColor(OR,OG,OB,1) end)
        row:SetScript("OnLeave", function() rt:SetTextColor(1,1,1,1) end)
        row:SetScript("OnClick", function() txt:SetText(fd.name); panel:Hide(); if onSelect then onSelect(fd.path) end end)
    end
    selBtn:SetScript("OnClick", function() if panel:IsShown() then panel:Hide() else panel:Show() end end)
    panel:SetScript("OnHide", function() end)
    frame.GetSelected = function() return currentFont end
    return frame
end

-- CreateScrollFrame
function W:CreateScrollFrame(parent, contentHeight)
    local sf = CreateFrame("ScrollFrame", nil, parent, "UIPanelScrollFrameTemplate")
    sf:SetPoint("TOPLEFT",0,0); sf:SetPoint("BOTTOMRIGHT",-26,0)
    local sb = sf.ScrollBar
    if sb then sb:ClearAllPoints(); sb:SetPoint("TOPRIGHT",sf,"TOPRIGHT",22,-40); sb:SetPoint("BOTTOMRIGHT",sf,"BOTTOMRIGHT",22,40) end
    local sc = CreateFrame("Frame",nil,sf); sc:SetWidth(sf:GetWidth() or 700); sc:SetHeight(contentHeight or 900); sf:SetScrollChild(sc)
    sf:EnableMouseWheel(true); sf:SetScript("OnMouseWheel",function(s,d) local p=s:GetVerticalScroll(); local mx=sc:GetHeight()-s:GetHeight(); if mx<0 then mx=0 end; s:SetVerticalScroll(math.max(0,math.min(p-(d*23),mx))) end)
    return sf, sc
end

local _panelRegistry = {}
function W:CachedPanel(cache, key, parent, buildFn)
    if cache[key] then cache[key]:Hide() end
    if not cache[key] then
        cache[key] = CreateFrame("Frame", nil, parent)
        buildFn(cache[key])
    end
    -- Immer neu verankern (Parent kann sich beim Tab-Wechsel ändern)
    cache[key]:ClearAllPoints()
    cache[key]:SetParent(parent)
    cache[key]:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, 0)
    cache[key]:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 0, 0)
    cache[key]:Show()
    return cache[key]
end

-- CreatePageHeader
function W:CreatePageHeader(parent, titleParts, subtitle, opts)
    opts = opts or {}
    local title = parent:CreateFontString(nil,"OVERLAY","GameFontNormalHuge"); title:SetPoint("TOP",0,-20)
    if type(titleParts)=="table" then
        local str="" for _,p in ipairs(titleParts) do str=str..W.Colorize(p[1],p[2]) end; title:SetText(str)
    else title:SetText(titleParts) end
    local sub = parent:CreateFontString(nil,"OVERLAY","GameFontNormal"); sub:SetPoint("TOP",title,"BOTTOM",0,-5); sub:SetText(subtitle or "")
    local sep; if opts.separator~=false then sep=parent:CreateTexture(nil,"ARTWORK"); sep:SetColorTexture(0.6,0.0,0.0,0.8); sep:SetSize(400,2); sep:SetPoint("TOP",sub,"BOTTOM",0,-8) end
    return title, sub, sep
end

-- CreateCollapsibleSection
function W:CreateCollapsibleSection(parent, opts)
    opts = opts or {}
    local HEADER_H=28; local SEP_H=1; local PAD=8
    local wrapper = CreateFrame("Frame",nil,parent); wrapper:SetWidth(opts.width or 460)
    local headerBar = CreateFrame("Button",nil,wrapper,"BackdropTemplate"); headerBar:SetHeight(HEADER_H); headerBar:SetPoint("TOPLEFT",0,0); headerBar:SetPoint("TOPRIGHT",0,0)
    headerBar:SetBackdrop({ bgFile=[[Interface\Buttons\WHITE8x8]] }); headerBar:SetBackdropColor(0.10,0.10,0.10,0.7)
    local arrow = headerBar:CreateFontString(nil,"OVERLAY","GameFontNormal"); arrow:SetPoint("LEFT",10,0)
    local headerText = headerBar:CreateFontString(nil,"OVERLAY","GameFontNormalLarge"); headerText:SetPoint("LEFT",arrow,"RIGHT",6,0)
    headerText:SetText(W.Colorize(opts.text or "SECTION", C.ORANGE))
    local sep = wrapper:CreateTexture(nil,"ARTWORK"); sep:SetColorTexture(0.6,0.0,0.0,0.7); sep:SetHeight(SEP_H); sep:SetPoint("TOPLEFT",headerBar,"BOTTOMLEFT",0,0); sep:SetPoint("TOPRIGHT",headerBar,"BOTTOMRIGHT",0,0)
    local content = CreateFrame("Frame",nil,wrapper); content:SetPoint("TOPLEFT",sep,"BOTTOMLEFT",0,-PAD); content:SetPoint("RIGHT",wrapper,"RIGHT",0,0); content:SetHeight(100)
    local isCollapsed = not (opts.startOpen ~= false)
    local function UpdateState()
        arrow:SetText(W.Colorize(isCollapsed and "+" or "-", C.ORANGE))
        if isCollapsed then content:Hide(); wrapper:SetHeight(HEADER_H+SEP_H)
        else content:Show(); wrapper:SetHeight(HEADER_H+SEP_H+PAD+content:GetHeight()) end
        if opts.onCollapse then opts.onCollapse() end
    end
    headerBar:SetScript("OnClick", function() isCollapsed=not isCollapsed; UpdateState() end)
    wrapper.RecalcHeight = function() if isCollapsed then wrapper:SetHeight(HEADER_H+SEP_H) else wrapper:SetHeight(HEADER_H+SEP_H+PAD+content:GetHeight()) end end
    wrapper.SetCollapsed = function(_,c) isCollapsed=c; UpdateState() end
    wrapper.IsCollapsed  = function() return isCollapsed end
    UpdateState(); wrapper.__isSectionWrapper=true; wrapper.__sectionContent=content
    return wrapper, content
end

-- ApplyDisableStates / RegisterDisable / WalkSetEnabled
function W:ApplyDisableStates(frame)
    if not frame.__disableRegistrations then return end
    for _, reg in ipairs(frame.__disableRegistrations) do reg.apply(reg.fn()) end
end
function W:RegisterDisable(parent, fn, apply) RegisterDisableState(parent, fn, apply) end
function W:WalkSetEnabled(frame, enabled) WalkSetEnabled(frame, enabled) end
function W:SetSectionContainerEnabled(container, enabled)
    for _, child in ipairs({container:GetChildren()}) do
        if child.__isSectionWrapper and child.__sectionContent then
            WalkSetEnabled(child.__sectionContent, enabled)
            if enabled then W:ApplyDisableStates(child.__sectionContent) end
        end
    end
end

-------------------------
-- Zone-Events
-------------------------
local zoneFrame = CreateFrame("Frame")
zoneFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
zoneFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
zoneFrame:SetScript("OnEvent", function()
    local _, it  = IsInInstance()
    local diff   = select(3, GetInstanceInfo())
    ns.ZoneUtil._instanceType = it or "none"
    ns.ZoneUtil._inMythicPlus = (it=="party" and diff==8)
    for _, fn in pairs(ns.ZoneUtil._callbacks) do pcall(fn) end
    -- auch BWV2 aktualisieren falls schon geladen
    if ns.BWV2 then
        ns.BWV2._instanceType = ns.ZoneUtil._instanceType
        ns.BWV2._inMythicPlus = ns.ZoneUtil._inMythicPlus
    end
end)
