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 RefreshOptionsIfVisible()
    if FN and FN.UpdateOptionsFrame and FN.OptionsFrame and FN.OptionsFrame:IsShown() then
        FN.UpdateOptionsFrame()
    end
end
FN.RefreshOptionsIfVisible = RefreshOptionsIfVisible

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 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)
    if FN.Debug and FN.Debug.IsEnabled and FN.Debug:IsEnabled() and FN.Debug.SetActiveNeighborhoodOverride then
        return FN.Debug:SetActiveNeighborhoodOverride(id)
    end
    if C_NeighborhoodInitiative and C_NeighborhoodInitiative.SetActiveNeighborhood then
        C_NeighborhoodInitiative.SetActiveNeighborhood(id)
        return true
    end
    return false
end
FN.SetNeighborhood = SetNeighborhood

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

    local active = GetActiveNeighborhood()
    if active and tostring(active) == sid and FN.TryIngestCurrentInitiativeInfo and FN.TryIngestCurrentInitiativeInfo(sid) then
        return true
    end

    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 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)
    if type(houseInfoList) ~= "table" or #houseInfoList == 0 then
        return false
    end

    local validIds = {}
    local count = 0

    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
            local sid = tostring(neighborhoodGUID)
            validIds[sid] = true
            RememberNeighborhood(sid, houseName or sid)
            count = count + 1
        end
    end

    if count > 0 then
        ValidateFavorites(validIds)
        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()
    local favorite = FN_DB and FN_DB.global or nil

    if FN.Debug and FN.Debug.IsEnabled and FN.Debug:IsEnabled() then
        if not favorite or (FN.Debug.IsDebugNeighborhood and not FN.Debug:IsDebugNeighborhood(favorite)) then
            local items = FN.Debug.GetDropdownNeighborhoods and FN.Debug:GetDropdownNeighborhoods() or nil
            if items and items[1] and items[1].value then
                favorite = tostring(items[1].value)
            end
        end
    end

    if not favorite then return nil end
    favorite = tostring(favorite)

    local autoEnabled = FN_DB and FN_DB.config and FN_DB.config.autoSwitchGlobal
    if not autoEnabled then
        return favorite
    end

    if FN.AutoSwitch and FN.AutoSwitch.GetPreferredTarget then
        local preferred = FN.AutoSwitch:GetPreferredTarget(FN.GetActiveNeighborhood and FN.GetActiveNeighborhood() or favorite)
        if preferred then
            return tostring(preferred)
        end
    end

    return favorite
end
FN.GetCurrentTargetNeighborhood = GetCurrentTargetNeighborhood

local function ApplyLoginFix()
    local active = GetActiveNeighborhood()
    local target = GetCurrentTargetNeighborhood()

    if active then
        RememberNeighborhood(active)
        ClearCorrectionTargetIfReached(active)
    end

    if not target then
        return
    end

    if tostring(active or "") == tostring(target) then
        return
    end

    if WasRecentCorrectionTarget(target) then
        return
    end

    Msg("correcting neighborhood.")
    RememberCorrectionTarget(target)
    SetNeighborhood(target)
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()

    RequestOwnedHouses()

    local active = GetActiveNeighborhood()
    if active and FN.TryIngestCurrentInitiativeInfo then
        FN.TryIngestCurrentInitiativeInfo(active)
    end

    if FN_DB.global then
        RequestNeighborhoodProgress(FN_DB.global)
    end

    ApplyLoginFix()

    RefreshOptionsIfVisible()
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()
        if FN.AutoSwitch and FN.AutoSwitch.HandleNeighborhoodListChanged then
            FN.AutoSwitch:HandleNeighborhoodListChanged()
        end
    elseif event == "PLAYER_ENTERING_WORLD" then
        RequestOwnedHouses()
        if FN.AutoSwitch and FN.AutoSwitch.HandleNeighborhoodListChanged then
            FN.AutoSwitch:HandleNeighborhoodListChanged()
        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
        else
            if FN_DB.global then
                RequestProgressForCurrentFavorite()
            end
        end

        ApplyLoginFix()

        if FN.AutoSwitch and FN.AutoSwitch.HandleNeighborhoodListChanged then
            FN.AutoSwitch:HandleNeighborhoodListChanged()
        end
    elseif event == "NEIGHBORHOOD_INITIATIVE_UPDATED" then
        local pending = FN._pendingProgressNeighborhood or FN_DB.global or GetActiveNeighborhood()
        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)
            end
        end

        local active = GetActiveNeighborhood()
        if active and FN.TryIngestCurrentInitiativeInfo then
            FN.TryIngestCurrentInitiativeInfo(active)
        end
        if FN.AutoSwitch and FN.AutoSwitch.HandleProgressUpdated then
            FN.AutoSwitch:HandleProgressUpdated(active)
        end
    end
end)
