local _, addonTable = ...
---@type MK
local MK = addonTable.MK
local core = addonTable

core.AccountType = core.AccountType or {}
local AccountType = core.AccountType

local ACCOUNT_TYPE_PRIORITY = {
    "newbie",
    "leader",
    "star",
    "veteran",
}

local ACCOUNT_TYPE_STATUS = {
    pending = true,
    no_response = true,
    unavailable = true,
    unknown = true,
    newbie = true,
    leader = true,
    star = true,
    veteran = true,
}

local function TrimString(value)
    if type(value) ~= "string" then
        return nil
    end

    value = value:gsub("^%s+", ""):gsub("%s+$", "")
    if value == "" then
        return nil
    end
    return value
end

function AccountType.SplitFullName(name)
    name = TrimString(name)
    if not name then
        return nil, nil
    end

    local shortName, realm = name:match("^([^-]+)%-(.+)$")
    if shortName and realm then
        return TrimString(shortName) or name, TrimString(realm)
    end
    return name, nil
end

function AccountType.NormalizeIdentity(name, realm)
    name = TrimString(name)
    realm = TrimString(realm)
    if not name then
        return nil, nil, nil
    end

    local shortName, embeddedRealm = AccountType.SplitFullName(name)
    if shortName then
        name = shortName
        realm = realm or embeddedRealm
    end

    local key = realm and (name .. "-" .. realm) or name
    return name, realm, key
end

function AccountType.GetIdentityFromCharacterEntry(characterEntry)
    if type(characterEntry) ~= "table" then
        return nil, nil, nil
    end

    local character = characterEntry.data
    if type(character) == "table" then
        return AccountType.NormalizeIdentity(character.name or characterEntry.key, character.realm)
    end
    return AccountType.NormalizeIdentity(characterEntry.name or characterEntry.key, characterEntry.realm)
end

function AccountType.GetIdentityFromName(name, realm)
    return AccountType.NormalizeIdentity(name, realm)
end

local function GetIdentityFromUnit(unit)
    if type(unit) ~= "table" then
        return nil, nil, nil
    end

    if unit.characterEntry then
        return AccountType.GetIdentityFromCharacterEntry(unit.characterEntry)
    end
    if unit.entry then
        return AccountType.GetIdentityFromCharacterEntry(unit.entry)
    end
    return AccountType.NormalizeIdentity(unit.name, unit.realm)
end

function AccountType.MakeCharacterQueryEntry(name, realm, extraData)
    local normalizedName, normalizedRealm, key = AccountType.NormalizeIdentity(name, realm)
    if not normalizedName then
        return nil
    end

    local data = {
        name = normalizedName,
        realm = normalizedRealm,
    }
    if type(extraData) == "table" then
        for dataKey, value in pairs(extraData) do
            data[dataKey] = value
        end
    end
    return {
        key = key,
        data = data,
        mkAccountTypeKey = key,
    }
end

local function ShouldIncludeQueryEntry(name, realm, options)
    if not name then
        return false
    end
    if options and options.requireRealm == true and not realm then
        return false
    end
    return true
end

function AccountType.NormalizeQueryEntries(entries, options)
    local normalized = {}
    if type(entries) ~= "table" then
        return normalized
    end

    local seen = {}
    for _, entry in ipairs(entries) do
        if not (options and type(options.skip) == "function" and options.skip(entry) == true) then
            local name, realm, key
            if type(entry) == "table" then
                if type(options and options.getIdentity) == "function" then
                    name, realm, key = options.getIdentity(entry)
                    name, realm, key = AccountType.NormalizeIdentity(name, realm or select(2, AccountType.SplitFullName(key)))
                else
                    name, realm, key = AccountType.GetIdentityFromCharacterEntry(entry)
                    if not name then
                        name, realm, key = AccountType.NormalizeIdentity(entry.name, entry.realm)
                    end
                end
            elseif type(entry) == "string" then
                name, realm, key = AccountType.NormalizeIdentity(entry)
            end

            if ShouldIncludeQueryEntry(name, realm, options) and key and not seen[key] then
                seen[key] = true
                local queryEntry = AccountType.MakeCharacterQueryEntry(name, realm)
                if queryEntry then
                    normalized[#normalized + 1] = queryEntry
                end
            end
        end
    end

    return normalized
end

function AccountType.QueueQueries(entries, options)
    local meetingStoneData = core.MeetingStoneData
    if not (meetingStoneData and meetingStoneData.QueryCharacters) then
        return 0, {}, {}, {}
    end

    local queryEntries = AccountType.NormalizeQueryEntries(entries, options)
    local queuedKeys = {}
    local waitingKeys = {}
    local queuedCount = 0
    for _, queryEntry in ipairs(queryEntries) do
        local key = queryEntry.mkAccountTypeKey or queryEntry.key
        local wasWaiting = key
            and meetingStoneData.characterAccountTypeQuerySet
            and meetingStoneData.characterAccountTypeQuerySet[key] == true
        local count = meetingStoneData:QueryCharacters({ queryEntry }) or 0
        if count > 0 then
            if key then
                queuedKeys[key] = true
                waitingKeys[key] = true
            end
            queuedCount = queuedCount + count
        elseif wasWaiting and key then
            waitingKeys[key] = true
        else
            local character = queryEntry.data
            local accountState = type(character) == "table" and AccountType.GetAccountState(character.name, character.realm, {
                requireRealm = options and options.requireRealm == true,
                returnUnavailable = false,
                includeVeteran = options and options.includeVeteran,
            }) or nil
            if accountState and accountState.status == "pending" and key then
                waitingKeys[key] = true
            end
        end
    end

    return queuedCount, queuedKeys, queryEntries, waitingKeys
end

local function GetBadgeStatus(accountTypeInfo, includeVeteran)
    if type(accountTypeInfo) ~= "table" then
        return nil
    end

    if type(accountTypeInfo.badges) ~= "table" then
        return nil
    end

    for _, status in ipairs(ACCOUNT_TYPE_PRIORITY) do
        if status ~= "veteran" or includeVeteran ~= false then
            for _, badge in ipairs(accountTypeInfo.badges) do
                if badge and badge.key == status then
                    return status
                end
            end
        end
    end
    return nil
end

function AccountType.GetAccountTypeInfoState(accountTypeInfo, options)
    options = type(options) == "table" and options or {}
    if not accountTypeInfo then
        return nil
    end

    local status = accountTypeInfo.status
    if status == "pending" or status == "no_response" or status == "unknown" then
        return status
    end
    if status == "veteran" and options.includeVeteran == false then
        return nil
    end
    if status == "newbie" or status == "leader" or status == "star" or status == "veteran" then
        return status
    end

    return GetBadgeStatus(accountTypeInfo, options.includeVeteran)
end

function AccountType.IsAccountTypeStatus(status)
    return ACCOUNT_TYPE_STATUS[status] == true
end

function AccountType.GetAccountState(name, realm, options)
    options = type(options) == "table" and options or {}
    name, realm = AccountType.NormalizeIdentity(name, realm)
    if not name then
        return {
            status = "unknown",
            name = nil,
            realm = nil,
        }
    end

    if options.requireRealm == true and not realm then
        return {
            status = options.returnUnavailable == false and "unknown" or "unavailable",
            name = name,
            realm = realm,
            key = name,
        }
    end

    local meetingStoneData = core.MeetingStoneData
    if not (meetingStoneData and meetingStoneData.GetAccountTypeInfo) then
        return {
            status = "unknown",
            name = name,
            realm = realm,
            key = realm and (name .. "-" .. realm) or name,
        }
    end

    local accountTypeInfo = meetingStoneData:GetAccountTypeInfo(name, realm)
    local status = AccountType.GetAccountTypeInfoState(accountTypeInfo, options) or "unknown"
    return {
        status = status,
        accountTypeInfo = accountTypeInfo,
        name = name,
        realm = realm,
        key = realm and (name .. "-" .. realm) or name,
    }
end

local function IsAllowedAccountStatus(status, allowedStatuses)
    if type(allowedStatuses) ~= "table" then
        return status and status ~= "unknown"
    end
    return allowedStatuses[status] == true
end

function AccountType.ResolveUnitStatus(unit, options)
    unit = type(unit) == "table" and unit or {}
    options = type(options) == "table" and options or {}
    local name, realm, key = GetIdentityFromUnit(unit)

    if options.includeOffline == true and unit.isOffline == true then
        return {
            status = "offline",
            name = name,
            realm = realm,
            key = key,
        }
    end

    if options.includeBlacklist ~= false then
        local isBlacklisted = unit.isBlacklisted == true
        local blacklistRecord = unit.blacklistRecord
        if not isBlacklisted and options.checkBlacklist ~= false and MK and MK.IsPlayerBlacklisted and name then
            isBlacklisted, blacklistRecord = MK:IsPlayerBlacklisted(name, realm)
        end
        if isBlacklisted then
            return {
                status = "blacklist",
                name = name,
                realm = realm,
                key = key,
                blacklistRecord = blacklistRecord,
            }
        end
    end

    if options.includeLeaver ~= false and unit.isLeaver == true then
        return {
            status = "leaver",
            name = name,
            realm = realm,
            key = key,
        }
    end

    if options.includeAccount ~= false then
        local accountState = AccountType.GetAccountState(name, realm, {
            requireRealm = options.requireRealm == true,
            returnUnavailable = options.returnUnavailable,
            includeVeteran = options.includeVeteran,
        })
        if IsAllowedAccountStatus(accountState.status, options.accountStatuses) then
            return accountState
        end
    end

    if options.includeRelationship == true and unit.relationship then
        return {
            status = unit.relationship,
            relationship = unit.relationship,
            name = name,
            realm = realm,
            key = key,
        }
    end

    return {
        status = "unknown",
        name = name,
        realm = realm,
        key = key,
    }
end
