local addonName, addonTable = ...
---@type MK
local MK = addonTable.MK
local core = addonTable
local TRANSLATE = core.TRANSLATE or function(msg) return msg end

local GROUP_ROLE_BY_ASSIGNED_ROLE = {
    TANK = "TANK",
    HEALER = "HEAL",
    DAMAGER = "DPS",
}

local GROUP_ROLE_KEYS = {
    TANK = true,
    HEAL = true,
    DPS = true,
}

local function NormalizeGroupRoleKey(role)
    return GROUP_ROLE_BY_ASSIGNED_ROLE[role] or (GROUP_ROLE_KEYS[role] and role) or nil
end

local function HasAnyGroupRole(roles)
    if type(roles) ~= "table" then
        return false
    end

    for roleKey in pairs(GROUP_ROLE_KEYS) do
        if roles[roleKey] == true then
            return true
        end
    end

    return false
end

local function SnapshotBool(value)
    return value and "1" or "0"
end

local function SnapshotValue(value)
    if value == nil then
        return "-"
    end

    return tostring(value)
end

local function SnapshotBestRuns(bestRuns)
    if type(bestRuns) ~= "table" or type(bestRuns.runs) ~= "table" then
        return ""
    end

    local parts = {
        SnapshotValue(bestRuns.score),
    }
    for _, run in ipairs(bestRuns.runs) do
        parts[#parts + 1] = table.concat({
            SnapshotValue(run.mapID),
            SnapshotValue(run.level),
            SnapshotValue(run.score),
            SnapshotValue(run.durationMS),
            SnapshotBool(run.timed),
        }, ":")
    end

    return table.concat(parts, ",")
end

local function GetGroupListKind(listKind)
    return listKind == "carpool" and "carpool" or "group"
end

local function GetGroupSortConfig(listKind)
    if GetGroupListKind(listKind) == "carpool" then
        return core.CARPOOL_SORT_DEFAULT or core.GROUP_SORT_DEFAULT,
            core.CARPOOL_SORT_INITIAL_DIRECTION or core.GROUP_SORT_INITIAL_DIRECTION,
            "carpoolSort"
    end

    return core.GROUP_SORT_DEFAULT, core.GROUP_SORT_INITIAL_DIRECTION, "groupSort"
end

local function GetGroupSortState(owner, listKind)
    local sortDefault, sortInitialDirection, stateKey = GetGroupSortConfig(listKind)
    if not owner[stateKey] then
        local savedSort = owner.db and owner.db.profile and owner.db.profile[stateKey]
        local savedKey = type(savedSort) == "table" and savedSort.key or nil
        local savedDirection = type(savedSort) == "table" and savedSort.direction or nil
        if GetGroupListKind(listKind) == "group" and savedKey == "presence" then
            savedKey = sortDefault.key
            savedDirection = sortDefault.direction
        end

        if not sortInitialDirection[savedKey] then
            savedKey = sortDefault.key
        end

        if savedDirection ~= "asc" and savedDirection ~= "desc" then
            savedDirection = sortDefault.direction
        end

        owner[stateKey] = {
            key = savedKey,
            direction = savedDirection,
        }
    end

    return owner[stateKey]
end

local function ResetGroupSortState(owner, listKind)
    if not owner then
        return
    end

    local sortDefault, _, stateKey = GetGroupSortConfig(listKind)
    owner[stateKey] = {
        key = sortDefault.key,
        direction = sortDefault.direction,
    }

    if owner.db and owner.db.profile then
        owner.db.profile[stateKey] = {
            key = sortDefault.key,
            direction = sortDefault.direction,
        }
    end

    local tabKey = GetGroupListKind(listKind)
    if owner.lastRenderedSnapshotByTab then
        owner.lastRenderedSnapshotByTab[tabKey] = nil
    end
end

function MK:SetGroupSort(key, listKind)
    listKind = GetGroupListKind(listKind or self.activeTab)
    local _, sortInitialDirection, stateKey = GetGroupSortConfig(listKind)
    if not sortInitialDirection[key] then
        return
    end

    local sortState = GetGroupSortState(self, listKind)
    if sortState.key == key then
        sortState.direction = sortState.direction == "asc" and "desc" or "asc"
    else
        sortState.key = key
        sortState.direction = sortInitialDirection[key] or "asc"
    end

    if self.db and self.db.profile then
        self.db.profile[stateKey] = {
            key = sortState.key,
            direction = sortState.direction,
        }
    end

    if self.widgets and self.widgets.groupHeaderFrame and core.UpdateGroupHeaderSortIndicators then
        core.UpdateGroupHeaderSortIndicators(self, self.widgets.groupHeaderFrame)
    end

    if self.IsMainWindowShown and self:IsMainWindowShown() then
        self:RefreshMainWindow("SetGroupSort", string.format("%s:%s", tostring(sortState.key), tostring(sortState.direction)))
    end
end

core.GetGroupListKind = GetGroupListKind
core.GetGroupSortConfig = GetGroupSortConfig
core.GetGroupSortState = GetGroupSortState
core.ResetGroupSortState = ResetGroupSortState

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

    local normalized = value:match("^%s*(.-)%s*$")
    if not normalized or normalized == "" then
        return nil
    end

    return normalized:lower()
end

local function GetCharacterIdentifierAliases(value)
    local aliases = {}
    local normalized = NormalizeCharacterIdentifier(value)
    if not normalized then
        return aliases
    end

    aliases[normalized] = true

    local plainName = Ambiguate and Ambiguate(value, "none") or value:match("^[^-]+") or value
    local normalizedPlainName = NormalizeCharacterIdentifier(plainName)
    if normalizedPlainName then
        aliases[normalizedPlainName] = true
    end

    return aliases
end

local function AddCharacterIdentifierAliases(target, value)
    if type(target) ~= "table" then
        return
    end

    for alias in pairs(GetCharacterIdentifierAliases(value)) do
        target[alias] = true
    end
end

local function AddCharacterEntryAliases(target, characterEntry)
    if not characterEntry then
        return
    end

    local character = characterEntry.data or {}
    AddCharacterIdentifierAliases(target, character.name)
    AddCharacterIdentifierAliases(target, characterEntry.key)
    AddCharacterIdentifierAliases(target, characterEntry.owner)
end

local function GetGroupRoleKeyForUnit(unit)
    if type(unit) ~= "string" or unit == "" or not UnitGroupRolesAssigned then
        return nil
    end

    return NormalizeGroupRoleKey(UnitGroupRolesAssigned(unit))
end

local function BuildGroupRoleTable(roleKey)
    local roles = {}
    if roleKey then
        roles[roleKey] = true
    end
    return roles
end

local function IdentifierMatchesCharacterEntry(identifier, characterEntry)
    if type(identifier) ~= "string" or not characterEntry then
        return false
    end

    local aliases = GetCharacterIdentifierAliases(identifier)
    local character = characterEntry.data or {}
    local candidates = {
        character.name,
        characterEntry.key,
        characterEntry.owner,
    }

    for _, candidate in ipairs(candidates) do
        for alias in pairs(GetCharacterIdentifierAliases(candidate)) do
            if aliases[alias] then
                return true
            end
        end
    end

    return false
end

local function IndexGroupRosterMember(cache, member)
    if type(cache) ~= "table" or type(member) ~= "table" then
        return
    end

    for alias in pairs(GetCharacterIdentifierAliases(member.fullName)) do
        cache.memberByAlias[alias] = member
    end
    for alias in pairs(GetCharacterIdentifierAliases(member.name)) do
        cache.memberByAlias[alias] = member
    end
end

local function IndexGroupRosterSort(cache, identifier, rosterIndex)
    if type(cache) ~= "table" or type(identifier) ~= "string" or identifier == "" then
        return
    end

    cache.rosterIndexByAlias = cache.rosterIndexByAlias or {}
    for alias in pairs(GetCharacterIdentifierAliases(identifier)) do
        cache.rosterIndexByAlias[alias] = rosterIndex
    end
end

local function AddGroupRosterCacheMember(cache, member)
    if type(cache) ~= "table" or type(member) ~= "table" or type(member.fullName) ~= "string" or member.fullName == "" then
        return false
    end

    cache.members[#cache.members + 1] = member
    AddCharacterIdentifierAliases(cache.activeAliases, member.name)
    AddCharacterIdentifierAliases(cache.activeAliases, member.fullName)
    IndexGroupRosterMember(cache, member)
    IndexGroupRosterSort(cache, member.fullName, member.rosterIndex)
    IndexGroupRosterSort(cache, member.name, member.rosterIndex)
    return true
end

local function AddTestGroupRosterMembers(cache, owner, startingRosterIndex, playerFullName)
    local testMembers = owner and owner.testGroupRosterMembers
    if type(testMembers) ~= "table" then
        return startingRosterIndex
    end

    local rosterIndex = tonumber(startingRosterIndex) or 0
    for _, testMember in ipairs(testMembers) do
        local fullName = testMember and testMember.fullName
        if type(fullName) == "string" and fullName ~= "" and fullName ~= playerFullName then
            local member = {
                unit = nil,
                fullName = fullName,
                name = testMember.name or fullName,
                realm = testMember.realm,
                class = testMember.class,
                connected = testMember.connected ~= false,
                roleKey = testMember.roleKey,
                rosterIndex = rosterIndex + 1,
                isTest = true,
            }
            if AddGroupRosterCacheMember(cache, member) then
                rosterIndex = member.rosterIndex
            end
        end
    end

    return rosterIndex
end

local function BuildGroupRosterCache(owner)
    local cache = {
        members = {},
        activeAliases = {},
        memberByAlias = {},
        rosterIndexByAlias = {},
    }

    local playerName = UnitName and UnitName("player") or nil
    local playerFullName = GetUnitName and GetUnitName("player", true) or playerName
    AddCharacterIdentifierAliases(cache.activeAliases, playerName)
    AddCharacterIdentifierAliases(cache.activeAliases, playerFullName)
    IndexGroupRosterSort(cache, playerName, 0)
    IndexGroupRosterSort(cache, playerFullName, 0)
    local rosterIndex = 0

    if owner and owner.IsInGroupForComm and owner:IsInGroupForComm() then
        local inRaid = IsInRaid and IsInRaid()
        local inGroup = IsInGroup and IsInGroup()
        local groupPrefix = inRaid and "raid" or "party"
        local memberCount = GetNumGroupMembers and GetNumGroupMembers() or 0

        if not inRaid and inGroup then
            memberCount = math.max(memberCount - 1, 0)
        end

        for index = 1, memberCount do
            local unit = groupPrefix .. index
            if UnitExists and UnitExists(unit) then
                local fullName = GetUnitName and GetUnitName(unit, true) or nil
                local shortUnitName = UnitName and UnitName(unit) or nil
                local shortName, realm
                if fullName then
                    shortName, realm = strsplit("-", fullName)
                end

                if fullName and fullName ~= playerFullName then
                    rosterIndex = rosterIndex + 1
                    AddGroupRosterCacheMember(cache, {
                        unit = unit,
                        fullName = fullName,
                        name = shortName or fullName,
                        realm = realm,
                        class = select(2, UnitClass(unit)),
                        connected = not UnitIsConnected or UnitIsConnected(unit),
                        roleKey = GetGroupRoleKeyForUnit(unit),
                        rosterIndex = rosterIndex,
                    })
                else
                    AddCharacterIdentifierAliases(cache.activeAliases, shortUnitName)
                    AddCharacterIdentifierAliases(cache.activeAliases, fullName)
                    AddCharacterIdentifierAliases(cache.activeAliases, shortName)
                end
            end
        end
    end

    AddTestGroupRosterMembers(cache, owner, rosterIndex, playerFullName)
    return cache
end

local function GetGroupRosterCache(owner)
    owner = owner or MK
    if owner and owner.groupListRosterCache then
        return owner.groupListRosterCache
    end

    return BuildGroupRosterCache(owner)
end

local function Pack(...)
    return { n = select("#", ...), ... }
end

local function WithGroupListRosterCache(owner, callback, ...)
    if not (owner and owner.BeginGroupListRosterCache and owner.EndGroupListRosterCache) then
        return callback(...)
    end

    owner:BeginGroupListRosterCache()
    local result = Pack(pcall(callback, ...))
    owner:EndGroupListRosterCache()

    if not result[1] then
        error(result[2], 2)
    end

    return unpack(result, 2, result.n)
end

function MK:BeginGroupListRosterCache()
    self.groupListRosterCacheDepth = (self.groupListRosterCacheDepth or 0) + 1
    if self.groupListRosterCacheDepth == 1 then
        self.groupListRosterCache = BuildGroupRosterCache(self)
    end

    return self.groupListRosterCache
end

function MK:EndGroupListRosterCache()
    local depth = (self.groupListRosterCacheDepth or 0) - 1
    if depth <= 0 then
        self.groupListRosterCacheDepth = nil
        self.groupListRosterCache = nil
        return
    end

    self.groupListRosterCacheDepth = depth
end

local function GetGroupRosterMembers()
    return GetGroupRosterCache(MK).members or {}
end

local function FindGroupRosterMemberForEntry(characterEntry)
    local cache = GetGroupRosterCache(MK)
    local memberByAlias = cache.memberByAlias or {}
    local character = characterEntry and characterEntry.data or {}
    local candidates = {
        character.name,
        characterEntry and characterEntry.key,
        characterEntry and characterEntry.owner,
    }

    for _, candidate in ipairs(candidates) do
        for alias in pairs(GetCharacterIdentifierAliases(candidate)) do
            local member = memberByAlias[alias]
            if member then
                return member
            end
        end
    end

    return nil
end

local function FindGroupRosterMemberByIdentifier(identifier)
    if type(identifier) ~= "string" or identifier == "" then
        return nil
    end

    local cache = GetGroupRosterCache(MK)
    local memberByAlias = cache.memberByAlias or {}
    for alias in pairs(GetCharacterIdentifierAliases(identifier)) do
        local member = memberByAlias[alias]
        if member then
            return member
        end
    end

    return nil
end

core.GetCharacterIdentifierAliases = GetCharacterIdentifierAliases
core.IdentifierMatchesCharacterEntry = IdentifierMatchesCharacterEntry
core.GetGroupRosterMembers = GetGroupRosterMembers
core.FindGroupRosterMemberForEntry = FindGroupRosterMemberForEntry
core.FindGroupRosterMemberByIdentifier = FindGroupRosterMemberByIdentifier

local function GetCurrentPlayerShortName()
    local name = UnitName and UnitName("player")
    if type(name) == "string" and name ~= "" then
        return name
    end

    return TRANSLATE("战团")
end

local function GetCurrentPlayerOwnerKey()
    local name = UnitName and UnitName("player") or TRANSLATE("战团")
    local realm = GetRealmName and GetRealmName() or nil
    if type(realm) == "string" and realm ~= "" and type(name) == "string" and not name:find("-", 1, true) then
        return string.format("%s-%s", name, realm)
    end

    return name
end

local function GetShortNameFromCharacterKey(characterKey)
    if type(characterKey) ~= "string" or characterKey == "" then
        return nil
    end

    return characterKey:match("^([^-]+)") or characterKey
end

local function GetCurrentPlayerClassToken()
    if not UnitClass then
        return nil
    end

    local _, classToken = UnitClass("player")
    return classToken
end

local function CharacterIdentifiersCanMatch(left, right)
    if type(left) ~= "string" or type(right) ~= "string" then
        return false
    end

    local rightAliases = GetCharacterIdentifierAliases(right)
    for alias in pairs(GetCharacterIdentifierAliases(left)) do
        if rightAliases[alias] then
            return true
        end
    end

    return false
end

local function FormatWarbandGroupCharacterName(owner, character)
    local characterName = (character and character.name) or TRANSLATE("角色选项卡")
    local ownerName = GetShortNameFromCharacterKey(owner) or GetCurrentPlayerShortName()
    local suffix = string.format(TRANSLATE("%s的战团"), ownerName)
    local classColor = GetClassColorString(character and character.class)
    return string.format("%s-%s", ColorText(characterName, classColor), ColorText(suffix, "ff58d7ff"))
end

local function GetWarbandSourceDisplayInfo(characterEntry)
    local ownerKey = characterEntry and characterEntry.owner
    local sourceName = characterEntry and characterEntry.warbandSourceName
    local sourceClass = characterEntry and characterEntry.warbandSourceClass

    if type(sourceName) ~= "string" or sourceName == "" then
        sourceName = GetShortNameFromCharacterKey(ownerKey) or GetCurrentPlayerShortName()
    end

    if type(sourceClass) ~= "string" or sourceClass == "" then
        local member = FindGroupRosterMemberByIdentifier(ownerKey) or FindGroupRosterMemberByIdentifier(sourceName)
        sourceClass = member and member.class or nil
    end

    if type(sourceClass) ~= "string" or sourceClass == "" then
        local currentOwnerKey = GetCurrentPlayerOwnerKey()
        local currentOwnerName = GetCurrentPlayerShortName()
        if CharacterIdentifiersCanMatch(ownerKey, currentOwnerKey) or CharacterIdentifiersCanMatch(sourceName, currentOwnerName) then
            sourceClass = GetCurrentPlayerClassToken()
        end
    end

    return sourceName, sourceClass
end

local function GetWarbandSourceDisplayName(characterEntry)
    return GetWarbandSourceDisplayInfo(characterEntry)
end

core.GetCurrentPlayerShortName = GetCurrentPlayerShortName
core.GetCurrentPlayerOwnerKey = GetCurrentPlayerOwnerKey
core.GetShortNameFromCharacterKey = GetShortNameFromCharacterKey
core.GetWarbandSourceDisplayName = GetWarbandSourceDisplayName
core.GetWarbandSourceDisplayInfo = GetWarbandSourceDisplayInfo

local function NormalizeGroupSortText(value)
    return string.lower(tostring(value or ""))
end

local IsCurrentPlayerGroupEntry

local CLASS_SORT_TEXT_BY_TOKEN = {
    WARRIOR = "zhanshi",
    PALADIN = "shengqishi",
    HUNTER = "lieren",
    ROGUE = "qianxingzhe",
    PRIEST = "mushi",
    DEATHKNIGHT = "siwangqishi",
    SHAMAN = "samanzhisi",
    MAGE = "fashi",
    WARLOCK = "shushi",
    MONK = "wuseng",
    DRUID = "deluyi",
    DEMONHUNTER = "emolieshou",
    EVOKER = "huanmushi",
}

local ARMOR_SORT_TEXT_BY_KEY = {
    ["板甲"] = "banjia",
    ["布甲"] = "bujia",
    ["皮甲"] = "pijia",
    ["锁甲"] = "suojia",
}

local ROLE_SORT_TEXT_BY_KEY = {
    DPS = "shuchu",
    TANK = "tanke",
    HEAL = "zhiliao",
}

local function GetCharacterFullName(character, fallbackKey)
    local name = character and character.name or fallbackKey
    local realm = character and character.realm or nil
    if type(name) ~= "string" or name == "" then
        return tostring(fallbackKey or "")
    end
    if name:find("-", 1, true) then
        return name
    end
    if type(realm) == "string" and realm ~= "" then
        return string.format("%s-%s", name, realm)
    end
    return name
end

local function GetGroupClassSortText(character)
    local classToken = character and character.class
    return CLASS_SORT_TEXT_BY_TOKEN[classToken] or NormalizeGroupSortText(classToken)
end

local function GetGroupArmorSortText(owner, character)
    local armorType = owner.GetArmorTypeKey and owner:GetArmorTypeKey(character) or nil
    return ARMOR_SORT_TEXT_BY_KEY[armorType] or NormalizeGroupSortText(owner:GetArmorType(character))
end

local function GetGroupEntryRoleSortText(owner, entry)
    local roles = owner.GetCharacterEntryDisplayRoles and owner:GetCharacterEntryDisplayRoles(entry)
        or owner:NormalizeRoles(entry and entry.data or {})
    local values = {}
    for _, roleKey in ipairs(owner:GetRoleOrder()) do
        if roles[roleKey] then
            values[#values + 1] = ROLE_SORT_TEXT_BY_KEY[roleKey] or roleKey
        end
    end
    table.sort(values)
    return table.concat(values, ",")
end

local function GetGroupRosterSortValue(owner, entry)
    if IsCurrentPlayerGroupEntry(entry) then
        return 0
    end

    local member = owner.GetCharacterEntryGroupRosterMember and owner:GetCharacterEntryGroupRosterMember(entry) or nil
    if member and member.rosterIndex then
        return member.rosterIndex
    end

    local cache = GetGroupRosterCache(owner)
    local indexes = cache.rosterIndexByAlias or {}
    local character = entry and entry.data or {}
    local candidates = {
        character.name,
        entry and entry.key,
        entry and entry.owner,
    }

    for _, candidate in ipairs(candidates) do
        for alias in pairs(GetCharacterIdentifierAliases(candidate)) do
            local index = indexes[alias]
            if index ~= nil then
                return index
            end
        end
    end

    return 9999
end

local function GetGroupEntrySortValue(owner, entry, sortKey, activeGroupAliases)
    local character = entry and entry.data or {}

    if sortKey == "roster" then
        return GetGroupRosterSortValue(owner, entry)
    end

    if sortKey == "presence" then
        if owner:IsCharacterEntryOfflineInGroup(entry) then
            return 1
        end

        return owner:IsCharacterEntryCurrentlyInGroup(entry, activeGroupAliases) and 2 or 0
    end

    if sortKey == "name" then
        return GetGroupClassSortText(character)
    end

    if sortKey == "armor" then
        return GetGroupArmorSortText(owner, character)
    end

    if sortKey == "key" then
        return tonumber(character.mapID) or 999999
    end

    if sortKey == "rating" then
        if owner.GetGroupRatingDisplayScore then
            local displayRating = owner:GetGroupRatingDisplayScore(entry)
            if tonumber(displayRating) then
                return tonumber(displayRating) or 0
            end
        end
        return tonumber(character.rating) or 0
    end

    if sortKey == "roles" then
        return NormalizeGroupSortText(GetGroupEntryRoleSortText(owner, entry))
    end

    return NormalizeGroupSortText(GetCharacterFullName(character, entry and entry.key))
end

local function GetKeystoneSortParts(entry)
    local character = entry and entry.data or {}
    return tonumber(character.mapID), tonumber(character.keyLevel) or 0
end

local function BuildKeystoneDungeonSortLevels(entries, sortDirection)
    local levelsByMapID = {}
    for _, entry in ipairs(entries or {}) do
        local mapID, keyLevel = GetKeystoneSortParts(entry)
        if mapID then
            local existing = levelsByMapID[mapID]
            if existing == nil
                or (sortDirection == "asc" and keyLevel < existing)
                or (sortDirection ~= "asc" and keyLevel > existing)
            then
                levelsByMapID[mapID] = keyLevel
            end
        end
    end

    return levelsByMapID
end

local function CompareKeystoneSortEntries(left, right, sortDirection, dungeonSortLevels)
    local leftMapID, leftLevel = GetKeystoneSortParts(left)
    local rightMapID, rightLevel = GetKeystoneSortParts(right)
    local leftHasDungeon = leftMapID ~= nil
    local rightHasDungeon = rightMapID ~= nil

    if leftHasDungeon ~= rightHasDungeon then
        return leftHasDungeon
    end

    if leftMapID and rightMapID and leftMapID ~= rightMapID then
        local leftGroupLevel = dungeonSortLevels and dungeonSortLevels[leftMapID] or leftLevel
        local rightGroupLevel = dungeonSortLevels and dungeonSortLevels[rightMapID] or rightLevel
        if leftGroupLevel ~= rightGroupLevel then
            if sortDirection == "asc" then
                return leftGroupLevel < rightGroupLevel
            end

            return leftGroupLevel > rightGroupLevel
        end

        return leftMapID < rightMapID
    end

    if leftLevel ~= rightLevel then
        if sortDirection == "asc" then
            return leftLevel < rightLevel
        end

        return leftLevel > rightLevel
    end

    return nil
end

local function CharacterEntryMatchesIdentifier(characterEntry, identifier)
    if not characterEntry or type(identifier) ~= "string" or identifier == "" then
        return false
    end

    local identifierAliases = GetCharacterIdentifierAliases(identifier)
    local character = characterEntry.data or {}
    local candidates = {
        character.name,
        characterEntry.key,
        characterEntry.owner,
    }

    for _, candidate in ipairs(candidates) do
        for alias in pairs(GetCharacterIdentifierAliases(candidate)) do
            if identifierAliases[alias] then
                return true
            end
        end
    end

    return false
end

function IsCurrentPlayerGroupEntry(characterEntry)
    if not characterEntry then
        return false
    end

    return CharacterEntryMatchesIdentifier(characterEntry, GetCurrentPlayerOwnerKey())
        or CharacterEntryMatchesIdentifier(characterEntry, GetCurrentPlayerShortName())
        or CharacterEntryMatchesIdentifier(characterEntry, UnitName and UnitName("player"))
        or CharacterEntryMatchesIdentifier(characterEntry, GetUnitName and GetUnitName("player", true))
end

local function SortGroupViewCharacters(owner, entries, activeGroupAliases)
    local sortState = GetGroupSortState(owner)
    local sortKey = sortState.key or core.GROUP_SORT_DEFAULT.key
    local sortDirection = sortState.direction or core.GROUP_SORT_DEFAULT.direction
    local pinCurrentPlayerFirst = sortKey == core.GROUP_SORT_DEFAULT.key
        and sortDirection == core.GROUP_SORT_DEFAULT.direction
    local keystoneDungeonSortLevels = sortKey == "key" and BuildKeystoneDungeonSortLevels(entries, sortDirection) or nil

    table.sort(entries, function(left, right)
        if pinCurrentPlayerFirst then
            local leftIsCurrent = IsCurrentPlayerGroupEntry(left)
            local rightIsCurrent = IsCurrentPlayerGroupEntry(right)
            if leftIsCurrent ~= rightIsCurrent then
                return leftIsCurrent
            end
        end

        if sortKey == "key" then
            local keyCompare = CompareKeystoneSortEntries(left, right, sortDirection, keystoneDungeonSortLevels)
            if keyCompare ~= nil then
                return keyCompare
            end
        end

        local leftValue = GetGroupEntrySortValue(owner, left, sortKey, activeGroupAliases)
        local rightValue = GetGroupEntrySortValue(owner, right, sortKey, activeGroupAliases)

        if leftValue ~= rightValue then
            if sortDirection == "desc" then
                return leftValue > rightValue
            end

            return leftValue < rightValue
        end

        local leftName = NormalizeGroupSortText(GetCharacterFullName(left and left.data, left and left.key))
        local rightName = NormalizeGroupSortText(GetCharacterFullName(right and right.data, right and right.key))
        if leftName == rightName then
            return NormalizeGroupSortText(left.owner) < NormalizeGroupSortText(right.owner)
        end

        return leftName < rightName
    end)
end

local function IsCurrentPlayerCarpoolSource(characterEntry, currentOwnerKey, currentOwnerName)
    return CharacterIdentifiersCanMatch(characterEntry and characterEntry.owner, currentOwnerKey)
        or CharacterIdentifiersCanMatch(characterEntry and characterEntry.warbandSourceName, currentOwnerName)
end

local function GetCarpoolSourceSortValues(characterEntry, currentOwnerKey, currentOwnerName)
    local sourceName = GetWarbandSourceDisplayName(characterEntry)
    local sourceKey = NormalizeGroupSortText(characterEntry and characterEntry.owner or sourceName)
    local sourceNameKey = NormalizeGroupSortText(sourceName)
    local priority = IsCurrentPlayerCarpoolSource(characterEntry, currentOwnerKey, currentOwnerName) and 0 or 1

    return priority, sourceNameKey, sourceKey
end

local function CompareGroupSortValues(leftValue, rightValue, sortDirection)
    if leftValue == rightValue then
        return nil
    end

    if sortDirection == "desc" then
        return leftValue > rightValue
    end

    return leftValue < rightValue
end

local function GetCarpoolEntrySortValue(owner, entry, sortKey, activeGroupAliases)
    return GetGroupEntrySortValue(owner, entry, sortKey, activeGroupAliases)
end

local function SortCarpoolViewCharacters(owner, entries, currentOwnerKey, currentOwnerName, activeGroupAliases)
    local sortState = GetGroupSortState(owner, "carpool")
    local sortKey = sortState.key or core.CARPOOL_SORT_DEFAULT.key
    local sortDirection = sortState.direction or core.CARPOOL_SORT_DEFAULT.direction
    local sortBySource = sortKey == "meetingStone"
    local keystoneDungeonSortLevels = sortKey == "key" and BuildKeystoneDungeonSortLevels(entries, sortDirection) or nil

    table.sort(entries, function(left, right)
        local leftPriority, leftSourceName, leftSourceKey = GetCarpoolSourceSortValues(left, currentOwnerKey, currentOwnerName)
        local rightPriority, rightSourceName, rightSourceKey = GetCarpoolSourceSortValues(right, currentOwnerKey, currentOwnerName)

        if sortBySource then
            if leftPriority ~= rightPriority then
                return leftPriority < rightPriority
            end

            local sourceNameCompare = CompareGroupSortValues(leftSourceName, rightSourceName, sortDirection)
            if sourceNameCompare ~= nil then
                return sourceNameCompare
            end

            local sourceKeyCompare = CompareGroupSortValues(leftSourceKey, rightSourceKey, sortDirection)
            if sourceKeyCompare ~= nil then
                return sourceKeyCompare
            end
        else
            if sortKey == "key" then
                local keyCompare = CompareKeystoneSortEntries(left, right, sortDirection, keystoneDungeonSortLevels)
                if keyCompare ~= nil then
                    return keyCompare
                end
            end

            local leftValue = GetCarpoolEntrySortValue(owner, left, sortKey, activeGroupAliases)
            local rightValue = GetCarpoolEntrySortValue(owner, right, sortKey, activeGroupAliases)
            local valueCompare = CompareGroupSortValues(leftValue, rightValue, sortDirection)
            if valueCompare ~= nil then
                return valueCompare
            end
        end

        local leftName = NormalizeGroupSortText(left and left.data and left.data.name or left and left.key)
        local rightName = NormalizeGroupSortText(right and right.data and right.data.name or right and right.key)
        if leftName ~= rightName then
            return leftName < rightName
        end

        if leftSourceName ~= rightSourceName then
            return leftSourceName < rightSourceName
        end

        if leftSourceKey ~= rightSourceKey then
            return leftSourceKey < rightSourceKey
        end

        return NormalizeGroupSortText(left and left.key) < NormalizeGroupSortText(right and right.key)
    end)
end

function MK:GetSharedGroupViewCharacters()
    ---@type BKCharacterEntry[]
    local entries = {}
    local activeGroupAliases = self:GetActiveGroupCharacterAliases()
    local shownAliases = {}
    local currentPlayerEntryAdded = false

    local function addEntry(characterEntry)
        if IsCurrentPlayerGroupEntry(characterEntry) then
            currentPlayerEntryAdded = true
        end
        AddCharacterEntryAliases(shownAliases, characterEntry)
        entries[#entries + 1] = characterEntry
    end

    if self.GetCharacters then
        for _, characterEntry in ipairs(self:GetCharacters(true)) do
            local isCurrentPlayerEntry = IsCurrentPlayerGroupEntry(characterEntry)
            if self:IsCharacterEntryCurrentlyInGroup(characterEntry, activeGroupAliases)
                and (self:IsCharacterShownInGroup(characterEntry.data) or isCurrentPlayerEntry)
            then
                characterEntry.warbandTag = self.db.profile.warbandTag or ""
                characterEntry.owner = characterEntry.owner or GetCurrentPlayerOwnerKey()
                addEntry(characterEntry)
            end
        end
    end

    if not currentPlayerEntryAdded then
        local currentOwnerKey = GetCurrentPlayerOwnerKey()
        local currentCharacter = self.GetCharacterDataByKey and self:GetCharacterDataByKey(currentOwnerKey) or nil
        if currentCharacter then
            addEntry({
                key = currentOwnerKey,
                owner = currentOwnerKey,
                warbandTag = self.db.profile.warbandTag or "",
                data = currentCharacter,
            })
        end
    end

    if self.GetGroupCharacters then
        for _, characterEntry in ipairs(self:GetGroupCharacters()) do
            if self:IsCharacterEntryCurrentlyInGroup(characterEntry, activeGroupAliases) then
                if not (currentPlayerEntryAdded and IsCurrentPlayerGroupEntry(characterEntry)) then
                    addEntry(characterEntry)
                end
            end
        end
    end

    if self.IsInGroupForComm and self:IsInGroupForComm() then
        for _, member in ipairs(GetGroupRosterMembers()) do
            local alreadyShown = false
            for alias in pairs(GetCharacterIdentifierAliases(member.fullName)) do
                if shownAliases[alias] then
                    alreadyShown = true
                    break
                end
            end
            if not alreadyShown then
                for alias in pairs(GetCharacterIdentifierAliases(member.name)) do
                    if shownAliases[alias] then
                        alreadyShown = true
                        break
                    end
                end
            end

            if not alreadyShown then
                local isOffline = member.connected == false
                local rosterEntry = {
                    key = member.fullName,
                    owner = member.fullName,
                    source = "roster",
                    warbandTag = "",
                    isRosterOnly = true,
                    isOffline = isOffline or nil,
                    data = {
                        name = member.fullName,
                        class = member.class,
                        realm = member.realm,
                        hasKeystone = false,
                        showInGroup = true,
                        hide = false,
                        roles = BuildGroupRoleTable(member.roleKey),
                        source = "roster",
                        isRosterOnly = true,
                        isOffline = isOffline or nil,
                    },
                }
                AddCharacterEntryAliases(shownAliases, rosterEntry)
                entries[#entries + 1] = rosterEntry
            end
        end
    end

    SortGroupViewCharacters(self, entries, activeGroupAliases)

    return entries
end

function MK:GetCarpoolViewCharacters()
    ---@type BKCharacterEntry[]
    local entries = {}
    local activeGroupAliases = self:GetActiveGroupCharacterAliases()
    local currentOwnerKey = GetCurrentPlayerOwnerKey()
    local currentOwnerName = GetCurrentPlayerShortName()
    local currentOwnerClass = GetCurrentPlayerClassToken()

    if self.GetCharacters then
        for _, characterEntry in ipairs(self:GetCharacters(true)) do
            if self:IsCharacterShownInGroup(characterEntry.data)
                and not self:IsCharacterEntryCurrentlyInGroup(characterEntry, activeGroupAliases)
            then
                characterEntry.owner = characterEntry.owner or currentOwnerKey
                characterEntry.warbandSourceName = currentOwnerName
                characterEntry.warbandSourceClass = currentOwnerClass
                characterEntry.warbandTag = self.db and self.db.profile and self.db.profile.warbandTag or ""
                entries[#entries + 1] = characterEntry
            end
        end
    end

    if self.GetGroupCharacters then
        for _, characterEntry in ipairs(self:GetGroupCharacters()) do
            if not self:IsCharacterEntryCurrentlyInGroup(characterEntry, activeGroupAliases)
                and not (characterEntry.isRosterOnly or characterEntry.data and characterEntry.data.isRosterOnly)
            then
                local sourceMember = FindGroupRosterMemberByIdentifier(characterEntry.owner)
                characterEntry.warbandSourceName = (sourceMember and sourceMember.name) or GetShortNameFromCharacterKey(characterEntry.owner)
                characterEntry.warbandSourceClass = sourceMember and sourceMember.class or characterEntry.warbandSourceClass
                entries[#entries + 1] = characterEntry
            end
        end
    end

    SortCarpoolViewCharacters(self, entries, currentOwnerKey, currentOwnerName, activeGroupAliases)
    return entries
end

function MK:GetActiveGroupCharacterAliases()
    return GetGroupRosterCache(self).activeAliases or {}
end

function MK:IsCharacterEntryCurrentlyInGroup(characterEntry, activeAliases)
    local character = characterEntry and characterEntry.data or nil
    if not character then
        return false
    end

    local aliases = activeAliases or self:GetActiveGroupCharacterAliases()
    for alias in pairs(GetCharacterIdentifierAliases(character.name or characterEntry.key)) do
        if aliases[alias] then
            return true
        end
    end

    return false
end

function MK:GetCharacterEntryGroupAssignedRoleKey(characterEntry)
    if IsCurrentPlayerGroupEntry(characterEntry) then
        return GetGroupRoleKeyForUnit("player")
    end

    local member = FindGroupRosterMemberForEntry(characterEntry)
    return member and member.roleKey or nil
end

function MK:GetCharacterEntryDisplayRoles(characterEntry)
    local assignedRoleKey = self:GetCharacterEntryGroupAssignedRoleKey(characterEntry)
    if assignedRoleKey then
        return BuildGroupRoleTable(assignedRoleKey), "roster"
    end

    local applicantRating = self.GetMeetingStoneApplicantRating and self:GetMeetingStoneApplicantRating(characterEntry)
    local applicantRoleKey = NormalizeGroupRoleKey(applicantRating and applicantRating.role)
    if applicantRoleKey then
        return BuildGroupRoleTable(applicantRoleKey), "meetingstone_applicant"
    end
    if HasAnyGroupRole(applicantRating and applicantRating.roles) then
        return applicantRating.roles, "meetingstone_applicant"
    end

    return self:NormalizeRoles(characterEntry and characterEntry.data or nil), "stored"
end

function MK:GetCharacterEntryGroupRosterMember(characterEntry)
    return FindGroupRosterMemberForEntry(characterEntry)
end

function MK:IsCharacterEntryOfflineInGroup(characterEntry)
    if characterEntry and characterEntry.isOffline == true then
        return true
    end

    local character = characterEntry and characterEntry.data or nil
    if character and character.isRosterOnly == true and character.isOffline == true then
        return true
    end

    local member = self:GetCharacterEntryGroupRosterMember(characterEntry)
    return member and member.connected == false or false
end

local function BuildGroupListTabSnapshot(owner, entries, listKind)
    local parts = {}
    local activeGroupAliases = owner:GetActiveGroupCharacterAliases()
    local sortDefault = listKind == "carpool" and core.CARPOOL_SORT_DEFAULT or core.GROUP_SORT_DEFAULT
    local sortState = GetGroupSortState(owner, listKind)
    local activeSortKey = sortState.key or sortDefault.key
    local activeSortDirection = sortState.direction or sortDefault.direction
    local isCarpoolList = listKind == "carpool"

    parts[#parts + 1] = table.concat({
        SnapshotValue(activeSortKey),
        SnapshotValue(activeSortDirection),
        SnapshotBool(IsGroupLeader and IsGroupLeader()),
        SnapshotBool(not owner.IsMeetingStoneManagementAvailable or owner:IsMeetingStoneManagementAvailable()),
    }, "~")

    for _, characterEntry in ipairs(entries) do
        local character = characterEntry.data or {}
        local roles = owner.GetCharacterEntryDisplayRoles and owner:GetCharacterEntryDisplayRoles(characterEntry)
            or owner:NormalizeRoles(character)
        local warbandTag = characterEntry.warbandTag or ""
        local isCurrentGroupEntry = owner:IsCharacterEntryCurrentlyInGroup(characterEntry, activeGroupAliases)
        local isOffline = (not isCarpoolList) and owner:IsCharacterEntryOfflineInGroup(characterEntry) or false
        local isRosterOnly = characterEntry.isRosterOnly or character.isRosterOnly

        parts[#parts + 1] = table.concat({
            SnapshotValue(characterEntry.key),
            SnapshotValue(characterEntry.owner),
            SnapshotValue(warbandTag),
            SnapshotValue(character.name),
            SnapshotValue(character.class),
            SnapshotValue(character.specID),
            SnapshotValue(character.specName),
            SnapshotValue(character.specIcon),
            SnapshotValue(character.keyLevel),
            SnapshotValue(character.mapID),
            SnapshotValue(character.dungeonName),
            SnapshotValue(character.keystoneLink),
            SnapshotBool(character.hasKeystone),
            SnapshotValue(character.rating),
            SnapshotBool(owner:IsCharacterShownInGroup(character)),
            SnapshotBool(roles.TANK),
            SnapshotBool(roles.HEAL),
            SnapshotBool(roles.DPS),
            SnapshotValue(SnapshotBestRuns(character.bestRuns)),
            SnapshotBool(isCurrentGroupEntry),
            SnapshotBool(isOffline),
            SnapshotBool(isRosterOnly),
            SnapshotValue(characterEntry.warbandSourceName),
            SnapshotValue(characterEntry.warbandSourceClass),
        }, "~")
    end

    return table.concat(parts, "||")
end

function MK:BuildGroupTabSnapshot()
    return WithGroupListRosterCache(self, function(owner)
        return BuildGroupListTabSnapshot(owner, owner:GetSharedGroupViewCharacters(), "group")
    end, self)
end

function MK:BuildCarpoolTabSnapshot()
    return WithGroupListRosterCache(self, function(owner)
        return BuildGroupListTabSnapshot(owner, owner:GetCarpoolViewCharacters(), "carpool")
    end, self)
end


core.GroupListData = {
    GetListKind = GetGroupListKind,
    GetSortState = GetGroupSortState,
    ResetSortState = ResetGroupSortState,
    GetWarbandSourceDisplayName = GetWarbandSourceDisplayName,
}
