-- SaveVariables helper for settings persistence  ## Author: Ckraigfriend ## Version: 2.0 ## SavedVariables: ClassPetWatcherDB, SoulstoneTrackerDB
local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
local function SaveVariables()
end
local function GetSelectedFont()
    if not ClassPetWatcherDB or not ClassPetWatcherDB.fontName or not LSM then
        return "Fonts\\FRIZQT__.TTF"
    end
    local font = LSM:Fetch("font", ClassPetWatcherDB.fontName)
    return font or "Fonts\\FRIZQT__.TTF"
end

local function GetSelectedFontFlags()
    if not ClassPetWatcherDB or not ClassPetWatcherDB.fontFlags then
        return "OUTLINE"
    end
    if ClassPetWatcherDB.fontFlags == "NONE" then
        return ""
    end
    return ClassPetWatcherDB.fontFlags
end

local grimoireText

-- Felguard-specific spell IDs (global scope for reuse)
local FELGUARD_ABILITY_IDS = {
    [89751] = true,  -- Felstorm (EN/Global)
    [30213] = true,  -- Legion Strike (EN/Global)
    [89766] = true,  -- Axe Toss (EN/Global)
    [30146] = true,  -- Felguard ability (Korean client)
    [30151] = true,  -- Felguard ability (Korean client, found in error log)
}

-- Felguard NPC IDs (global scope for reuse)
local FELGUARD_NPC_IDS = {
    [17252] = true,
}
-- Localization helper
local function L(key)
    local locale = GetLocale and GetLocale() or "enUS"
    local t = {
        HUNTER_PET_MISSING = {
            deDE = "Jägerbegleiter fehlt!", koKR = "사냥꾼 소환수 없음!", ruRU = "Питомец охотника отсутствует!",
            esES = "¡Falta mascota de cazador!", esMX = "¡Falta mascota de cazador!", frFR = "Familier du chasseur manquant!",
            zhCN = "猎人宠物缺失!", zhTW = "獵人寵物缺失!", itIT = "Famiglio del cacciatore mancante!",
            ptBR = "Mascote do caçador ausente!", enUS = "Hunter Pet Missing!"
        },
        WARLOCK_PET_MISSING = {
            deDE = "Teufelswache fehlt!", koKR = "지옥수호병 없음!", ruRU = "Страж ужаса отсутствует!",
            esES = "¡Falta guardia vil!", esMX = "¡Falta guardia vil!", frFR = "Garde funeste manquant!",
            zhCN = "恶魔卫士缺失!", zhTW = "惡魔守衛缺失!", itIT = "Vilguardia mancante!",
            ptBR = "Guarda Vil ausente!", enUS = "Warlock Pet Missing!"
        },
        DK_PET_MISSING = {
            deDE = "Unheiliges Ghoul fehlt!", koKR = "부정한 구울 없음!", ruRU = "Нечестивый вурдалак отсутствует!",
            esES = "¡Falta necrófago profano!", esMX = "¡Falta necrófago profano!", frFR = "Goule impie manquante!",
            zhCN = "邪恶食尸鬼缺失!", zhTW = "邪惡食屍鬼缺失!", itIT = "Ghoul Empio mancante!",
            ptBR = "Carniçal profano ausente!", enUS = "Unholy Ghoul Missing!"
        },
        MAGE_PET_MISSING = {
            deDE = "Wasserelementar fehlt!", koKR = "물의 정령 없음!", ruRU = "Водный элементаль отсутствует!",
            esES = "¡Falta elemental de agua!", esMX = "¡Falta elemental de agua!", frFR = "Élémentaire d'eau manquant!",
            zhCN = "水元素缺失!", zhTW = "水元素缺失!", itIT = "Elementale d'acqua mancante!",
            ptBR = "Elemental da água ausente!", enUS = "Water Elemental Missing!"
        },
        PET_PASSIVE = {
            deDE = "Begleiter ist auf PASSIV!", koKR = "소환수가 수동 상태입니다!", ruRU = "Питомец в режиме ПАССИВ!",
            esES = "¡Mascota en PASIVO!", esMX = "¡Mascota en PASIVO!", frFR = "Familier en mode PASSIF!",
            zhCN = "宠物处于被动状态!", zhTW = "寵物處於被動狀態!", itIT = "Famiglio impostato su PASSIVO!",
            ptBR = "Mascote está em PASSIVO!", enUS = "Pet is set to PASSIVE!"
        },
        WRONG_PET_FELGUARD = {
            deDE = "FALCHES PET, TEUFELSWACHE BESCHWÖREN!", koKR = "잘못된 소환수, 지옥수호병 소환!",
            ruRU = "НЕВЕРНЫЙ ПИТОМЕЦ, ПРИЗОВИТЕ СТРАЖА УЖАСА!", esES = "MASCOTA INCORRECTA, ¡INVOCA GUARDIA VIL!",
            esMX = "MASCOTA INCORRECTA, ¡INVOCA GUARDIA VIL!", frFR = "MAUVAIS FAMILIER, INVOQUEZ GARDE FUNESTE!",
            zhCN = "错误的宠物，请召唤恶魔卫士!", zhTW = "錯誤的寵物，請召喚惡魔守衛!",
            itIT = "FAMIGLIO ERRATO, EVOCA VILGUARDIA!", ptBR = "MASCOTE ERRADO, EVOQUE O GUARDA VIL!",
            enUS = "WRONG PET SUMM FELGUARD"
        },
    }
    return t[key] and (t[key][locale] or t[key]["enUS"]) or key
end

local function GetNPCID(unit)
    if not issecure() then return nil end
    return UnitCreatureID(unit)
end

local function HasGrimoireOfSacrifice()
    if IsPlayerSpell and IsPlayerSpell(108503) then return true end
    if IsSpellKnown and IsSpellKnown(108503) then return true end
    if C_ClassTalents and C_ClassTalents.GetActiveConfigID and C_Traits and C_Traits.GetNodeIDs then
        local configID = C_ClassTalents.GetActiveConfigID()
        if configID then
            local nodes = C_Traits.GetNodeIDs(configID)
            if nodes then
                for _, nodeID in ipairs(nodes) do
                    local nodeInfo = C_Traits.GetNodeInfo(configID, nodeID)
                    if nodeInfo and nodeInfo.activeEntry and nodeInfo.activeEntry.entryID then
                        local entryInfo = C_Traits.GetEntryInfo(configID, nodeInfo.activeEntry.entryID)
                        if entryInfo and entryInfo.definitionID then
                            if entryInfo.definitionID == 231410 then return true end
                        end
                    end
                end
            end
        end
    end
    return false
end

local ADDON_NAME = "ClassPetWatcher"

local showIcon
local onlyInInstance

------------------------------------------------------------
-- Frame
------------------------------------------------------------
local frame = CreateFrame("Frame", "ClassPetWarningFrame", UIParent)
frame:SetSize(45, 145)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 300)
frame:Hide()

frame:SetMovable(true)
frame:EnableMouse(true)
frame:RegisterForDrag("LeftButton")
frame:SetClampedToScreen(true)
frame.isLocked = true

frame:SetScript("OnDragStart", function(self)
    if not self.isLocked or IsShiftKeyDown() then self:StartMoving() end
end)
frame:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() end)

------------------------------------------------------------
-- Event Frame for Loading
------------------------------------------------------------
local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:SetScript("OnEvent", function(self, event, arg1)
    if arg1 == "ClassPetWatcher" then
        ClassPetWatcherDB = ClassPetWatcherDB or {}
        showIcon = ClassPetWatcherDB.showIcon
        if showIcon == nil then showIcon = true end
        onlyInInstance = ClassPetWatcherDB.onlyInInstance or false
        if not ClassPetWatcherDB.iconSize then ClassPetWatcherDB.iconSize = 54 end
        icon:SetSize(ClassPetWatcherDB.iconSize, ClassPetWatcherDB.iconSize)
        if not ClassPetWatcherDB.textSize then ClassPetWatcherDB.textSize = 32 end
        if ClassPetWatcherDB.fontFlags == nil then ClassPetWatcherDB.fontFlags = "OUTLINE" end
        text:SetFont(GetSelectedFont(), ClassPetWatcherDB.textSize, GetSelectedFontFlags())
        if ClassPetWatcherDB.motionText == nil then ClassPetWatcherDB.motionText = true end
        if ClassPetWatcherDB.showPassiveAlert == nil then ClassPetWatcherDB.showPassiveAlert = true end
        if ClassPetWatcherDB.showPassiveAlertPvP == nil then ClassPetWatcherDB.showPassiveAlertPvP = true end
        -- ── NEW: hide warnings in housing neighborhoods ──────────────────────
        if ClassPetWatcherDB.hideInNeighborhood == nil then ClassPetWatcherDB.hideInNeighborhood = false end
        -- ────────────────────────────────────────────────────────────────────
        print("ClassPetWatcher loaded: showIcon =", showIcon, "onlyInInstance =", onlyInInstance,
              "iconSize =", ClassPetWatcherDB.iconSize, "textSize =", ClassPetWatcherDB.textSize,
              "motionText =", ClassPetWatcherDB.motionText)
        UpdatePetStatus()
    end
end)

------------------------------------------------------------
-- Slash Commands
------------------------------------------------------------
SLASH_CLASSPETLOCK1 = "/petlock"
SlashCmdList.CLASSPETLOCK = function()
    frame.isLocked = not frame.isLocked
    print(frame.isLocked and "ClassPetWatcher locked (hold SHIFT to move)." or "ClassPetWatcher unlocked.")
end

SLASH_CLASSPETINSTANCE1 = "/petinstance"
SlashCmdList.CLASSPETINSTANCE = function()
    onlyInInstance = not onlyInInstance
    ClassPetWatcherDB.onlyInInstance = onlyInInstance
    print(onlyInInstance and "ClassPetWatcher: Alerts only in instances." or "ClassPetWatcher: Alerts everywhere.")
    UpdatePetStatus()
end

SLASH_CLASSPETHIDEICON1 = "/hideicon"
SlashCmdList.CLASSPETHIDEICON = function()
    showIcon = not showIcon
    ClassPetWatcherDB.showIcon = showIcon
    print("Saved showIcon =", ClassPetWatcherDB.showIcon)
    print(showIcon and "ClassPetWatcher: Icon shown." or "ClassPetWatcher: Icon hidden.")
    UpdatePetStatus()
end

-- ── NEW slash command: toggle neighborhood suppression ───────────────────────
SLASH_CLASSPETNEIGHBORHOOD1 = "/pethood"
SlashCmdList.CLASSPETNEIGHBORHOOD = function()
    if not ClassPetWatcherDB then return end
    ClassPetWatcherDB.hideInNeighborhood = not ClassPetWatcherDB.hideInNeighborhood
    print("ClassPetWatcher: Pet warnings " ..
          (ClassPetWatcherDB.hideInNeighborhood and "HIDDEN" or "SHOWN") ..
          " while in a housing neighborhood.")
    UpdatePetStatus()
end
-- ─────────────────────────────────────────────────────────────────────────────

SLASH_CLASSPETOPTIONS1 = "/PETOP"
SlashCmdList.CLASSPETOPTIONS = function()
     ClassPetWatcher_ShowPetOptionsMenu()
end

-- Popup menu for /PETOP
if not EasyMenu then
    if C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.LoadAddOn then
        if not C_AddOns.IsAddOnLoaded("Blizzard_UIDropDownMenu") then
            C_AddOns.LoadAddOn("Blizzard_UIDropDownMenu")
        end
    end
end

ClassPetWatcher_ShowPetOptionsMenu = function()
    if not ClassPetWatcherOptionsFrame then
        -- ── frame is now 580 tall to fit the extra button ───────────────────
        local f = CreateFrame("Frame", "ClassPetWatcherOptionsFrame", UIParent, "BackdropTemplate")
        f:SetSize(300, 580)
        f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
        f: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}})
        f:SetBackdropColor(0,0,0,0.8)
        f:SetFrameStrata("DIALOG")
        f:EnableMouse(true)
        f:SetMovable(true)
        f:RegisterForDrag("LeftButton")
        f:SetScript("OnDragStart", function(self) self:StartMoving() end)
        f:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() end)
        -- Title
        local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        title:SetPoint("TOP", f, "TOP", 0, -10)
        title:SetText("ClassPetWatcher Options")
        -- Button factory
        local function makeButton(text, y, func)
            local b = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
            b:SetSize(160, 24)
            b:SetPoint("TOP", f, "TOP", 0, y)
            b:SetText(text)
            b:SetScript("OnClick", func)
            return b
        end
        f.btn1 = makeButton("Toggle Icon", -40, function()
            showIcon = not showIcon
            ClassPetWatcherDB.showIcon = showIcon
            print("ClassPetWatcher: Icon " .. (showIcon and "shown" or "hidden"))
            UpdatePetStatus()
        end)
        f.btn2 = makeButton("Toggle Instance Only", -70, function()
            onlyInInstance = not onlyInInstance
            ClassPetWatcherDB.onlyInInstance = onlyInInstance
            print("ClassPetWatcher: Alerts " .. (onlyInInstance and "only in instances" or "everywhere"))
            UpdatePetStatus()
        end)
        f.btn3 = makeButton("Lock/Unlock Frame", -100, function()
            frame.isLocked = not frame.isLocked
            print(frame.isLocked and "ClassPetWatcher locked (hold SHIFT to move)." or "ClassPetWatcher unlocked.")
        end)
        f.btn4 = makeButton("Static Text", -130, function()
            ClassPetWatcherDB.motionText = false
            print("ClassPetWatcher: Static text enabled.")
            UpdatePetStatus()
        end)
        f.btn5 = makeButton("Motion Text", -160, function()
            ClassPetWatcherDB.motionText = true
            print("ClassPetWatcher: Motion text enabled.")
            UpdatePetStatus()
        end)
        f.btn6 = makeButton("Turn On/Off Passive Alert", -190, function()
            if not ClassPetWatcherDB then return end
            ClassPetWatcherDB.showPassiveAlert = not ClassPetWatcherDB.showPassiveAlert
            print("ClassPetWatcher: Passive alert " .. (ClassPetWatcherDB.showPassiveAlert and "enabled" or "disabled") .. ".")
            UpdatePetStatus()
        end)
        f.btn7 = makeButton("Passive Alert in PvP", -220, function()
            if not ClassPetWatcherDB then return end
            ClassPetWatcherDB.showPassiveAlertPvP = not ClassPetWatcherDB.showPassiveAlertPvP
            print("ClassPetWatcher: Passive alert in PvP " .. (ClassPetWatcherDB.showPassiveAlertPvP and "enabled" or "disabled") .. ".")
            UpdatePetStatus()
        end)
        -- ── NEW button: hide/show warnings in housing neighborhoods ──────────
        f.btn8 = makeButton("Hide in Neighborhood", -250, function()
            if not ClassPetWatcherDB then return end
            ClassPetWatcherDB.hideInNeighborhood = not ClassPetWatcherDB.hideInNeighborhood
            local state = ClassPetWatcherDB.hideInNeighborhood and "HIDDEN in neighborhoods" or "SHOWN in neighborhoods"
            print("ClassPetWatcher: Pet warnings now " .. state .. ".")
            -- update button label to reflect current state
            f.btn8:SetText(ClassPetWatcherDB.hideInNeighborhood and "Show in Neighborhood" or "Hide in Neighborhood")
            UpdatePetStatus()
        end)
        -- ────────────────────────────────────────────────────────────────────

        -- X exit button
        f.closeBtn = CreateFrame("Button", nil, f, "UIPanelCloseButton")
        f.closeBtn:SetSize(24, 24)
        f.closeBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -4, -4)
        f.closeBtn:SetScript("OnClick", function() f:Hide() end)

        -- Slider for text size  (shifted down 30px to make room for btn8)
        f.textSizeSlider = CreateFrame("Slider", nil, f, "OptionsSliderTemplate")
        f.textSizeSlider:SetOrientation("HORIZONTAL")
        f.textSizeSlider:SetSize(160, 16)
        f.textSizeSlider:SetPoint("TOP", f, "TOP", 0, -295)
        f.textSizeSlider:SetMinMaxValues(16, 48)
        f.textSizeSlider:SetValueStep(1)
        f.textSizeSlider:SetValue(ClassPetWatcherDB and ClassPetWatcherDB.textSize or 32)
        f.textSizeSlider:SetObeyStepOnDrag(true)
        f.textSizeSlider:SetScript("OnValueChanged", function(self, value)
            if not ClassPetWatcherDB then return end
            ClassPetWatcherDB.textSize = value
            text:SetFont(GetSelectedFont(), value, GetSelectedFontFlags())
            UpdatePetStatus()
            print("ClassPetWatcher: Text size set to " .. math.floor(value))
            if not InCombatLockdown() then SaveVariables() end
        end)
        f.textSizeSlider.Text = f.textSizeSlider:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        f.textSizeSlider.Text:SetPoint("BOTTOM", f.textSizeSlider, "TOP", 0, 2)
        f.textSizeSlider.Text:SetText("Text Size")

        -- Slider for icon size  (shifted down 30px)
        f.iconSizeSlider = CreateFrame("Slider", nil, f, "OptionsSliderTemplate")
        f.iconSizeSlider:SetOrientation("HORIZONTAL")
        f.iconSizeSlider:SetSize(160, 16)
        f.iconSizeSlider:SetPoint("TOP", f, "TOP", 0, -320)
        f.iconSizeSlider:SetMinMaxValues(24, 96)
        f.iconSizeSlider:SetValueStep(1)
        f.iconSizeSlider:SetValue(ClassPetWatcherDB and ClassPetWatcherDB.iconSize or 54)
        f.iconSizeSlider:SetObeyStepOnDrag(true)
        f.iconSizeSlider:SetScript("OnValueChanged", function(self, value)
            if not ClassPetWatcherDB then return end
            ClassPetWatcherDB.iconSize = value
            icon:SetSize(value, value)
            UpdatePetStatus()
            print("ClassPetWatcher: Icon size set to " .. math.floor(value))
            if not InCombatLockdown() then SaveVariables() end
        end)
        f.iconSizeSlider.Text = f.iconSizeSlider:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        f.iconSizeSlider.Text:SetPoint("BOTTOM", f.iconSizeSlider, "TOP", 0, 2)
        f.iconSizeSlider.Text:SetText("Icon Size")

        -- Scrollable font picker  (shifted down 30px)
        f.fontDropdownLabel = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        f.fontDropdownLabel:SetPoint("TOP", f, "TOP", 0, -350)
        f.fontDropdownLabel:SetText("Font")
        f.fontDropdown = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
        f.fontDropdown:SetSize(170, 22)
        f.fontDropdown:SetPoint("TOP", f, "TOP", 0, -360)
        f.fontDropdown:SetText((ClassPetWatcherDB and ClassPetWatcherDB.fontName) or "Default")

        f.fontListFrame = CreateFrame("Frame", nil, f, "BackdropTemplate")
        f.fontListFrame:SetPoint("TOP", f.fontDropdown, "BOTTOM", 0, -2)
        f.fontListFrame:SetSize(190, 130)
        f.fontListFrame: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}
        })
        f.fontListFrame:SetBackdropColor(0, 0, 0, 0.9)
        f.fontListFrame:SetFrameStrata("DIALOG")
        f.fontListFrame:Hide()

        f.fontListScroll = CreateFrame("ScrollFrame", nil, f.fontListFrame, "UIPanelScrollFrameTemplate")
        f.fontListScroll:SetPoint("TOPLEFT", 8, -8)
        f.fontListScroll:SetPoint("BOTTOMRIGHT", -28, 8)
        f.fontListContent = CreateFrame("Frame", nil, f.fontListScroll)
        f.fontListContent:SetSize(1, 1)
        f.fontListScroll:SetScrollChild(f.fontListContent)
        f.fontButtons = {}

        local function SelectFont(fontName)
            if not ClassPetWatcherDB then return end
            ClassPetWatcherDB.fontName = fontName
            local font = GetSelectedFont()
            text:SetFont(font, ClassPetWatcherDB.textSize or 32, GetSelectedFontFlags())
            local gText = grimoireText or (ClassPetWatcherOptionsFrame and ClassPetWatcherOptionsFrame._grimoireText)
            if gText and gText.SetFont then gText:SetFont(font, 28, GetSelectedFontFlags()) end
            f.fontDropdown:SetText(fontName)
            f.fontListFrame:Hide()
            UpdatePetStatus()
        end

        local function RebuildFontList()
            if not LSM then return end
            local fonts = LSM:List("font") or {}
            table.sort(fonts)
            for i = 1, #f.fontButtons do f.fontButtons[i]:Hide() end
            for i, fontName in ipairs(fonts) do
                local button = f.fontButtons[i]
                if not button then
                    button = CreateFrame("Button", nil, f.fontListContent)
                    button:SetSize(150, 18)
                    button:SetNormalFontObject("GameFontHighlightSmall")
                    button:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
                    button:GetHighlightTexture():SetBlendMode("ADD")
                    f.fontButtons[i] = button
                end
                button:SetPoint("TOPLEFT", f.fontListContent, "TOPLEFT", 0, -((i - 1) * 18))
                button:SetText(fontName)
                button:SetScript("OnClick", function() SelectFont(fontName) end)
                button:Show()
            end
            f.fontListContent:SetSize(150, math.max(1, #fonts * 18))
        end

        f.fontDropdown:SetScript("OnClick", function()
            if f.fontListFrame:IsShown() then
                f.fontListFrame:Hide()
            else
                f.fontStyleListFrame:Hide()
                RebuildFontList()
                f.fontListFrame:Show()
            end
        end)

        f.fontListFrame:EnableMouseWheel(true)
        f.fontListFrame:SetScript("OnMouseWheel", function(_, delta)
            local current = f.fontListScroll:GetVerticalScroll()
            local maxScroll = math.max(0, f.fontListContent:GetHeight() - f.fontListScroll:GetHeight())
            local nextScroll = math.min(math.max(0, current - (delta * 18)), maxScroll)
            f.fontListScroll:SetVerticalScroll(nextScroll)
        end)

        -- Scrollable font style picker  (shifted down 30px)
        f.fontStyleLabel = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        f.fontStyleLabel:SetPoint("TOP", f, "TOP", 0, -395)
        f.fontStyleLabel:SetText("Font Style")
        f.fontStyleDropdown = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
        f.fontStyleDropdown:SetSize(170, 22)
        f.fontStyleDropdown:SetPoint("TOP", f, "TOP", 0, -405)
        f.fontStyleDropdown:SetText((ClassPetWatcherDB and ClassPetWatcherDB.fontFlags) or "OUTLINE")

        f.fontStyleListFrame = CreateFrame("Frame", nil, f, "BackdropTemplate")
        f.fontStyleListFrame:SetPoint("TOP", f.fontStyleDropdown, "BOTTOM", 0, -2)
        f.fontStyleListFrame:SetSize(190, 130)
        f.fontStyleListFrame: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}
        })
        f.fontStyleListFrame:SetBackdropColor(0, 0, 0, 0.9)
        f.fontStyleListFrame:SetFrameStrata("DIALOG")
        f.fontStyleListFrame:Hide()

        f.fontStyleListScroll = CreateFrame("ScrollFrame", nil, f.fontStyleListFrame, "UIPanelScrollFrameTemplate")
        f.fontStyleListScroll:SetPoint("TOPLEFT", 8, -8)
        f.fontStyleListScroll:SetPoint("BOTTOMRIGHT", -28, 8)
        f.fontStyleListContent = CreateFrame("Frame", nil, f.fontStyleListScroll)
        f.fontStyleListContent:SetSize(1, 1)
        f.fontStyleListScroll:SetScrollChild(f.fontStyleListContent)
        f.fontStyleButtons = {}

        local styleOptions = {
            "NONE", "OUTLINE", "MONOCHROME", "OUTLINE, MONOCHROME",
            "THICKOUTLINE", "THICKOUTLINE, MONOCHROME",
        }

        local function SelectFontStyle(styleName)
            if not ClassPetWatcherDB then return end
            ClassPetWatcherDB.fontFlags = styleName
            local font = GetSelectedFont()
            local flags = GetSelectedFontFlags()
            text:SetFont(font, ClassPetWatcherDB.textSize or 32, flags)
            local gText = grimoireText or (ClassPetWatcherOptionsFrame and ClassPetWatcherOptionsFrame._grimoireText)
            if gText and gText.SetFont then gText:SetFont(font, 28, flags) end
            f.fontStyleDropdown:SetText(styleName)
            f.fontStyleListFrame:Hide()
            UpdatePetStatus()
        end

        local function RebuildFontStyleList()
            for i = 1, #f.fontStyleButtons do f.fontStyleButtons[i]:Hide() end
            for i, styleName in ipairs(styleOptions) do
                local button = f.fontStyleButtons[i]
                if not button then
                    button = CreateFrame("Button", nil, f.fontStyleListContent)
                    button:SetSize(150, 18)
                    button:SetNormalFontObject("GameFontHighlightSmall")
                    button:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
                    button:GetHighlightTexture():SetBlendMode("ADD")
                    f.fontStyleButtons[i] = button
                end
                button:SetPoint("TOPLEFT", f.fontStyleListContent, "TOPLEFT", 0, -((i - 1) * 18))
                button:SetText(styleName)
                button:SetScript("OnClick", function() SelectFontStyle(styleName) end)
                button:Show()
            end
            f.fontStyleListContent:SetSize(150, math.max(1, #styleOptions * 18))
        end

        f.fontStyleDropdown:SetScript("OnClick", function()
            if f.fontStyleListFrame:IsShown() then
                f.fontStyleListFrame:Hide()
            else
                f.fontListFrame:Hide()
                RebuildFontStyleList()
                f.fontStyleListFrame:Show()
            end
        end)

        f.fontStyleListFrame:EnableMouseWheel(true)
        f.fontStyleListFrame:SetScript("OnMouseWheel", function(_, delta)
            local current = f.fontStyleListScroll:GetVerticalScroll()
            local maxScroll = math.max(0, f.fontStyleListContent:GetHeight() - f.fontStyleListScroll:GetHeight())
            local nextScroll = math.min(math.max(0, current - (delta * 18)), maxScroll)
            f.fontStyleListScroll:SetVerticalScroll(nextScroll)
        end)

        ClassPetWatcherOptionsFrame = f
    end

    -- Refresh dropdown labels to current saved values when reopening
    if ClassPetWatcherOptionsFrame.fontDropdown then
        ClassPetWatcherOptionsFrame.fontDropdown:SetText((ClassPetWatcherDB and ClassPetWatcherDB.fontName) or "Default")
    end
    if ClassPetWatcherOptionsFrame.fontListFrame then
        ClassPetWatcherOptionsFrame.fontListFrame:Hide()
    end
    if ClassPetWatcherOptionsFrame.fontStyleDropdown then
        ClassPetWatcherOptionsFrame.fontStyleDropdown:SetText((ClassPetWatcherDB and ClassPetWatcherDB.fontFlags) or "OUTLINE")
    end
    if ClassPetWatcherOptionsFrame.fontStyleListFrame then
        ClassPetWatcherOptionsFrame.fontStyleListFrame:Hide()
    end
    -- Update neighborhood button label to match current saved state
    if ClassPetWatcherOptionsFrame.btn8 then
        local hiding = ClassPetWatcherDB and ClassPetWatcherDB.hideInNeighborhood
        ClassPetWatcherOptionsFrame.btn8:SetText(hiding and "Show in Neighborhood" or "Hide in Neighborhood")
    end
    ClassPetWatcherOptionsFrame:Show()
end

------------------------------------------------------------
-- Icon + Text
------------------------------------------------------------
icon = frame:CreateTexture(nil, "OVERLAY")
icon:SetSize(54, 54)
icon:SetPoint("CENTER", frame, "CENTER", 0, 20)

local blackSquare = frame:CreateTexture(nil, "BACKGROUND")
blackSquare:SetPoint("TOP", icon, "BOTTOM", 0, 35)
blackSquare:SetSize(40, 40)
blackSquare:SetColorTexture(0, 0, 0, 0)

grimoireText = frame:CreateFontString(nil, "OVERLAY")
grimoireText:SetPoint("TOP", icon, "BOTTOM", 0, -40)
grimoireText:SetFont(GetSelectedFont(), 28, GetSelectedFontFlags())
grimoireText:SetTextColor(0.8, 0.2, 1)
grimoireText:SetShadowOffset(1, -1)
grimoireText:SetShadowColor(0, 0, 0, 1)
grimoireText:Hide()
if ClassPetWatcherOptionsFrame then
    ClassPetWatcherOptionsFrame._grimoireText = grimoireText
end

text = frame:CreateFontString(nil, "OVERLAY")
text:SetPoint("CENTER", frame, "CENTER", 0, -40)
local _, classFilename = UnitClass("player")
local classColor = C_ClassColor.GetClassColor(classFilename)
if classColor then text:SetTextColor(classColor.r, classColor.g, classColor.b)
else text:SetTextColor(1, 0, 0) end
text:SetFont(GetSelectedFont(), (ClassPetWatcherDB and ClassPetWatcherDB.textSize) or 32, GetSelectedFontFlags())
text:SetShadowOffset(1, -1)
text:SetShadowColor(0, 0, 0, 1)

------------------------------------------------------------
-- Fade animation
------------------------------------------------------------
local fade = text:CreateAnimationGroup()
fade:SetLooping("REPEAT")
local fadeIn = fade:CreateAnimation("Alpha")
fadeIn:SetOrder(1); fadeIn:SetDuration(1.5); fadeIn:SetFromAlpha(0); fadeIn:SetToAlpha(1)
local fadeOut = fade:CreateAnimation("Alpha")
fadeOut:SetOrder(2); fadeOut:SetDuration(1.5); fadeOut:SetToAlpha(0)

local function PlayFade()
    if ClassPetWatcherDB and ClassPetWatcherDB.motionText then
        if not fade:IsPlaying() then fade:Play() end
    else StopFade() end
end
StopFade = function() if fade:IsPlaying() then fade:Stop(); text:SetAlpha(1) end end

------------------------------------------------------------
-- Constants
------------------------------------------------------------
local PET_PASSIVE_ICON = 132276
local PET_PASSIVE_TEXT = L("PET_PASSIVE")

local PET_INFO = {
    HUNTER     = { [0] = { iconID = 136095,  text = L("HUNTER_PET_MISSING") } },
    WARLOCK    = { [0] = { iconID = 236292,  text = L("WARLOCK_PET_MISSING") } },
    DEATHKNIGHT= { [0] = { iconID = 1100170, text = L("DK_PET_MISSING"),    specID = 252 } },
    MAGE       = { [0] = { iconID = 135862,  text = L("MAGE_PET_MISSING"),  specID = 64  } },
}

local HUNTER_NO_PET = { [466846] = true, [1232995] = true }
local MAGE_WATER_ELEMENTAL_SPELLID = 31687
local DEMONOLOGY_SPEC_ID = 266
local FELGUARD_SPELL_ID = 30146

------------------------------------------------------------
-- Utilities
------------------------------------------------------------
local function IsPetOnPassive()
    if not UnitExists("pet") or not PetHasActionBar() then return false end
    local SLOT_COUNT = NUM_PET_ACTION_SLOTS or 10
    for slot = 1, SLOT_COUNT do
        local name, _, isToken, isActive = GetPetActionInfo(slot)
        if isToken and name == "PET_MODE_PASSIVE" and isActive then return true end
    end
    return false
end

local function PetCheckAllowed(class)
    local zero = PET_INFO[class] and PET_INFO[class][0]
    if not zero then return false end
    if class == "HUNTER" then
        for spellID in pairs(HUNTER_NO_PET) do if IsPlayerSpell(spellID) then return false end end
        return true
    end
    if class == "MAGE" then
        local specIndex = GetSpecialization()
        local specID = specIndex and select(1, GetSpecializationInfo(specIndex))
        if specID ~= 64 then return false end
        if not IsPlayerSpell(MAGE_WATER_ELEMENTAL_SPELLID) then return false end
        return true
    end
    if zero.specID then
        local specIndex = GetSpecialization()
        local specID = specIndex and select(1, GetSpecializationInfo(specIndex))
        return specID == zero.specID
    end
    return true
end

local function IsPlayerMountedOrInVehicleOrTaxi()
    return IsMounted() or UnitOnTaxi("player") or UnitHasVehicleUI("player")
end

local function IsInPvPContext()
    local inInstance, instanceType = IsInInstance()
    if inInstance and (instanceType == "pvp" or instanceType == "arena") then return true end
    if C_PvP and C_PvP.IsWarModeActive and C_PvP.IsWarModeActive() then return true end
    return false
end

-- ── NEW: returns true when the player is inside a housing neighborhood ───────
local function IsInNeighborhood()
    if not C_Housing then return false end
    -- IsInsideHouseOrPlot covers being physically inside a house or on a plot.
    -- IsOnNeighborhoodMap covers being on the neighbourhood map view.
    -- Together they catch all the cases where the player "is in housing".
    local inHouse = C_Housing.IsInsideHouseOrPlot and C_Housing.IsInsideHouseOrPlot()
    local onMap   = C_Housing.IsOnNeighborhoodMap   and C_Housing.IsOnNeighborhoodMap()
    return inHouse or onMap
end
-- ─────────────────────────────────────────────────────────────────────────────

local function unitStringLower(s) if s then return string.lower(tostring(s)) else return "" end end
local function IsPetFelguard()
    if not UnitExists("pet") then return false end
    for i = 1, NUM_PET_ACTION_SLOTS do
        local _, _, _, _, _, _, spellID = GetPetActionInfo(i)
        if spellID and FELGUARD_ABILITY_IDS[spellID] then return true end
    end
    local npcID = GetNPCID("pet")
    if npcID and FELGUARD_NPC_IDS[npcID] then return true end
    if not issecure() then return false end
    local family = UnitCreatureFamily("pet") or ""
    local name   = UnitName("pet") or ""
    local lf = unitStringLower(family)
    local ln = unitStringLower(name)
    local felguardNames = {"felguard","teufelswache","zornw","지옥수호병","지옥수호병 소환","강력한 근접 전투형 투사인 지옥수호병","이직툰","izikthun","страж ужаса","guardia vil"}
    for _, fname in ipairs(felguardNames) do
        if lf:find(fname) or ln:find(fname) then return true end
    end
    return false
end

------------------------------------------------------------
-- Main Update
------------------------------------------------------------
UpdatePetStatus = function()
    -- Grimoire of Sacrifice special case
    if HasGrimoireOfSacrifice() then
        if UnitExists("pet") then
            icon:SetTexture(236292)
            icon:SetShown(showIcon)
            grimoireText:SetText("Grimoire  Alive!")
            grimoireText:Show()
            text:Hide()
            frame:Show(); PlayFade(); return
        else
            grimoireText:Hide()
            text:Hide()
            frame:Hide()
            StopFade()
            return
        end
    else
        grimoireText:Hide()
        text:Show()
    end

    -- hide if player is dead or ghost
    if UnitIsDeadOrGhost("player") then
        frame:Hide(); StopFade(); return
    end

    -- hide while mounted, on a flight path, or in a vehicle
    if IsPlayerMountedOrInVehicleOrTaxi() then
        frame:Hide(); StopFade(); return
    end

    -- ── NEW: hide if in a housing neighborhood and the option is enabled ─────
    if ClassPetWatcherDB and ClassPetWatcherDB.hideInNeighborhood and IsInNeighborhood() then
        frame:Hide(); StopFade(); return
    end
    -- ─────────────────────────────────────────────────────────────────────────

    -- hide if not in instance and onlyInInstance is enabled
    if onlyInInstance and not IsInInstance() then
        frame:Hide(); StopFade(); return
    end

    local _, class = UnitClass("player")
    local info = PET_INFO[class]
    if not info then frame:Hide(); StopFade(); return end

    if ClassPetWatcherDB and ClassPetWatcherDB.iconSize then
        icon:SetSize(ClassPetWatcherDB.iconSize, ClassPetWatcherDB.iconSize)
    end
    if ClassPetWatcherDB and ClassPetWatcherDB.textSize then
        text:SetFont(GetSelectedFont(), ClassPetWatcherDB.textSize, GetSelectedFontFlags())
    end

    if UnitExists("pet") and IsPetOnPassive()
        and (ClassPetWatcherDB == nil or ClassPetWatcherDB.showPassiveAlert ~= false)
        and (ClassPetWatcherDB == nil or ClassPetWatcherDB.showPassiveAlertPvP ~= false or not IsInPvPContext()) then
        icon:SetTexture(PET_PASSIVE_ICON)
        icon:SetShown(showIcon)
        local family = issecure() and UnitCreatureFamily("pet") or nil
        text:SetText((family and (family .. " " .. L("PET_PASSIVE"))) or PET_PASSIVE_TEXT)
        frame:Show(); PlayFade(); return
    end

    if class == "WARLOCK" then
        local specIndex = GetSpecialization()
        local specID = specIndex and select(1, GetSpecializationInfo(specIndex))
        if specID == DEMONOLOGY_SPEC_ID and IsPlayerSpell(FELGUARD_SPELL_ID) then
            if UnitExists("pet") and not IsPetFelguard() then
                icon:SetTexture(info[0] and info[0].iconID or PET_PASSIVE_ICON)
                icon:SetShown(showIcon)
                text:SetText(L("WRONG_PET_FELGUARD"))
                frame:Show(); PlayFade(); return
            end
        end
    end

    if info[0] and PetCheckAllowed(class) and not UnitExists("pet") then
        icon:SetTexture(info[0].iconID)
        icon:SetShown(showIcon)
        text:SetText(info[0].text)
        frame:Show(); PlayFade(); return
    end

    frame:Hide()
    StopFade()
end

------------------------------------------------------------
-- Dismount delay handling
------------------------------------------------------------
local dismountTimer = nil
local DISMOUNT_DELAY = 5.0

local function SchedulePostDismountCheck()
    if dismountTimer and dismountTimer.Cancel then
        dismountTimer:Cancel(); dismountTimer = nil
    end
    dismountTimer = C_Timer.NewTimer(DISMOUNT_DELAY, function()
        dismountTimer = nil
        UpdatePetStatus()
    end)
end

------------------------------------------------------------
-- Events
------------------------------------------------------------
frame:RegisterEvent("ADDON_LOADED")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("UNIT_PET")
frame:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
frame:RegisterEvent("SPELLS_CHANGED")
frame:RegisterEvent("PET_BAR_UPDATE")
frame:RegisterEvent("PET_BAR_UPDATE_COOLDOWN")
frame:RegisterEvent("PLAYER_MOUNT_DISPLAY_CHANGED")
frame:RegisterEvent("UNIT_ENTERED_VEHICLE")
frame:RegisterEvent("UNIT_EXITED_VEHICLE")
frame:RegisterEvent("PLAYER_FLAGS_CHANGED")
frame:RegisterEvent("PLAYER_ALIVE")
frame:RegisterEvent("PLAYER_UNGHOST")

frame:SetScript("OnEvent", function(self, event, arg1, ...)
    if event == "ADDON_LOADED" and arg1 ~= "OrzUI" then return end
    if event == "UNIT_PET" and arg1 ~= "player" then return end
    if event == "UNIT_ENTERED_VEHICLE" and arg1 ~= "player" then return end
    if event == "UNIT_EXITED_VEHICLE" and arg1 ~= "player" then return end
    if event == "PLAYER_FLAGS_CHANGED" and arg1 and arg1 ~= "player" then return end

    --if event == "ADDON_LOADED" then
        --print("|cff00ff00Type  Addon ClassPetWatcher   /petinstance if you wanna see reminders in dungeon or raids or to enable again for solo|r")
    --end

    if event == "PLAYER_MOUNT_DISPLAY_CHANGED" then
        if IsMounted() then
            if dismountTimer and dismountTimer.Cancel then dismountTimer:Cancel(); dismountTimer = nil end
            frame:Hide(); StopFade(); return
        else
            SchedulePostDismountCheck(); return
        end
    end

    if dismountTimer and event ~= "PLAYER_MOUNT_DISPLAY_CHANGED" then
        if dismountTimer and dismountTimer.Cancel then dismountTimer:Cancel(); dismountTimer = nil end
        UpdatePetStatus(); return
    end

    if UnitIsDeadOrGhost("player") then
        frame:Hide(); StopFade(); return
    end

    UpdatePetStatus()
end)