local addonName, FN = ...
local frame = CreateFrame("Frame")

FN_DB = FN_DB or {}
FN = FN or {}
FavoriteNeighborhood = FN

local function Msg(text)
    DEFAULT_CHAT_FRAME:AddMessage("|cff00ff98FavoriteNeighborhood:|r " .. tostring(text))
end
FN.Msg = Msg

local function Trace(_tag, _text)
end
FN.Trace = Trace

local function RefreshOptionsIfVisible()
    if FN and FN.UpdateOptionsFrame and FN.OptionsFrame and FN.OptionsFrame:IsShown() then
        FN.UpdateOptionsFrame()
    end
end
FN.RefreshOptionsIfVisible = RefreshOptionsIfVisible

local function QueueOptionsRefresh(_delay)
    RefreshOptionsIfVisible()
end
FN.QueueOptionsRefresh = QueueOptionsRefresh

local function QueueSettledOptionsRefresh()
    RefreshOptionsIfVisible()
end
FN.QueueSettledOptionsRefresh = QueueSettledOptionsRefresh

local function RequestProgressForCurrentFavorite()
    if FN.RequestNeighborhoodProgress and FN_DB and FN_DB.global then
        FN.RequestNeighborhoodProgress(FN_DB.global)
    end
end
FN.RequestProgressForCurrentFavorite = RequestProgressForCurrentFavorite

local relogRefreshToken = 0
local loginFixInProgress = false
local lastLoginFixAt = 0
local LOGIN_FIX_GUARD_WINDOW = 1.0
local relogBootstrapPending = false
local relogBootstrapRunning = false

local function BuildRelevantProgressRequestOrder()
    local ids = (FN.GetKnownNeighborhoodIDs and FN.GetKnownNeighborhoodIDs()) or {}
    local ordered = {}
    local seen = {}

    local function add(id)
        if not id then return end
        id = tostring(id)
        if seen[id] then return end
        seen[id] = true
        ordered[#ordered + 1] = id
    end

    add(FN.GetActiveNeighborhood and FN.GetActiveNeighborhood() or nil)
    add(FN_DB and FN_DB.global)

    for _, id in ipairs(ids) do
        add(id)
    end

    return ordered
end

local function ScheduleRelogStateRebuild()
    if relogBootstrapRunning then return end
    relogBootstrapRunning = true
    relogBootstrapPending = false

    relogRefreshToken = relogRefreshToken + 1
    local token = relogRefreshToken

    local active = FN.GetActiveNeighborhood and FN.GetActiveNeighborhood() or nil

    local ordered = BuildRelevantProgressRequestOrder()
    local delayStep = 0.12

    for idx, id in ipairs(ordered) do
        C_Timer.After((idx - 1) * delayStep, function()
            if token ~= relogRefreshToken then return end
            if FN.RequestNeighborhoodProgress then
                FN.RequestNeighborhoodProgress(id)
            end
        end)
    end

    local settleDelay = math.max(#ordered, 1) * delayStep + 0.08
    C_Timer.After(settleDelay, function()
        if token ~= relogRefreshToken then
            relogBootstrapRunning = false
            return
        end
        if FN.ApplyLoginFix then FN.ApplyLoginFix() end
        RefreshOptionsIfVisible()
        relogBootstrapRunning = false
    end)
end
FN.ScheduleRelogStateRebuild = ScheduleRelogStateRebuild

local lastCorrectionTarget = nil
local lastCorrectionAt = 0
local CORRECTION_GUARD_WINDOW = 2

local function now()
    if GetTime then return GetTime() end
    return 0
end

local function RememberCorrectionTarget(target)
    lastCorrectionTarget = target and tostring(target) or nil
    lastCorrectionAt = now()
end

local function ClearCorrectionTargetIfReached(active)
    if active and lastCorrectionTarget and tostring(active) == tostring(lastCorrectionTarget) then
        lastCorrectionTarget = nil
        lastCorrectionAt = 0
    end
end

local function WasRecentCorrectionTarget(target)
    if not target or not lastCorrectionTarget then return false end
    if tostring(target) ~= tostring(lastCorrectionTarget) then return false end
    return (now() - lastCorrectionAt) < CORRECTION_GUARD_WINDOW
end

local function CharKey(name, realm)
    return tostring(name) .. " - " .. tostring(realm)
end
FN.CharKey = CharKey

local function PlayerKey()
    return CharKey(UnitName("player"), GetRealmName())
end
FN.PlayerKey = PlayerKey

local function EnsureDB()
    FN_DB.global = FN_DB.global or nil
    FN_DB.neighborhoods = FN_DB.neighborhoods or {}
    FN_DB.chars = FN_DB.chars or {}
    FN_DB.ui = FN_DB.ui or {}
    FN_DB.progress = FN_DB.progress or {}
    FN_DB.config = FN_DB.config or {}
    if FN_DB.config.autoSwitchGlobal == nil then FN_DB.config.autoSwitchGlobal = false end
    FN_DB.config.debug = FN_DB.config.debug or {}
    if FN_DB.config.debug.enabled == nil then FN_DB.config.debug.enabled = false end
    if FN_DB.config.debug.preview == nil then FN_DB.config.debug.preview = "LIVE" end
    if FN_DB.ui.point == nil then FN_DB.ui.point = "CENTER" end
    if FN_DB.ui.x == nil then FN_DB.ui.x = 0 end
    if FN_DB.ui.y == nil then FN_DB.ui.y = 0 end
end
FN.EnsureDB = EnsureDB

local function MigrateCharEntry(entry)
    if type(entry) == "string" then
        return { mode = entry, classID = nil, classFile = nil, fav = nil, lastSeen = nil }
    elseif type(entry) == "table" then
        entry.mode = entry.mode or "G"
        entry.classID = entry.classID or nil
        entry.classFile = entry.classFile or nil
        entry.fav = entry.fav or nil
        entry.lastSeen = entry.lastSeen or nil
        return entry
    else
        return { mode = "G", classID = nil, classFile = nil, fav = nil, lastSeen = nil }
    end
end
FN.MigrateCharEntry = MigrateCharEntry

local function EnsureChar(key)
    EnsureDB()
    FN_DB.chars[key] = MigrateCharEntry(FN_DB.chars[key])
    return FN_DB.chars[key]
end
FN.EnsureChar = EnsureChar

local function ScanKnownCharacters()
    local name = UnitName("player")
    local realm = GetRealmName()
    local key = CharKey(name, realm)
    local _, classFile, classID = UnitClass("player")
    local entry = EnsureChar(key)
    entry.classID = classID
    entry.classFile = classFile
    entry.name = name
    entry.realm = realm
    entry.level = UnitLevel("player")
    entry.faction = UnitFactionGroup("player")
    entry.lastSeen = time()
end
FN.ScanKnownCharacters = ScanKnownCharacters

local function GetRegisteredCharacters()
    EnsureDB()
    local out = {}
    for key, entry in pairs(FN_DB.chars or {}) do
        entry = EnsureChar(key)
        out[#out + 1] = {
            key = key,
            name = entry.name or key:match("^(.-) %- ") or key,
            realm = entry.realm or key:match(" %- (.+)$") or "",
            classID = entry.classID,
            classFile = entry.classFile,
            level = entry.level,
            faction = entry.faction,
            lastSeen = entry.lastSeen,
        }
    end
    local currentKey = PlayerKey()
    table.sort(out, function(a, b)
        local aCurrent = (a.key == currentKey)
        local bCurrent = (b.key == currentKey)
        if aCurrent ~= bCurrent then
            return aCurrent
        end

        local aSeen = tonumber(a.lastSeen) or 0
        local bSeen = tonumber(b.lastSeen) or 0
        if aSeen ~= bSeen then
            return aSeen > bSeen
        end

        local ak = string.lower((a.name or "") .. " - " .. (a.realm or ""))
        local bk = string.lower((b.name or "") .. " - " .. (b.realm or ""))
        return ak < bk
    end)
    return out
end
FN.GetRegisteredCharacters = GetRegisteredCharacters


local function RemoveRegisteredCharacter(key)
    EnsureDB()
    if not key then return false end
    if FN_DB.chars[key] then
        FN_DB.chars[key] = nil
        RefreshOptionsIfVisible()
        return true
    end
    return false
end
FN.RemoveRegisteredCharacter = RemoveRegisteredCharacter

local function GetActiveNeighborhood()
    if FN.Debug and FN.Debug.IsEnabled and FN.Debug:IsEnabled() then
        if FN.Debug.GetActiveNeighborhoodOverride then
            local override = FN.Debug:GetActiveNeighborhoodOverride()
            if override then return override end
        end
        if FN.Debug.GetDropdownNeighborhoods then
            local items = FN.Debug:GetDropdownNeighborhoods()
            if items and items[1] and items[1].value then return tostring(items[1].value) end
        end
    end
    if C_NeighborhoodInitiative and C_NeighborhoodInitiative.GetActiveNeighborhood then
        return C_NeighborhoodInitiative.GetActiveNeighborhood()
    end
    return nil
end
FN.GetActiveNeighborhood = GetActiveNeighborhood

local function SetNeighborhood(id)
    local sid = NormalizeNeighborhoodID and NormalizeNeighborhoodID(id) or (id and tostring(id) or nil)
    if FN.Debug and FN.Debug.IsEnabled and FN.Debug:IsEnabled() and FN.Debug.SetActiveNeighborhoodOverride then
        local ok = FN.Debug:SetActiveNeighborhoodOverride(sid)
        if ok then
            FN._pendingSetNeighborhoodID = nil
            FN._pendingSetNeighborhoodAt = nil
        end
        return ok
    end
    if C_NeighborhoodInitiative and C_NeighborhoodInitiative.SetActiveNeighborhood and sid then
        FN._pendingSetNeighborhoodID = sid
        FN._pendingSetNeighborhoodAt = GetTime and GetTime() or 0
        C_NeighborhoodInitiative.SetActiveNeighborhood(sid)
        return true
    end
    return false
end
FN.SetNeighborhood = SetNeighborhood

local function ConfirmPendingNeighborhood(active)
    local pending = FN._pendingSetNeighborhoodID
    if not pending then return false end
    if active and tostring(active) == tostring(pending) then
        FN._pendingSetNeighborhoodID = nil
        FN._pendingSetNeighborhoodAt = nil
        if FN._pendingManualFavoriteDisplayID and tostring(FN._pendingManualFavoriteDisplayID) == tostring(active) then
            FN._pendingManualFavoriteDisplayID = nil
        end
        if FN.RequestNeighborhoodProgress then
            FN.RequestNeighborhoodProgress(active)
        end
        RefreshOptionsIfVisible()
        return true
    end
    return false
end
FN.ConfirmPendingNeighborhood = ConfirmPendingNeighborhood

local function StripColors(text)
    if not text then return text end
    text = tostring(text)
    text = text:gsub("|c%x%x%x%x%x%x%x%x", "")
    text = text:gsub("|r", "")
    return text
end

local function RememberNeighborhood(id, name)
    if not id then return end
    if FN.Debug and FN.Debug.IsDebugNeighborhood and FN.Debug:IsDebugNeighborhood(id) then return end
    local sid = tostring(id)
    local clean = StripColors(name)
    FN_DB.neighborhoods[sid] = tostring(clean or FN_DB.neighborhoods[sid] or sid)
end
FN.RememberNeighborhood = RememberNeighborhood

local function ResolveNeighborhoodName(id)
    if not id then return "None" end
    if FN.Debug and FN.Debug.GetResolvedNeighborhoodName then
        local debugName = FN.Debug:GetResolvedNeighborhoodName(id)
        if debugName then
            return debugName
        end
    end
    local sid = tostring(id)
    if FN_DB.neighborhoods[sid] and FN_DB.neighborhoods[sid] ~= "" then
        return FN_DB.neighborhoods[sid]
    end
    return sid
end
FN.ResolveNeighborhoodName = ResolveNeighborhoodName

local function GetProgressData(id)
    if not id then return nil end
    if FN.Debug and FN.Debug.GetProgressData then
        local debugData = FN.Debug:GetProgressData(id)
        if debugData then
            return debugData
        end
    end
    return FN_DB.progress[tostring(id)]
end
FN.GetProgressData = GetProgressData

local function SaveProgressData(id, data)
    if not id then return end
    FN_DB.progress[tostring(id)] = data
end
FN.SaveProgressData = SaveProgressData

local function RequestNeighborhoodProgress(id)
    if FN.Debug and FN.Debug.IsDebugNeighborhood and FN.Debug:IsDebugNeighborhood(id) then return false end
    if not id or not C_NeighborhoodInitiative then return false end
    local sid = tostring(id)
    FN._pendingProgressNeighborhood = sid

    if not C_NeighborhoodInitiative.SetViewingNeighborhood then return false end
    if not C_NeighborhoodInitiative.RequestNeighborhoodInitiativeInfo then return false end

    pcall(C_NeighborhoodInitiative.SetViewingNeighborhood, sid)
    pcall(C_NeighborhoodInitiative.RequestNeighborhoodInitiativeInfo)
    return true
end
FN.RequestNeighborhoodProgress = RequestNeighborhoodProgress

local function IngestInitiativeInfo(id, initiativeInfo)
    if not id or type(initiativeInfo) ~= "table" then return false end
    if initiativeInfo.isLoaded == false then return false end

    local current = initiativeInfo.currentProgress or initiativeInfo.progress or 0
    local maxv = 0

    if type(initiativeInfo.milestones) == "table" then
        for _, milestone in ipairs(initiativeInfo.milestones) do
            local threshold = milestone and milestone.requiredContributionAmount or 0
            if type(threshold) == "number" and threshold > maxv then
                maxv = threshold
            end
        end
    end

    if maxv == 0 then
        maxv = initiativeInfo.progressRequired or initiativeInfo.maxProgress or initiativeInfo.totalProgress or 0
    end

    local pct = 0
    if type(maxv) == "number" and maxv > 0 and type(current) == "number" then
        pct = math.floor(((current / maxv) * 100) + 0.5)
        if pct < 0 then pct = 0 end
        if pct > 100 then pct = 100 end
    end

    local shownCurrent = current or 0
    if type(shownCurrent) == "number" and type(maxv) == "number" and maxv > 0 and shownCurrent > maxv then
        shownCurrent = maxv
    end
    SaveProgressData(id, {
        currentProgress = shownCurrent,
        maxProgress = maxv or 0,
        percent = pct,
        seasonName = initiativeInfo.title or initiativeInfo.seasonName or initiativeInfo.initiativeTitle or nil,
        isComplete = pct >= 100,
        updatedAt = time(),
    })

    if not FN._refreshingOptions then
        RefreshOptionsIfVisible()
    end
    return true
end
FN.IngestInitiativeInfo = IngestInitiativeInfo

local function TryIngestCurrentInitiativeInfo(id)
    if not id or not C_NeighborhoodInitiative or not C_NeighborhoodInitiative.GetNeighborhoodInitiativeInfo then
        return false
    end
    local ok, info = pcall(C_NeighborhoodInitiative.GetNeighborhoodInitiativeInfo)
    if not ok or type(info) ~= "table" or info.isLoaded == false then
        return false
    end
    return IngestInitiativeInfo(id, info)
end
FN.TryIngestCurrentInitiativeInfo = TryIngestCurrentInitiativeInfo


local function NormalizeNeighborhoodID(id)
    if id == nil then return nil end
    return tostring(id)
end
FN.NormalizeNeighborhoodID = NormalizeNeighborhoodID

local function GetStoredNeighborhoodIDs()
    EnsureDB()
    local ids = {}
    local seen = {}

    local function add(id)
        id = NormalizeNeighborhoodID(id)
        if not id or seen[id] then return end
        seen[id] = true
        ids[#ids + 1] = id
    end

    add(FN_DB.global)

    for id in pairs(FN_DB.neighborhoods or {}) do
        add(id)
    end

    for id in pairs(FN_DB.progress or {}) do
        add(id)
    end

    table.sort(ids, function(a, b)
        local an = FN.ResolveNeighborhoodName and FN.ResolveNeighborhoodName(a) or a
        local bn = FN.ResolveNeighborhoodName and FN.ResolveNeighborhoodName(b) or b
        return tostring(an) < tostring(bn)
    end)

    return ids
end
FN.GetStoredNeighborhoodIDs = GetStoredNeighborhoodIDs

local function GetKnownNeighborhoodIDs()
    EnsureDB()
    local ids = {}
    local seen = {}

    local function add(id)
        id = NormalizeNeighborhoodID(id)
        if not id or seen[id] then return end
        seen[id] = true
        ids[#ids + 1] = id
    end

    for _, id in ipairs(GetStoredNeighborhoodIDs()) do
        add(id)
    end

    add(FN.GetActiveNeighborhood and FN.GetActiveNeighborhood() or nil)

    if FN.Debug and FN.Debug.IsEnabled and FN.Debug:IsEnabled() and FN.Debug.GetDropdownNeighborhoods then
        for _, item in ipairs(FN.Debug:GetDropdownNeighborhoods() or {}) do
            if item and item.value then
                add(item.value)
            end
        end
    end

    table.sort(ids, function(a, b)
        local an = FN.ResolveNeighborhoodName and FN.ResolveNeighborhoodName(a) or a
        local bn = FN.ResolveNeighborhoodName and FN.ResolveNeighborhoodName(b) or b
        return tostring(an) < tostring(bn)
    end)

    return ids
end
FN.GetKnownNeighborhoodIDs = GetKnownNeighborhoodIDs

local function BuildLiveNeighborhoodMap(houseInfoList)
    local live = {}
    local order = {}

    if type(houseInfoList) ~= "table" then
        return live, order, 0
    end

    for _, houseInfo in ipairs(houseInfoList) do
        local neighborhoodGUID = houseInfo and houseInfo.neighborhoodGUID
        if neighborhoodGUID then
            local sid = NormalizeNeighborhoodID(neighborhoodGUID)
            if sid and not live[sid] then
                live[sid] = StripColors(houseInfo.houseName or houseInfo.neighborhoodName or houseInfo.name or sid) or sid
                order[#order + 1] = sid
            end
        end
    end

    return live, order, #order
end

local function SyncKnownNeighborhoods(houseInfoList)
    EnsureDB()

    local liveMap, liveOrder, liveCount = BuildLiveNeighborhoodMap(houseInfoList)
    if liveCount <= 0 then
        return false, GetKnownNeighborhoodIDs()
    end

    local storedIds = GetStoredNeighborhoodIDs()
    local storedCount = #storedIds

    -- Be conservative during login/relog. If the live list is smaller than what we already know,
    -- prefer keeping the stored entries instead of pruning valid neighborhoods from a partial API result.
    local allowPrune = (storedCount == 0) or (liveCount >= storedCount)

    local changed = false
    local newNeighborhoods = {}
    local newProgress = {}
    local validIds = {}

    if allowPrune then
        for _, sid in ipairs(liveOrder) do
            validIds[sid] = true
            newNeighborhoods[sid] = tostring(liveMap[sid] or FN_DB.neighborhoods[sid] or sid)
            if FN_DB.neighborhoods[sid] ~= newNeighborhoods[sid] then
                changed = true
            end
            if FN_DB.progress[sid] ~= nil then
                newProgress[sid] = FN_DB.progress[sid]
            end
        end

        for sid in pairs(FN_DB.neighborhoods or {}) do
            sid = NormalizeNeighborhoodID(sid)
            if sid and not validIds[sid] then
                changed = true
            end
        end

        for sid in pairs(FN_DB.progress or {}) do
            sid = NormalizeNeighborhoodID(sid)
            if sid and not validIds[sid] then
                changed = true
            end
        end
    else
        for _, sid in ipairs(storedIds) do
            validIds[sid] = true
            newNeighborhoods[sid] = tostring(FN_DB.neighborhoods[sid] or liveMap[sid] or sid)
            if FN_DB.progress[sid] ~= nil then
                newProgress[sid] = FN_DB.progress[sid]
            end
        end
        for _, sid in ipairs(liveOrder) do
            if not validIds[sid] then
                validIds[sid] = true
                newNeighborhoods[sid] = tostring(liveMap[sid] or sid)
                changed = true
                if FN_DB.progress[sid] ~= nil then
                    newProgress[sid] = FN_DB.progress[sid]
                end
            end
        end
    end

    FN_DB.neighborhoods = newNeighborhoods
    FN_DB.progress = newProgress

    if FN.ValidateFavorites then
        FN.ValidateFavorites(validIds)
    end
    if not FN_DB.global and liveOrder[1] then
        FN_DB.global = liveOrder[1]
        changed = true
    end

    return changed, GetKnownNeighborhoodIDs()
end
FN.SyncKnownNeighborhoods = SyncKnownNeighborhoods

local function ValidateFavorites(validIds)
    validIds = validIds or {}

    local function isAllowedFavorite(id)
        if not id then return true end
        if validIds[tostring(id)] then return true end
        if FN.Debug and FN.Debug.IsEnabled and FN.Debug:IsEnabled() and FN.Debug.IsDebugNeighborhood and FN.Debug:IsDebugNeighborhood(id) then
            return true
        end
        return false
    end

    if FN_DB.global and not isAllowedFavorite(FN_DB.global) then
        FN_DB.global = nil
    end

    for key, _ in pairs(FN_DB.chars or {}) do
        local entry = EnsureChar(key)
        if entry.fav and not isAllowedFavorite(entry.fav) then
            entry.fav = nil
            if entry.mode == "C" then
                entry.mode = "G"
            end
        end
    end
end
FN.ValidateFavorites = ValidateFavorites

local function IngestHouseInfoList(houseInfoList)
    local changed = false

    local synced = false
    if FN.SyncKnownNeighborhoods then
        local didChange = FN.SyncKnownNeighborhoods(houseInfoList)
        synced = true
        changed = didChange and true or false
    end

    if type(houseInfoList) ~= "table" or #houseInfoList == 0 then
        return synced and changed or false
    end

    local rememberedAny = false
    for _, houseInfo in ipairs(houseInfoList) do
        local neighborhoodGUID = houseInfo and houseInfo.neighborhoodGUID
        local houseName = houseInfo and (houseInfo.houseName or houseInfo.neighborhoodName or houseInfo.name)

        if neighborhoodGUID then
            RememberNeighborhood(neighborhoodGUID, houseName or neighborhoodGUID)
            rememberedAny = true
        end
    end

    if changed or rememberedAny then
        RefreshOptionsIfVisible()
        return true
    end

    return false
end
FN.IngestHouseInfoList = IngestHouseInfoList

local function RequestOwnedHouses()
    if C_Housing and C_Housing.GetPlayerOwnedHouses then
        pcall(C_Housing.GetPlayerOwnedHouses)
    end
end
FN.RequestOwnedHouses = RequestOwnedHouses

local function EffectiveNeighborhood(charKey)
    return FN_DB.global
end
FN.EffectiveNeighborhood = EffectiveNeighborhood
FN.TargetNeighborhood = EffectiveNeighborhood

local function GetCurrentTargetNeighborhood()
    if FN.AutoSwitch and FN.AutoSwitch.EvaluateState then
        local state = FN.AutoSwitch:EvaluateState(GetActiveNeighborhood())
        if state and state.targetID then
            return tostring(state.targetID)
        end
        if state and state.favoriteID then
            return tostring(state.favoriteID)
        end
    end
    return FN_DB and FN_DB.global and tostring(FN_DB.global) or nil
end
FN.GetCurrentTargetNeighborhood = GetCurrentTargetNeighborhood

local function ApplyLoginFix()
    if loginFixInProgress then return false end

    local t = now()
    if (t - (lastLoginFixAt or 0)) < LOGIN_FIX_GUARD_WINDOW then
        return false
    end

    loginFixInProgress = true
    lastLoginFixAt = t

    local ok, result = pcall(function()
        local active = GetActiveNeighborhood()
        if active then
            RememberNeighborhood(active)
            ClearCorrectionTargetIfReached(active)
        end

        if FN.AutoSwitch and FN.AutoSwitch.EvaluateState and FN.AutoSwitch.PerformSwitch then
            local state = FN.AutoSwitch:EvaluateState(active)
            if state then Trace('LOGINFIX', 'state target='..tostring(state.targetID)..' display='..tostring(state.displayID)..' all='..tostring(state.allComplete)..' rec='..tostring(state.switchRecommended)..' can='..tostring(state.canAutoSwitch)) end
            if state and state.canAutoSwitch and state.targetID then
                if active and tostring(active) == tostring(state.targetID) then
                    return false
                end
                if tostring(active or "") ~= tostring(state.targetID) then
                    RememberCorrectionTarget(state.targetID)
                end
                return FN.AutoSwitch:PerformSwitch(state, { ignoreCooldown = true, source = "login_fix" })
            end
        end

        return false
    end)

    loginFixInProgress = false
    if not ok then return false end
    return result and true or false
end
FN.ApplyLoginFix = ApplyLoginFix

-- Fallback: capture names if Blizzard still builds a classic dropdown somewhere.
hooksecurefunc("UIDropDownMenu_AddButton", function(info)
    if not info or not info.value or not info.text then return end
    local id = tostring(info.value)
    local text = tostring(info.text)
    if id:find("Housing") then
        RememberNeighborhood(id, text)
        RefreshOptionsIfVisible()
    end
end)

local function OnLogin()
    EnsureDB()
    if FN.Debug and FN.Debug.SetEnabled then
        FN.Debug:SetEnabled(false)
    end
    ScanKnownCharacters()

    relogBootstrapPending = true
    if FN.RequestOwnedHouses then
        FN.RequestOwnedHouses()
    end
end

frame:RegisterEvent("PLAYER_LOGIN")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("PLAYER_HOUSE_LIST_UPDATED")
frame:RegisterEvent("NEIGHBORHOOD_INITIATIVE_UPDATED")
frame:SetScript("OnEvent", function(_, event, ...)
    if event == "PLAYER_LOGIN" then
        OnLogin()
    elseif event == "PLAYER_ENTERING_WORLD" then
        relogBootstrapPending = true
        if FN.RequestOwnedHouses then
            FN.RequestOwnedHouses()
        end
    elseif event == "PLAYER_HOUSE_LIST_UPDATED" then
        local houseInfoList = ...
        if not IngestHouseInfoList(houseInfoList) then
            if FN.UpdateOptionsFrame and FN.OptionsFrame and FN.OptionsFrame:IsShown() then
                FN.UpdateOptionsFrame()
            end
        end

        local active = GetActiveNeighborhood()
        if active then
            ConfirmPendingNeighborhood(active)
        end

        if relogBootstrapPending and not relogBootstrapRunning then
            ScheduleRelogStateRebuild()
        end
    elseif event == "NEIGHBORHOOD_INITIATIVE_UPDATED" then
        local pending = FN._pendingProgressNeighborhood
        local active = GetActiveNeighborhood()
        local updatedID = nil

        if active then
            ConfirmPendingNeighborhood(active)
        end

        if pending and C_NeighborhoodInitiative and C_NeighborhoodInitiative.GetNeighborhoodInitiativeInfo then
            local ok, info = pcall(C_NeighborhoodInitiative.GetNeighborhoodInitiativeInfo)
            if ok and info then
                IngestInitiativeInfo(pending, info)
                updatedID = pending
            end
            FN._pendingProgressNeighborhood = nil
        end

        if FN.AutoSwitch and FN.AutoSwitch.HandleProgressUpdated then
            FN.AutoSwitch:HandleProgressUpdated(updatedID or active)
        end

        if relogBootstrapRunning then
            RefreshOptionsIfVisible()
        end
    end
end)

FN.IsLoginFixInProgress = function() return loginFixInProgress end
