local addonName, FN = ...
FN = FN or FavoriteNeighborhood or {}
FavoriteNeighborhood = FN

FN.AutoSwitch = FN.AutoSwitch or {}
local AutoSwitch = FN.AutoSwitch

local SWITCH_COOLDOWN = 3
local BURST_WINDOW = 15
local BURST_LIMIT = 3
local BURST_LOCKOUT = 20
local lastSwitchAt = 0
local lastSwitchFrom = nil
local lastSwitchTo = nil
local recentSwitches = {}
local lockoutUntil = 0
local lastSeenCompleteState = {}
local switchInProgress = false

local function isBusy()
    if switchInProgress then return true end
    if FN and FN.IsLoginFixInProgress and FN.IsLoginFixInProgress() then return true end
    return false
end

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

local function refreshOptions(_settled)
    if FN and FN.RefreshOptionsIfVisible then
        FN.RefreshOptionsIfVisible()
    elseif FN and FN.UpdateOptionsFrame and FN.OptionsFrame and FN.OptionsFrame:IsShown() then
        FN.UpdateOptionsFrame()
    end
end

local function pruneRecentSwitches(t)
    for i = #recentSwitches, 1, -1 do
        if (t - recentSwitches[i]) > BURST_WINDOW then
            table.remove(recentSwitches, i)
        end
    end
end

function AutoSwitch:GetSelectableNeighborhoods()
    if FN.GetKnownNeighborhoodIDs then
        return FN.GetKnownNeighborhoodIDs()
    end

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

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

    return ids
end

function AutoSwitch:GetProgressData(id)
    if FN.GetProgressData then
        return FN.GetProgressData(id)
    end
    return FN_DB and FN_DB.progress and FN_DB.progress[tostring(id)] or nil
end

function AutoSwitch:IsComplete(id)
    if not id then return false end
    local info = self:GetProgressData(id)
    if not info then return false end
    if info.isComplete ~= nil then return info.isComplete and true or false end
    if tonumber(info.percent) then return tonumber(info.percent) >= 100 end
    local current = tonumber(info.currentProgress) or 0
    local maxv = tonumber(info.maxProgress) or 0
    return maxv > 0 and current >= maxv
end

function AutoSwitch:HasIncomplete(ids)
    ids = ids or self:GetSelectableNeighborhoods()
    for _, id in ipairs(ids) do
        if not self:IsComplete(id) then
            return true
        end
    end
    return false
end

function AutoSwitch:AreAllComplete(ids)
    ids = ids or self:GetSelectableNeighborhoods()
    if #ids == 0 then return false end
    for _, id in ipairs(ids) do
        if not self:IsComplete(id) then
            return false
        end
    end
    return true
end

function AutoSwitch:ShouldShowSwitchRecommended(currentID, ids)
    if not currentID then return false end
    ids = ids or self:GetSelectableNeighborhoods()
    if not self:IsComplete(currentID) then return false end
    for _, id in ipairs(ids) do
        if tostring(id) ~= tostring(currentID) and not self:IsComplete(id) then
            return true
        end
    end
    return false
end

function AutoSwitch:GetNextIncomplete(currentID, ids)
    ids = ids or self:GetSelectableNeighborhoods()
    if #ids == 0 then return nil end

    local startIndex = 0
    for i, id in ipairs(ids) do
        if tostring(id) == tostring(currentID) then
            startIndex = i
            break
        end
    end

    for step = 1, #ids do
        local idx = ((startIndex + step - 1) % #ids) + 1
        local id = ids[idx]
        if tostring(id) ~= tostring(currentID) and not self:IsComplete(id) then
            return id
        end
    end

    return nil
end

function AutoSwitch:IsEnabledForPlayer()
    return FN_DB and FN_DB.config and FN_DB.config.autoSwitchGlobal and true or false
end

function AutoSwitch:GetPreferredTarget(currentID, ids)
    ids = ids or self:GetSelectableNeighborhoods()
    local favorite = FN_DB and FN_DB.global or nil
    local active = currentID and tostring(currentID) or (FN.GetActiveNeighborhood and FN.GetActiveNeighborhood() or nil)
    if active then active = tostring(active) end
    if favorite then favorite = tostring(favorite) end

    if favorite and not self:IsComplete(favorite) then
        return favorite
    end

    if active and not self:IsComplete(active) then
        return active
    end

    for _, id in ipairs(ids) do
        id = tostring(id)
        if id ~= tostring(active or "") and not self:IsComplete(id) then
            return id
        end
    end

    return favorite or active
end

function AutoSwitch:EvaluateTarget(currentID, ids)
    return self:GetPreferredTarget(currentID, ids)
end

function AutoSwitch:EvaluateState(currentID, ids)
    ids = ids or self:GetSelectableNeighborhoods()

    local state = {
        favoriteID = FN_DB and FN_DB.global and tostring(FN_DB.global) or nil,
        activeID = currentID and tostring(currentID) or (FN.GetActiveNeighborhood and FN.GetActiveNeighborhood() or nil),
        knownIDs = ids,
        autoSwitchEnabled = self:IsEnabledForPlayer(),
        allComplete = false,
        activeComplete = false,
        favoriteComplete = false,
        hasOtherIncomplete = false,
        switchRecommended = false,
        targetID = nil,
        displayID = nil,
        canAutoSwitch = false,
    }

    if state.activeID then state.activeID = tostring(state.activeID) end
    if state.favoriteID then state.favoriteID = tostring(state.favoriteID) end

    state.allComplete = self:AreAllComplete(ids)
    state.activeComplete = state.activeID and self:IsComplete(state.activeID) or false
    state.favoriteComplete = state.favoriteID and self:IsComplete(state.favoriteID) or false

    local referenceID = state.activeID or state.favoriteID
    if referenceID then
        for _, id in ipairs(ids) do
            id = tostring(id)
            if id ~= tostring(referenceID) and not self:IsComplete(id) then
                state.hasOtherIncomplete = true
                break
            end
        end
    end

    state.switchRecommended = state.activeComplete and state.hasOtherIncomplete or false
    state.targetID = self:GetPreferredTarget(state.activeID or state.favoriteID, ids)

    if state.autoSwitchEnabled then
        state.displayID = state.targetID or state.favoriteID or state.activeID
    else
        state.displayID = state.favoriteID or state.activeID or state.targetID
    end

    state.canAutoSwitch = state.autoSwitchEnabled
        and state.activeComplete
        and state.hasOtherIncomplete
        and state.targetID
        and state.activeID
        and tostring(state.targetID) ~= tostring(state.activeID)
        or false

    return state
end

function AutoSwitch:CanSwitchNow(currentID, targetID, opts)
    opts = opts or {}
    if not self:IsEnabledForPlayer() then return false end
    if not currentID or not targetID then return false end
    currentID = tostring(currentID)
    targetID = tostring(targetID)
    if currentID == targetID then return false end
    local t = now()
    if lockoutUntil > t then return false end
    if (not opts.ignoreCooldown) and (t - lastSwitchAt) < SWITCH_COOLDOWN then return false end
    pruneRecentSwitches(t)
    if #recentSwitches >= BURST_LIMIT then
        lockoutUntil = t + BURST_LOCKOUT
        return false
    end
    return true
end

function AutoSwitch:ShouldPerformSwitch(state, opts)
    opts = opts or {}
    if not state then return false end
    if not state.autoSwitchEnabled then return false end
    if not state.activeID or not state.targetID then return false end
    if tostring(state.activeID) == tostring(state.targetID) then return false end
    if not state.activeComplete then return false end
    if not state.hasOtherIncomplete then return false end
    return self:CanSwitchNow(state.activeID, state.targetID, opts)
end

function AutoSwitch:PerformSwitch(state, opts)
    opts = opts or {}
    if isBusy() and opts.source ~= "login_fix" then return false end
    if not self:ShouldPerformSwitch(state, opts) then return false end

    local currentID = tostring(state.activeID)
    local targetID = tostring(state.targetID)

    if (not opts.ignoreCooldown) and lastSwitchTo and tostring(targetID) == tostring(lastSwitchTo) and (now() - lastSwitchAt) < SWITCH_COOLDOWN then
        return false
    end

    switchInProgress = true
    local ok = false
    if FN.SetNeighborhood then
        ok = FN.SetNeighborhood(targetID) and true or false
    end

    if ok then
        lastSwitchAt = now()
        lastSwitchFrom = currentID
        lastSwitchTo = targetID
        table.insert(recentSwitches, lastSwitchAt)
        pruneRecentSwitches(lastSwitchAt)
        refreshOptions(true)
    end

    C_Timer.After(0.25, function()
        switchInProgress = false
    end)

    return ok
end

function AutoSwitch:TrySwitch(currentID, ids, opts)
    local state = self:EvaluateState(currentID, ids)
    return self:PerformSwitch(state, opts)
end

function AutoSwitch:HandleAutoSwitchEnabled()
    if isBusy() then return false end
    local state = self:EvaluateState(FN.GetActiveNeighborhood and FN.GetActiveNeighborhood() or nil)
    return self:PerformSwitch(state)
end

function AutoSwitch:HandleFavoriteChanged(currentID)
    if isBusy() then return false end
    local state = self:EvaluateState(currentID or (FN.GetActiveNeighborhood and FN.GetActiveNeighborhood() or nil))
    return self:PerformSwitch(state)
end

function AutoSwitch:HandleNeighborhoodListChanged()
    if isBusy() then return false end
    local state = self:EvaluateState(FN.GetActiveNeighborhood and FN.GetActiveNeighborhood() or nil)
    return self:PerformSwitch(state)
end

function AutoSwitch:HandleProgressUpdated(currentID)
    if isBusy() then return false end
    local activeID = FN.GetActiveNeighborhood and FN.GetActiveNeighborhood() or currentID
    if not activeID then return false end

    local sid = tostring(activeID)
    local isComplete = self:IsComplete(sid)
    local wasComplete = lastSeenCompleteState[sid]
    lastSeenCompleteState[sid] = isComplete and true or false

    local state = self:EvaluateState(sid)
    if not state then return false end

    if state.canAutoSwitch and (isComplete or wasComplete == nil or wasComplete == true) then
        return self:PerformSwitch(state, { ignoreCooldown = true })
    end

    return false
end

function AutoSwitch:GetLastSwitchInfo()
    return lastSwitchFrom, lastSwitchTo, lastSwitchAt
end
