GuildHelper = GuildHelper or {}

local addon = GuildHelper

addon.Reconciler = addon.Reconciler or {}

local TABLES = { "roster", "mytoons", "mythics", "content", "groups", "federation" }
local SNAPSHOT_TABLES = {
    roster = true,
    mytoons = true,
    mythics = true,
    content = true,
    groups = true,
    federation = true,
}
local SNAPSHOT_REQUEST_ORDER = { "mytoons", "mythics", "content", "groups", "roster" }
local MAX_CONFLICTS = 50
local APPLY_BATCH_SIZE = 25
local APPLY_BATCH_DELAY_SECONDS = 0.01
local STATUS_REQUEST_COOLDOWN_SECONDS = 5
local PENDING_PROBE_TIMEOUT_SECONDS = 15
local CROSS_GUILD_ROSTER_STALE_SECONDS = 24 * 60 * 60
local PARTY_MYTHICS_AUTOSYNC_COOLDOWN_SECONDS = 5
local GROUP_STATUS_BROADCAST_COOLDOWN_SECONDS = 5
local ROSTER_JOURNAL_RETENTION_DAYS = 30
local ROSTER_JOURNAL_MAX_ENTRIES_PER_GUILD = 512
local DEFAULT_SYNC_PROTOCOL_VERSION = 2
local LOCAL_STATUS_CACHE_MAX_AGE_SECONDS = 2
local STATUS_RESPONSE_CACHE_MAX_AGE_SECONDS = 15
local GM_PRIORITY_TABLES = {
    federation = true,
    content = true,
    groups = true,
}

local SIGNATURE_CACHE_FIELDS = {
    noteCapturedAt = true,
    syncSignature = true,
    syncSignatureTable = true,
    syncSignatureId = true,
    syncSignatureRevision = true,
    syncSignatureUpdatedAt = true,
}

local ROSTER_SYNC_STRIPPED_FIELDS = {
    battleTag = true,
    birthdate = true,
    interests = true,
    itemLevel = true,
    lastSeenAt = true,
    officerNote = true,
    online = true,
    professions = true,
    readiness = true,
    syncSignature = true,
    syncSignatureId = true,
    syncSignatureRevision = true,
    syncSignatureUpdatedAt = true,
    spec = true,
    updatedBy = true,
}

local function deepCopy(value)
    if type(value) ~= "table" then
        return value
    end

    local copy = {}

    for key, nestedValue in pairs(value) do
        copy[key] = deepCopy(nestedValue)
    end

    return copy
end

function addon.Reconciler:IsManualOnlySync()
    return addon.Store:GetProfile().sync.manualOnly and true or false
end

function addon.Reconciler:IsVerboseSyncEnabled()
    return addon.Store:GetProfile().sync.verbose and true or false
end

function addon.Reconciler:Notify(level, ...)
    if level == "debug" and not self:IsVerboseSyncEnabled() then
        return
    end

    if level == "info" and not self:IsVerboseSyncEnabled() then
        return
    end

    addon:Print(...)
end

function addon.Reconciler:SetManualOnlySync(enabled)
    addon.Store:GetProfile().sync.manualOnly = enabled and true or false

    if addon.MainFrame and addon.MainFrame.RefreshCurrentView then
        addon.MainFrame:RefreshCurrentView()
    end
end

local function refreshAfterTableUpdate(tableName)
    if not addon.MainFrame or not addon.MainFrame.MarkBackgroundRefreshPending then
        return
    end
    addon.MainFrame:MarkBackgroundRefreshPending()
end

local function stableInsert(list, value)
    list[#list + 1] = value
end

local function getTableStore(tableName)
    if tableName == "mytoons" then
        return addon.Store:GetMyToons()
    end

    if tableName == "mythics" then
        return addon.Store:GetMythics()
    end

    if tableName == "federation" then
        return addon.Store:GetGuild().federation
    end

    return addon.Store:GetGuild()[tableName]
end

local function simpleChecksum(parts)
    local checksum = 0
    local combined = table.concat(parts, "|")

    for index = 1, #combined do
        checksum = (checksum * 33 + string.byte(combined, index)) % 2147483647
    end

    return tostring(checksum)
end

local function splitGuildKey(guildKey)
    local normalizedGuildKey = tostring(guildKey or "")
    local guildName, realmName = normalizedGuildKey:match("^(.*)%-(.+)$")

    if not guildName or not realmName then
        return normalizedGuildKey, nil
    end

    return guildName, realmName
end

local function sanitizeRosterRecordForSync(record)
    if type(record) ~= "table" then
        return record
    end

    local sanitized = deepCopy(record)

    for fieldName in pairs(ROSTER_SYNC_STRIPPED_FIELDS) do
        sanitized[fieldName] = nil
    end

    return sanitized
end

local function splitCapabilityList(value)
    local capabilities = {}

    for capability in string.gmatch(tostring(value or ""), "([^|]+)") do
        local normalized = tostring(capability or ""):match("^%s*(.-)%s*$") or ""

        if normalized ~= "" then
            capabilities[normalized] = true
        end
    end

    return capabilities
end

local function encodeCapabilities(capabilities)
    local values = {}

    for capability, enabled in pairs(capabilities or {}) do
        if enabled then
            values[#values + 1] = tostring(capability)
        end
    end
    table.sort(values)

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

local function parseVersionString(value)
    local parts = {}

    for token in string.gmatch(tostring(value or ""), "(%d+)") do
        parts[#parts + 1] = tonumber(token) or 0
    end

    return parts
end

local function compareVersionStrings(leftValue, rightValue)
    local leftParts = parseVersionString(leftValue)
    local rightParts = parseVersionString(rightValue)
    local length = math.max(#leftParts, #rightParts)

    for index = 1, length do
        local leftPart = leftParts[index] or 0
        local rightPart = rightParts[index] or 0

        if leftPart ~= rightPart then
            return leftPart > rightPart and 1 or -1
        end
    end

    return 0
end

local function getLocalAddonVersion()
    if type(addon.GetVersion) == "function" then
        return tostring(addon:GetVersion() or "0")
    end

    return "0"
end

local function buildLocalStatusCacheKey(self)
    local parts = {
        tostring(getLocalAddonVersion()),
        tostring(addon.Store:GetPlayerName() or ""),
        tostring(addon.FederationService and addon.FederationService:GetHomeGuild() or ""),
        tostring(self:GetSyncProtocolVersion() or 0),
        encodeCapabilities(self:GetSyncCapabilities()),
        addon.FederationService and addon.FederationService:CanManageLinkedGuilds() and "1" or "0",
    }
    local tableMeta = addon.Store:GetSync().tableMeta or {}

    for _, tableName in ipairs(TABLES) do
        local meta = type(tableMeta[tableName]) == "table" and tableMeta[tableName] or nil
        stableInsert(parts, string.format("%s:%s", tableName, tostring(meta and (meta.updatedAt or 0) or 0)))
    end

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

local function buildLocalStatusSnapshot(self)
    local status = {
        addonVersion = getLocalAddonVersion(),
        player = addon.Store:GetPlayerName(),
        homeGuild = addon.FederationService:GetHomeGuild(),
        isGuildMaster = addon.FederationService and addon.FederationService:CanManageLinkedGuilds() or false,
        generatedAt = time(),
        syncCapabilities = self:GetSyncCapabilities(),
        syncVersion = self:GetSyncProtocolVersion(),
        tables = {},
    }

    for _, tableName in ipairs(TABLES) do
        status.tables[tableName] = self:BuildTableStatus(tableName)
    end

    return status
end

function addon.Reconciler:GetSyncProtocolVersion()
    if type(addon.GetSyncMetadataVersion) == "function" then
        local configured = addon:GetSyncMetadataVersion()

        if configured and configured > 0 then
            return configured
        end
    end

    return DEFAULT_SYNC_PROTOCOL_VERSION
end

function addon.Reconciler:GetLocalAddonVersion()
    return getLocalAddonVersion()
end

function addon.Reconciler:IsPeerOnNewerAddonVersion(peer)
    local peerRecord = type(peer) == "table" and peer or self:GetPeer(peer)

    if not peerRecord or not peerRecord.addonVersion or peerRecord.addonVersion == "" then
        return false
    end

    return compareVersionStrings(peerRecord.addonVersion, getLocalAddonVersion()) > 0
end

function addon.Reconciler:GetSyncCapabilities()
    return {
        rosterJournal = true,
        versionNotice = true,
    }
end

function addon.Reconciler:HasPeerCapability(peer, capability)
    local peerRecord = type(peer) == "table" and peer or self:GetPeer(peer)
    return type(peerRecord) == "table"
        and type(peerRecord.syncCapabilities) == "table"
        and peerRecord.syncCapabilities[capability] == true
end

function addon.Reconciler:IsPeerSyncCompatible(peer)
    local peerRecord = type(peer) == "table" and peer or self:GetPeer(peer)

    if not peerRecord then
        return false, "peer status is unavailable"
    end

    local peerVersion = tonumber(peerRecord.syncVersion) or 0
    local localVersion = self:GetSyncProtocolVersion()

    if peerVersion ~= localVersion then
        return false, string.format("sync version mismatch (local %d, peer %d)", localVersion, peerVersion)
    end

    return true
end

function addon.Reconciler:MaybeNotifyPeerUpdate(peer)
    local peerRecord = type(peer) == "table" and peer or self:GetPeer(peer)

    if not peerRecord or not peerRecord.addonVersion or peerRecord.addonVersion == "" then
        return
    end

    if compareVersionStrings(peerRecord.addonVersion, getLocalAddonVersion()) <= 0 then
        return
    end

    self._versionNotices = type(self._versionNotices) == "table" and self._versionNotices or {}

    if self._versionNotices[peerRecord.addonVersion] then
        return
    end

    self._versionNotices[peerRecord.addonVersion] = true
    addon:Print(string.format("A newer version of GH3 is available: %s. You are on %s.", peerRecord.addonVersion, getLocalAddonVersion()))
end

local function buildVisibleRosterGuildSet()
    local visibleGuilds = {}

    if addon.FederationService and addon.FederationService.GetAllGuildKeys then
        for _, guildKey in ipairs(addon.FederationService:GetAllGuildKeys()) do
            if guildKey and guildKey ~= "" then
                visibleGuilds[guildKey] = true
            end
        end
    else
        local homeGuild = addon.Store:EnsureHomeGuild()

        if homeGuild and homeGuild ~= "" then
            visibleGuilds[homeGuild] = true
        end
    end

    return visibleGuilds
end

local function isRosterRecordStatusVisible(record, visibleGuilds, guildKey)
    if type(record) ~= "table" then
        return false
    end

    if record.deleted or record.leftGuild then
        return false
    end

    if guildKey then
        return record.guildName == guildKey
    end

    return type(visibleGuilds) == "table" and visibleGuilds[record.guildName] == true
end

local function collectSignatureParts(parts, prefix, value)
    local valueType = type(value)

    if valueType == "table" then
        local keys = {}

        for key in pairs(value) do
            if not SIGNATURE_CACHE_FIELDS[key] then
                keys[#keys + 1] = key
            end
        end

        table.sort(keys, function(left, right)
            return tostring(left) < tostring(right)
        end)

        for _, key in ipairs(keys) do
            collectSignatureParts(parts, prefix .. "." .. tostring(key), value[key])
        end

        return
    end

    parts[#parts + 1] = string.format("%s=%s", prefix, tostring(value))
end

local function sanitizeRecordForSignature(tableName, record)
    if type(record) ~= "table" then
        return record
    end

    if tableName == "mytoons" then
        local sanitized = deepCopy(record)
        sanitized.officerNote = nil
        return sanitized
    end

    if tableName == "mythics" then
        return record
    end

    if tableName ~= "roster" then
        return record
    end

    return sanitizeRosterRecordForSync(record)
end

local function sanitizeMyToonSnapshotRecord(record)
    if type(record) ~= "table" then
        return record
    end

    local sanitized = deepCopy(record)
    sanitized.officerNote = nil
    return sanitized
end

local function sanitizeMythicSnapshotRecord(record)
    if type(record) ~= "table" then
        return record
    end

    return deepCopy(record)
end

local function sanitizeRosterSnapshotRecord(record)
    if type(record) ~= "table" then
        return record
    end

    return sanitizeRosterRecordForSync(record)
end

local function formatConflictValue(value)
    local valueType = type(value)

    if valueType == "nil" then
        return "nil"
    end

    if valueType == "boolean" or valueType == "number" then
        return tostring(value)
    end

    if valueType == "string" then
        local normalized = value:gsub("\n", " ")

        if #normalized > 32 then
            normalized = normalized:sub(1, 29) .. "..."
        end

        return normalized
    end

    if valueType == "table" then
        local count = 0

        for _ in pairs(value) do
            count = count + 1
        end

        return string.format("table(%d)", count)
    end

    return valueType
end

local function findFirstDiffPath(left, right, path)
    local leftType = type(left)
    local rightType = type(right)
    local currentPath = path or "record"

    if leftType ~= rightType then
        return currentPath, left, right
    end

    if leftType ~= "table" then
        if left ~= right then
            return currentPath, left, right
        end

        return nil
    end

    local orderedKeys = {}
    local seen = {}

    for key in pairs(left or {}) do
        seen[key] = true
        orderedKeys[#orderedKeys + 1] = key
    end

    for key in pairs(right or {}) do
        if not seen[key] then
            orderedKeys[#orderedKeys + 1] = key
        end
    end

    table.sort(orderedKeys, function(a, b)
        return tostring(a) < tostring(b)
    end)

    for _, key in ipairs(orderedKeys) do
        local nextPath = string.format("%s.%s", currentPath, tostring(key))
        local diffPath, leftValue, rightValue = findFirstDiffPath(left and left[key], right and right[key], nextPath)

        if diffPath then
            return diffPath, leftValue, rightValue
        end
    end

    return nil
end

local function buildConflictReason(tableName, localRecord, remoteRecord, fallbackReason)
    local localComparable = sanitizeRecordForSignature(tableName, localRecord)
    local remoteComparable = sanitizeRecordForSignature(tableName, remoteRecord)
    local diffPath, leftValue, rightValue = findFirstDiffPath(localComparable, remoteComparable)

    if not diffPath then
        return fallbackReason or "metadata mismatch"
    end

    local cleanedPath = diffPath:gsub("^record%.", "")
    return string.format("%s (%s != %s)", cleanedPath, formatConflictValue(leftValue), formatConflictValue(rightValue))
end

local function collectDiffLines(left, right, path, lines, limit)
    if #lines >= limit then
        return
    end

    local leftType = type(left)
    local rightType = type(right)
    local currentPath = path or "record"

    if leftType ~= rightType then
        lines[#lines + 1] = string.format("%s: %s -> %s", currentPath, formatConflictValue(left), formatConflictValue(right))
        return
    end

    if leftType ~= "table" then
        if left ~= right then
            lines[#lines + 1] = string.format("%s: %s -> %s", currentPath, formatConflictValue(left), formatConflictValue(right))
        end
        return
    end

    local orderedKeys = {}
    local seen = {}

    for key in pairs(left or {}) do
        seen[key] = true
        orderedKeys[#orderedKeys + 1] = key
    end

    for key in pairs(right or {}) do
        if not seen[key] then
            orderedKeys[#orderedKeys + 1] = key
        end
    end

    table.sort(orderedKeys, function(a, b)
        return tostring(a) < tostring(b)
    end)

    for _, key in ipairs(orderedKeys) do
        if #lines >= limit then
            return
        end

        collectDiffLines(left and left[key], right and right[key], string.format("%s.%s", currentPath, tostring(key)), lines, limit)
    end
end

local function buildConflictCompareText(tableName, id, sender, localMeta, remoteMeta, localRecord, remoteRecord)
    local localComparable = sanitizeRecordForSignature(tableName, localRecord)
    local remoteComparable = sanitizeRecordForSignature(tableName, remoteRecord)
    local diffLines = {}

    collectDiffLines(localComparable, remoteComparable, "record", diffLines, 40)

    local lines = {
        string.format("table: %s", tostring(tableName or "-")),
        string.format("record: %s", tostring(id or "-")),
        string.format("sender: %s", tostring(sender or "-")),
        string.format("local revision: %d", localMeta and (localMeta.revision or 0) or 0),
        string.format("remote revision: %d", remoteMeta and (remoteMeta.revision or 0) or 0),
        string.format("local updatedAt: %d", localMeta and (localMeta.updatedAt or 0) or 0),
        string.format("remote updatedAt: %d", remoteMeta and (remoteMeta.updatedAt or 0) or 0),
        string.format("local signature: %s", localMeta and (localMeta.signature or "0") or "0"),
        string.format("remote signature: %s", remoteMeta and (remoteMeta.signature or "0") or "0"),
        "",
        "Differences:",
    }

    if #diffLines == 0 then
        lines[#lines + 1] = "No comparable field differences were captured. This conflict record may predate compare logging or only differs in metadata outside the sanitized compare set."
    else
        for _, line in ipairs(diffLines) do
            lines[#lines + 1] = line
        end
    end

    return table.concat(lines, "\n")
end

function addon.Reconciler:BuildTableStatus(tableName)
    if tableName == "federation" then
        local linkedGuilds = {}

        if addon.FederationService and addon.FederationService.GetLinkedGuilds and addon.FederationService.IsApprovedLinkedGuild then
            for _, record in ipairs(addon.FederationService:GetLinkedGuilds()) do
                local guildKey = record.guildKey or record.name

                if guildKey and addon.FederationService:IsApprovedLinkedGuild(guildKey) then
                    linkedGuilds[#linkedGuilds + 1] = {
                        guildKey = guildKey,
                        name = guildKey,
                        requestedAt = record.requestedAt or record.addedAt,
                        requestedBy = record.requestedBy or record.addedBy,
                        remoteRequestedAt = record.remoteRequestedAt,
                        remoteRequestedBy = record.remoteRequestedBy,
                        approvedAt = record.approvedAt,
                        approvedBy = record.approvedBy,
                        remoteApprovedAt = record.remoteApprovedAt,
                        remoteApprovedBy = record.remoteApprovedBy,
                    }
                end
            end
        end

        table.sort(linkedGuilds, function(left, right)
            return (left.guildKey or "") < (right.guildKey or "")
        end)

        local snapshot = {
            homeGuild = addon.Store:EnsureHomeGuild(),
            linkedGuilds = linkedGuilds,
            permissions = {},
        }
        local signatureParts = {}
        local tableMeta = addon.Store:GetSync().tableMeta.federation or {}

        collectSignatureParts(signatureParts, "federation", snapshot)

        return {
            tableName = tableName,
            count = #linkedGuilds,
            checksum = simpleChecksum(signatureParts),
            updatedAt = tableMeta.updatedAt or 0,
        }
    end

    local source = getTableStore(tableName)
    local ids = {}
    local signatureParts = {}
    local newest = 0
    local count = 0
    local visibleGuilds = tableName == "roster" and buildVisibleRosterGuildSet() or nil

    if type(source) ~= "table" then
        return {
            tableName = tableName,
            count = 0,
            checksum = "0",
            updatedAt = 0,
        }
    end

    for id, record in pairs(source) do
        if tableName ~= "roster" or isRosterRecordStatusVisible(record, visibleGuilds) then
            stableInsert(ids, id)
        end
    end

    table.sort(ids)

    for _, id in ipairs(ids) do
        local record = source[id]

        if type(record) == "table" then
            count = count + 1
            newest = math.max(newest, record.updatedAt or 0)

            if tableName == "roster" then
                stableInsert(signatureParts, string.format("%s:%s", id, self:BuildRecordSignature(tableName, id, record)))
            else
                stableInsert(signatureParts, string.format("%s:%s:%s:%s", id, record.revision or 0, record.updatedAt or 0, record.deleted and 1 or 0))
            end
        else
            count = count + 1
            stableInsert(signatureParts, string.format("%s:%s", id, tostring(record)))
        end
    end

    return {
        tableName = tableName,
        count = count,
        checksum = simpleChecksum(signatureParts),
        updatedAt = newest,
    }
end

local function doTableStatusesMatch(leftStatus, rightStatus)
    if type(leftStatus) ~= "table" or type(rightStatus) ~= "table" then
        return false
    end

    return (leftStatus.count or 0) == (rightStatus.count or 0)
        and (leftStatus.checksum or "0") == (rightStatus.checksum or "0")
end

function addon.Reconciler:BuildRecordSignature(tableName, id, record)
    if tableName == "roster"
        and type(record) == "table"
        and type(record.syncSignature) == "string"
        and record.syncSignature ~= ""
        and tostring(record.syncSignatureId or "") == tostring(id or "")
        and (tonumber(record.syncSignatureRevision) or 0) == (tonumber(record.revision) or 0)
        and (tonumber(record.syncSignatureUpdatedAt) or 0) == (tonumber(record.updatedAt) or 0) then
        return record.syncSignature
    end

    if tableName ~= "roster" and type(record) == "table" then
        self._recordSignatureCache = type(self._recordSignatureCache) == "table" and self._recordSignatureCache or {}

        local cacheKey = string.format("%s\31%s", tostring(tableName or "unknown"), tostring(id or record.id or record.fullName or ""))
        local cacheEntry = self._recordSignatureCache[cacheKey]
        local revision = tonumber(record.revision) or 0
        local updatedAt = tonumber(record.updatedAt) or 0

        if cacheEntry
            and cacheEntry.revision == revision
            and cacheEntry.updatedAt == updatedAt
            and type(cacheEntry.signature) == "string"
            and cacheEntry.signature ~= "" then
            return cacheEntry.signature
        end

        local parts = { "id=" .. tostring(id) }
        collectSignatureParts(parts, "record", sanitizeRecordForSignature(tableName, record))
        local signature = simpleChecksum(parts)

        self._recordSignatureCache[cacheKey] = {
            revision = revision,
            updatedAt = updatedAt,
            signature = signature,
        }

        return signature
    end

    local parts = { "id=" .. tostring(id) }
    collectSignatureParts(parts, "record", sanitizeRecordForSignature(tableName, record))
    local signature = simpleChecksum(parts)

    if tableName == "roster" and type(record) == "table" then
        record.syncSignature = signature
        record.syncSignatureId = tostring(id or record.id or record.fullName or "")
        record.syncSignatureRevision = tonumber(record.revision) or 0
        record.syncSignatureUpdatedAt = tonumber(record.updatedAt) or 0
    end

    return signature
end

local function getRosterJournalStore()
    local syncState = addon.Store:GetSync()

    if type(syncState.rosterJournal) ~= "table" then
        syncState.rosterJournal = {
            byGuild = {},
            watermarks = {},
        }
    end

    syncState.rosterJournal.byGuild = type(syncState.rosterJournal.byGuild) == "table" and syncState.rosterJournal.byGuild or {}
    syncState.rosterJournal.watermarks = type(syncState.rosterJournal.watermarks) == "table" and syncState.rosterJournal.watermarks or {}
    return syncState.rosterJournal
end

local function getRosterJournalGuildState(guildKey)
    local journalStore = getRosterJournalStore()

    if type(guildKey) ~= "string" or guildKey == "" then
        return nil
    end

    local guildState = journalStore.byGuild[guildKey]

    if type(guildState) ~= "table" then
        guildState = {
            entries = {},
            nextSequence = 1,
            updatedAt = 0,
        }
        journalStore.byGuild[guildKey] = guildState
    end

    guildState.entries = type(guildState.entries) == "table" and guildState.entries or {}
    guildState.nextSequence = math.max(1, tonumber(guildState.nextSequence) or 1)
    guildState.updatedAt = tonumber(guildState.updatedAt) or 0
    return guildState
end

local function pruneRosterJournalGuildState(guildState, now)
    if type(guildState) ~= "table" then
        return
    end

    local entries = guildState.entries
    if type(entries) ~= "table" or #entries == 0 then
        return
    end

    local currentTime = tonumber(now) or time()
    local cutoff = currentTime - (ROSTER_JOURNAL_RETENTION_DAYS * 86400)
    local total = #entries

    -- Skip past entries that exceed the count cap (oldest removed first).
    local keepFrom = math.max(1, total - ROSTER_JOURNAL_MAX_ENTRIES_PER_GUILD + 1)

    -- Continue past any expired entries at the surviving head.
    while keepFrom <= total do
        local entry = entries[keepFrom]
        local updatedAt = tonumber(entry and entry.updatedAt) or 0
        if not (updatedAt > 0 and updatedAt < cutoff) then
            break
        end
        keepFrom = keepFrom + 1
    end

    if keepFrom > 1 then
        -- Compact in a single pass instead of repeated table.remove(t, 1).
        local kept = total - keepFrom + 1
        for i = 1, kept do
            entries[i] = entries[keepFrom + i - 1]
        end
        for i = kept + 1, total do
            entries[i] = nil
        end
    end
end

function addon.Reconciler:GetRosterJournalWatermark(guildKey)
    local journalStore = getRosterJournalStore()
    return math.max(0, tonumber(journalStore.watermarks[guildKey]) or 0)
end

function addon.Reconciler:SetRosterJournalWatermark(guildKey, sequence)
    if type(guildKey) ~= "string" or guildKey == "" then
        return 0
    end

    local journalStore = getRosterJournalStore()
    local currentValue = math.max(0, tonumber(journalStore.watermarks[guildKey]) or 0)
    local nextValue = math.max(currentValue, math.max(0, tonumber(sequence) or 0))
    journalStore.watermarks[guildKey] = nextValue
    return nextValue
end

function addon.Reconciler:AppendRosterJournalRecord(record)
    if type(record) ~= "table" then
        return false
    end

    local localHomeGuild = addon.Store:EnsureHomeGuild()
    local guildKey = record.guildName

    if not guildKey or guildKey == "" or guildKey ~= localHomeGuild then
        return false
    end

    local guildState = getRosterJournalGuildState(guildKey)
    local sequence = guildState.nextSequence
    local updatedAt = tonumber(record.updatedAt) or time()

    guildState.entries[#guildState.entries + 1] = {
        sequence = sequence,
        guildKey = guildKey,
        recordId = record.id or record.fullName,
        updatedAt = updatedAt,
        deleted = record.deleted and true or false,
        record = sanitizeRosterSnapshotRecord(record),
    }
    guildState.nextSequence = sequence + 1
    guildState.updatedAt = updatedAt

    pruneRosterJournalGuildState(guildState, updatedAt)
    self:SetRosterJournalWatermark(guildKey, sequence)
    return true
end

function addon.Reconciler:BuildRosterGuildStatus(guildKey)
    local source = getTableStore("roster") or {}
    local ids = {}
    local signatureParts = {}
    local newest = 0
    local count = 0

    if type(guildKey) ~= "string" or guildKey == "" then
        return {
            tableName = "roster",
            guildKey = guildKey,
            count = 0,
            checksum = "0",
            updatedAt = 0,
        }
    end

    for id, record in pairs(source) do
        if isRosterRecordStatusVisible(record, nil, guildKey) then
            ids[#ids + 1] = id
        end
    end

    table.sort(ids)

    for _, id in ipairs(ids) do
        local record = source[id]

        if type(record) == "table" then
            count = count + 1
            newest = math.max(newest, record.updatedAt or 0)
            stableInsert(signatureParts, string.format("%s:%s", id, self:BuildRecordSignature("roster", id, record)))
        end
    end

    return {
        tableName = "roster",
        guildKey = guildKey,
        count = count,
        checksum = simpleChecksum(signatureParts),
        updatedAt = newest,
    }
end

local function buildRosterRepairSnapshotTable(guildKey)
    local source = getTableStore("roster") or {}
    local snapshot = {}

    for id, record in pairs(source) do
        if type(record) == "table" and record.guildName == guildKey then
            snapshot[id] = sanitizeRosterSnapshotRecord(record)
        end
    end

    return snapshot
end

local function encodeRosterJournalRequest(request)
    local guildKey = type(request) == "table" and tostring(request.guildKey or "") or ""
    local afterSequence = math.max(0, tonumber(request and request.afterSequence) or 0)
    local status = type(request and request.localStatus) == "table" and request.localStatus or nil
    local count = status and math.max(0, tonumber(status.count) or 0) or 0
    local checksum = status and tostring(status.checksum or "0") or "0"
    local updatedAt = status and math.max(0, tonumber(status.updatedAt) or 0) or 0

    return table.concat({
        guildKey,
        tostring(afterSequence),
        tostring(count),
        checksum,
        tostring(updatedAt),
    }, "\29")
end

function addon.Reconciler:DeserializeRosterJournalRequest(payload)
    if type(payload) == "string" and string.find(payload, "\29", 1, true) then
        local guildKey, afterSequence, count, checksum, updatedAt = strsplit("\29", payload, 5)

        if not guildKey or guildKey == "" then
            return nil, "roster journal request guild key was missing"
        end

        return {
            guildKey = guildKey,
            afterSequence = tonumber(afterSequence) or 0,
            localStatus = {
                tableName = "roster",
                guildKey = guildKey,
                count = tonumber(count) or 0,
                checksum = checksum or "0",
                updatedAt = tonumber(updatedAt) or 0,
            },
        }
    end

    local decoded = addon.Json:Decode(payload)

    if type(decoded) ~= "table" then
        return nil, "roster journal request payload was not a table"
    end

    return decoded
end

function addon.Reconciler:DeserializeRosterJournalPayload(payload)
    local decoded = addon.Json:Decode(payload)

    if type(decoded) ~= "table" then
        return nil, "roster journal payload was not a table"
    end

    return decoded
end

function addon.Reconciler:BuildRosterJournalResponse(sender, request)
    local localHomeGuild = addon.Store:EnsureHomeGuild()
    local requestedGuildKey = type(request) == "table" and tostring(request.guildKey or "") or ""
    local afterSequence = math.max(0, tonumber(request and request.afterSequence) or 0)
    local requesterStatus = type(request and request.localStatus) == "table" and request.localStatus or nil

    if requestedGuildKey == "" or requestedGuildKey ~= localHomeGuild then
        return nil, "requested roster journal guild was unavailable"
    end

    local authoritativeStatus = self:BuildRosterGuildStatus(localHomeGuild)
    local guildState = getRosterJournalGuildState(localHomeGuild)
    local latestSequence = math.max(0, (guildState.nextSequence or 1) - 1)

    if requesterStatus and doTableStatusesMatch(requesterStatus, authoritativeStatus) then
        return addon.Json:Encode({
            mode = "journal",
            guildKey = localHomeGuild,
            latestSequence = latestSequence,
            status = authoritativeStatus,
            entries = {},
        })
    end

    local earliestSequence = guildState.entries[1] and (tonumber(guildState.entries[1].sequence) or 0) or math.max(1, guildState.nextSequence or 1)

    if afterSequence < (earliestSequence - 1) then
        return addon.Json:Encode({
            mode = "repair",
            guildKey = localHomeGuild,
            latestSequence = latestSequence,
            status = authoritativeStatus,
            snapshot = buildRosterRepairSnapshotTable(localHomeGuild),
        })
    end

    local entries = {}

    for _, entry in ipairs(guildState.entries or {}) do
        if (tonumber(entry.sequence) or 0) > afterSequence then
            entries[#entries + 1] = entry
        end
    end

    if #entries == 0 and afterSequence >= latestSequence then
        return addon.Json:Encode({
            mode = "journal",
            guildKey = localHomeGuild,
            latestSequence = latestSequence,
            status = authoritativeStatus,
            entries = {},
        })
    end

    if #entries == 0 and not (requesterStatus and doTableStatusesMatch(requesterStatus, authoritativeStatus)) then
        return addon.Json:Encode({
            mode = "repair",
            guildKey = localHomeGuild,
            latestSequence = latestSequence,
            status = authoritativeStatus,
            snapshot = buildRosterRepairSnapshotTable(localHomeGuild),
        })
    end

    return addon.Json:Encode({
        mode = "journal",
        guildKey = localHomeGuild,
        latestSequence = latestSequence,
        status = authoritativeStatus,
        entries = entries,
    })
end

function addon.Reconciler:ApplyRosterJournalPayload(payload, sender, options, onComplete)
    if type(payload) ~= "table" then
        if onComplete then
            onComplete(false, "roster journal payload was not a table")
        end

        return false, "roster journal payload was not a table"
    end

    local guildKey = tostring(payload.guildKey or "")
    local latestSequence = math.max(0, tonumber(payload.latestSequence) or 0)
    local applyOptions = {}

    for key, value in pairs(options or {}) do
        applyOptions[key] = value
    end

    local function finish(ok, message, applied)
        if ok and guildKey ~= "" then
            self:SetRosterJournalWatermark(guildKey, latestSequence)
        end

        if onComplete then
            onComplete(ok, message, applied)
        end
    end

    if payload.mode == "repair" then
        local snapshot = type(payload.snapshot) == "table" and payload.snapshot or {}
        local forceRecordIds = {}

        for id in pairs(snapshot) do
            forceRecordIds[id] = true
        end

        applyOptions.forceRecordIds = forceRecordIds

        if next(snapshot) == nil then
            finish(true, nil, 0)
            return true
        end

        return self:ApplySnapshotAsync("roster", snapshot, sender, applyOptions, finish)
    end

    if payload.mode ~= "journal" then
        finish(false, "unknown roster journal mode")
        return false, "unknown roster journal mode"
    end

    local snapshot = {}
    local forceRecordIds = {}

    for _, entry in ipairs(payload.entries or {}) do
        if type(entry) == "table" and type(entry.record) == "table" then
            local recordId = entry.recordId or entry.record.id or entry.record.fullName

            if recordId then
                snapshot[recordId] = entry.record
                forceRecordIds[recordId] = true
            end
        end
    end

    applyOptions.forceRecordIds = forceRecordIds

    if next(snapshot) == nil then
        finish(true, nil, 0)
        return true
    end

    return self:ApplySnapshotAsync("roster", snapshot, sender, applyOptions, finish)
end

function addon.Reconciler:BuildManifest(tableName)
    if not self:IsSnapshotTable(tableName) then
        return nil, "unsupported table"
    end

    local source = getTableStore(tableName) or {}
    local manifest = {}

    for id, record in pairs(source) do
        if type(record) == "table" then
            manifest[id] = {
                guildName = record.guildName,
                owner = record.owner,
                revision = record.revision or 0,
                updatedAt = record.updatedAt or 0,
                updatedBy = record.updatedBy,
                deleted = record.deleted and true or false,
                signature = self:BuildRecordSignature(tableName, id, record),
            }
        end
    end

    return manifest
end

function addon.Reconciler:BuildRecordMeta(tableName, id, record)
    if type(record) ~= "table" then
        return nil
    end

    return {
        id = id,
        guildName = record.guildName,
        owner = record.owner,
        revision = record.revision or 0,
        updatedAt = record.updatedAt or 0,
        updatedBy = record.updatedBy,
        deleted = record.deleted and true or false,
        signature = self:BuildRecordSignature(tableName, id, record),
    }
end

local function compareRevisionThenTimestamp(leftMeta, rightMeta)
    local leftRevision = leftMeta and (leftMeta.revision or 0) or 0
    local rightRevision = rightMeta and (rightMeta.revision or 0) or 0

    if leftRevision ~= rightRevision then
        return leftRevision > rightRevision and 1 or -1
    end

    local leftUpdatedAt = leftMeta and (leftMeta.updatedAt or 0) or 0
    local rightUpdatedAt = rightMeta and (rightMeta.updatedAt or 0) or 0

    if leftUpdatedAt ~= rightUpdatedAt then
        return leftUpdatedAt > rightUpdatedAt and 1 or -1
    end

    return 0
end

local function compareTimestampThenRevision(leftMeta, rightMeta)
    local leftUpdatedAt = leftMeta and (leftMeta.updatedAt or 0) or 0
    local rightUpdatedAt = rightMeta and (rightMeta.updatedAt or 0) or 0

    if leftUpdatedAt ~= rightUpdatedAt then
        return leftUpdatedAt > rightUpdatedAt and 1 or -1
    end

    local leftRevision = leftMeta and (leftMeta.revision or 0) or 0
    local rightRevision = rightMeta and (rightMeta.revision or 0) or 0

    if leftRevision ~= rightRevision then
        return leftRevision > rightRevision and 1 or -1
    end

    return 0
end

function addon.Reconciler:ResolveNewestWinsAction(tableName, sender, localMeta, remoteMeta)
    if not localMeta or not remoteMeta then
        return nil
    end

    if tableName == "mytoons" then
        local winner = compareRevisionThenTimestamp(remoteMeta, localMeta)

        if winner > 0 then
            return "pull"
        end

        if winner < 0 then
            return "keep"
        end

        return nil
    end

    if tableName == "content" or tableName == "groups" then
        local winner = compareTimestampThenRevision(remoteMeta, localMeta)

        if winner > 0 then
            return "pull"
        end

        if winner < 0 then
            return "keep"
        end

        return nil
    end

    if tableName == "roster" then
        local peerHomeGuild = self:GetPeerHomeGuild(sender)
        local localHomeGuild = addon.Store:EnsureHomeGuild()
        local recordGuild = remoteMeta.guildName or localMeta.guildName
        local isCrossGuildRecord = recordGuild and localHomeGuild and recordGuild ~= localHomeGuild

        if isCrossGuildRecord then
            local remoteUpdatedAt = remoteMeta.updatedAt or 0
            local localUpdatedAt = localMeta.updatedAt or 0
            local senderOwnsRecordGuild = peerHomeGuild and recordGuild == peerHomeGuild
            local now = time()

            if senderOwnsRecordGuild and remoteUpdatedAt > localUpdatedAt and (now - localUpdatedAt) >= CROSS_GUILD_ROSTER_STALE_SECONDS then
                return "pull"
            end

            if senderOwnsRecordGuild and localUpdatedAt > remoteUpdatedAt and (now - remoteUpdatedAt) >= CROSS_GUILD_ROSTER_STALE_SECONDS then
                return "keep"
            end

            local winner = compareTimestampThenRevision(remoteMeta, localMeta)

            if winner > 0 then
                return "pull"
            end

            if winner < 0 then
                return "keep"
            end
        end

        return nil
    end

    return nil
end

function addon.Reconciler:BuildConflictKey(tableName, id, sender)
    return table.concat({ tableName or "na", tostring(id or "na"), sender or "unknown" }, "::")
end

function addon.Reconciler:GetConflict(tableName, id, sender)
    return addon.Store:GetSync().conflicts[self:BuildConflictKey(tableName, id, sender)]
end

function addon.Reconciler:GetResolution(tableName, id, sender)
    return addon.Store:GetSync().resolutions[self:BuildConflictKey(tableName, id, sender)]
end

function addon.Reconciler:StoreResolution(tableName, id, sender, localMeta, remoteMeta, decision)
    local key = self:BuildConflictKey(tableName, id, sender)

    addon.Store:GetSync().resolutions[key] = {
        key = key,
        tableName = tableName,
        recordId = id,
        sender = sender,
        decision = decision,
        localSignature = localMeta and localMeta.signature or "0",
        remoteSignature = remoteMeta and remoteMeta.signature or "0",
        resolvedAt = time(),
    }

    return addon.Store:GetSync().resolutions[key]
end

function addon.Reconciler:ClearResolution(tableName, id, sender)
    addon.Store:GetSync().resolutions[self:BuildConflictKey(tableName, id, sender)] = nil
end

function addon.Reconciler:IsSuppressedConflict(tableName, id, sender, localMeta, remoteMeta)
    local resolution = self:GetResolution(tableName, id, sender)

    if not resolution or resolution.decision ~= "keep-local" then
        return false
    end

    if resolution.localSignature ~= (localMeta and localMeta.signature or "0") then
        return false
    end

    if resolution.remoteSignature ~= (remoteMeta and remoteMeta.signature or "0") then
        return false
    end

    return true
end

function addon.Reconciler:RecordConflict(tableName, id, sender, localMeta, remoteMeta, reason, compareText)
    if self:IsSuppressedConflict(tableName, id, sender, localMeta, remoteMeta) then
        return nil
    end

    local conflicts = addon.Store:GetSync().conflicts
    local key = self:BuildConflictKey(tableName, id, sender)

    conflicts[key] = {
        key = key,
        tableName = tableName,
        recordId = id,
        sender = sender,
        reason = reason or "metadata mismatch",
        detectedAt = time(),
        localRevision = localMeta and (localMeta.revision or 0) or 0,
        remoteRevision = remoteMeta and (remoteMeta.revision or 0) or 0,
        localUpdatedAt = localMeta and (localMeta.updatedAt or 0) or 0,
        remoteUpdatedAt = remoteMeta and (remoteMeta.updatedAt or 0) or 0,
        localDeleted = localMeta and localMeta.deleted and true or false,
        remoteDeleted = remoteMeta and remoteMeta.deleted and true or false,
        localSignature = localMeta and localMeta.signature or "0",
        remoteSignature = remoteMeta and remoteMeta.signature or "0",
        compareText = compareText,
    }

    local orderedKeys = {}

    for conflictKey in pairs(conflicts) do
        orderedKeys[#orderedKeys + 1] = conflictKey
    end

    table.sort(orderedKeys, function(left, right)
        return (conflicts[left].detectedAt or 0) > (conflicts[right].detectedAt or 0)
    end)

    for index = MAX_CONFLICTS + 1, #orderedKeys do
        conflicts[orderedKeys[index]] = nil
    end

    return conflicts[key]
end

function addon.Reconciler:ClearConflict(tableName, id, sender)
    addon.Store:GetSync().conflicts[self:BuildConflictKey(tableName, id, sender)] = nil
end

function addon.Reconciler:ClearConflicts(filter)
    local conflicts = addon.Store:GetSync().conflicts
    local cleared = 0

    for key, conflict in pairs(conflicts) do
        local matchesTable = not filter or not filter.tableName or conflict.tableName == filter.tableName
        local matchesSender = not filter or not filter.sender or conflict.sender == filter.sender
        local matchesRecord = not filter or not filter.recordId or conflict.recordId == filter.recordId

        if matchesTable and matchesSender and matchesRecord then
            conflicts[key] = nil
            cleared = cleared + 1
        end
    end

    return cleared
end

function addon.Reconciler:ListConflicts()
    local rows = {}

    for _, conflict in pairs(addon.Store:GetSync().conflicts) do
        rows[#rows + 1] = conflict
    end

    table.sort(rows, function(left, right)
        return (left.detectedAt or 0) > (right.detectedAt or 0)
    end)

    return rows
end

function addon.Reconciler:SummarizeConflicts()
    local rows = self:ListConflicts()
    local summary = {
        total = #rows,
        byTable = {},
    }

    for _, conflict in ipairs(rows) do
        summary.byTable[conflict.tableName] = (summary.byTable[conflict.tableName] or 0) + 1
    end

    return summary
end

function addon.Reconciler:ShouldUseGuildMasterPriority(tableName)
    return GM_PRIORITY_TABLES[tableName] == true
end

function addon.Reconciler:IsLocalGuildMaster()
    return addon.FederationService and addon.FederationService:CanManageLinkedGuilds() or false
end

function addon.Reconciler:IsRemoteGuildMaster(sender)
    local peer = sender and self:GetPeer(sender) or nil
    return peer and peer.isGuildMaster and true or false
end

function addon.Reconciler:ResolveRolePriorityAction(tableName, sender)
    if not self:ShouldUseGuildMasterPriority(tableName) then
        return nil
    end

    local remoteIsGuildMaster = self:IsRemoteGuildMaster(sender)
    local localIsGuildMaster = self:IsLocalGuildMaster()

    if remoteIsGuildMaster and not localIsGuildMaster then
        return "pull"
    end

    if localIsGuildMaster and not remoteIsGuildMaster then
        return "keep"
    end

    return nil
end

function addon.Reconciler:ResolveManifestAction(tableName, sender, localMeta, remoteMeta)
    if not remoteMeta then
        return "none"
    end

    if not localMeta then
        return "pull"
    end

    if remoteMeta.signature == localMeta.signature then
        return "match"
    end

    local priorityAction = self:ResolveRolePriorityAction(tableName, sender)

    if priorityAction then
        return priorityAction
    end

    local newestWinsAction = self:ResolveNewestWinsAction(tableName, sender, localMeta, remoteMeta)

    if newestWinsAction then
        return newestWinsAction
    end

    local remoteDominates = (remoteMeta.revision or 0) >= (localMeta.revision or 0)
        and (remoteMeta.updatedAt or 0) >= (localMeta.updatedAt or 0)
    local localDominates = (localMeta.revision or 0) >= (remoteMeta.revision or 0)
        and (localMeta.updatedAt or 0) >= (remoteMeta.updatedAt or 0)

    if remoteDominates and not localDominates then
        return "pull"
    end

    if localDominates and not remoteDominates then
        return "keep"
    end

    return "conflict"
end

function addon.Reconciler:SerializeManifest(tableName)
    local manifest, errorMessage = self:BuildManifest(tableName)

    if not manifest then
        return nil, errorMessage
    end

    return addon.Json:Encode(manifest)
end

function addon.Reconciler:DeserializeManifest(payload)
    local decoded = addon.Json:Decode(payload)

    if type(decoded) ~= "table" then
        return nil, "manifest payload was not a table"
    end

    return decoded
end

function addon.Reconciler:GetDeltaRequestIds(tableName, remoteManifest, sender)
    local requestedIds = {}
    local localManifest = self:BuildManifest(tableName) or {}
    local seenRemoteIds = {}

    for id, remoteMeta in pairs(remoteManifest or {}) do
        seenRemoteIds[id] = true
        local localMeta = localManifest[id]

        local action = self:ResolveManifestAction(tableName, sender, localMeta, remoteMeta)

        if action == "pull" then
            requestedIds[#requestedIds + 1] = id
            self:ClearConflict(tableName, id, sender)
            self:ClearResolution(tableName, id, sender)
        elseif action == "conflict" then
            self:RecordConflict(tableName, id, sender, localMeta, remoteMeta, "diverged revisions")
        elseif action == "match" or action == "keep" then
            self:ClearConflict(tableName, id, sender)

            if action == "match" then
                self:ClearResolution(tableName, id, sender)
            end
        end
    end

    for id in pairs(localManifest) do
        if not seenRemoteIds[id] then
            self:ClearConflict(tableName, id, sender)
            self:ClearResolution(tableName, id, sender)
        end
    end

    table.sort(requestedIds)
    return requestedIds
end

function addon.Reconciler:SerializeRequestedRecords(tableName, ids)
    if not self:IsSnapshotTable(tableName) then
        return nil, "unsupported table"
    end

    local source = getTableStore(tableName) or {}
    local payloadTable = {}

    for _, id in ipairs(ids or {}) do
        if source[id] then
            payloadTable[id] = source[id]
        end
    end

    return addon.Json:Encode(payloadTable)
end

function addon.Reconciler:DeserializeRequestedIds(payload)
    local decoded = addon.Json:Decode(payload)

    if type(decoded) ~= "table" then
        return nil, "requested id payload was not a table"
    end

    return decoded
end

function addon.Reconciler:GetCachedLocalStatus(maxAgeSeconds)
    local now = time()
    local maxAge = math.max(0, tonumber(maxAgeSeconds) or LOCAL_STATUS_CACHE_MAX_AGE_SECONDS)
    local cache = type(self._localStatusCache) == "table" and self._localStatusCache or nil
    local cacheAge = cache and (now - (tonumber(cache.builtAt) or 0)) or math.huge

    if cache and type(cache.status) == "table" then
        if maxAge > 0 and cacheAge <= maxAge then
            return cache.status
        end

        local cacheKey = buildLocalStatusCacheKey(self)

        if cache.key == cacheKey then
            return cache.status
        end
    end

    local cacheKey = buildLocalStatusCacheKey(self)
    local status = buildLocalStatusSnapshot(self)
    self._localStatusCache = {
        builtAt = now,
        encoded = nil,
        key = cacheKey,
        status = status,
    }

    return status
end

function addon.Reconciler:GetCachedEncodedStatus(maxAgeSeconds)
    local status = self:GetCachedLocalStatus(maxAgeSeconds)
    local cache = self._localStatusCache

    if type(cache) == "table" and type(cache.encoded) == "string" and cache.encoded ~= "" then
        return cache.encoded
    end

    local encoded = self:EncodeStatus(status)

    if type(cache) == "table" then
        cache.encoded = encoded
    end

    return encoded
end

function addon.Reconciler:WarmLocalStatusCache(maxAgeSeconds)
    return self:GetCachedEncodedStatus(maxAgeSeconds or STATUS_RESPONSE_CACHE_MAX_AGE_SECONDS)
end

function addon.Reconciler:ScheduleLocalStatusCacheWarm(delaySeconds, maxAgeSeconds)
    local delay = math.max(0, tonumber(delaySeconds) or 0)

    if self._localStatusWarmPending then
        self._localStatusWarmRequested = true
        return false, "local status cache warm already pending"
    end

    local function warmLater()
        addon.Reconciler._localStatusWarmPending = false
        local rerunRequested = addon.Reconciler._localStatusWarmRequested
        addon.Reconciler._localStatusWarmRequested = false

        if not (addon.IsSyncSuspended and addon:IsSyncSuspended()) then
            addon.Reconciler:WarmLocalStatusCache(maxAgeSeconds)
        end

        if rerunRequested then
            addon.Reconciler:ScheduleLocalStatusCacheWarm(delaySeconds, maxAgeSeconds)
        end
    end

    self._localStatusWarmPending = true
    self._localStatusWarmRequested = false

    if not (C_Timer and C_Timer.After) then
        warmLater()
        return true
    end

    C_Timer.After(delay, warmLater)
    return true
end

function addon.Reconciler:BuildLocalStatus()
    return self:GetCachedLocalStatus(LOCAL_STATUS_CACHE_MAX_AGE_SECONDS)
end

function addon.Reconciler:EncodeStatus(status)
    local tableParts = {}

    for _, tableName in ipairs(TABLES) do
        local tableStatus = status.tables[tableName]

        stableInsert(tableParts, string.format(
            "%s,%d,%s,%d",
            tableName,
            tableStatus.count or 0,
            tableStatus.checksum or "0",
            tableStatus.updatedAt or 0
        ))
    end

    stableInsert(tableParts, string.format("@addon,0,%s,0", status.addonVersion or "0"))
    stableInsert(tableParts, string.format("@sync,%d,%s,0", status.syncVersion or 0, encodeCapabilities(status.syncCapabilities)))

    return table.concat({
        status.player or "",
        status.homeGuild or "",
        status.isGuildMaster and "1" or "0",
        tostring(status.generatedAt or 0),
        table.concat(tableParts, ";"),
    }, "\29")
end

function addon.Reconciler:DecodeStatus(payload)
    local player, homeGuild, isGuildMaster, generatedAt, tableBlob = strsplit("\29", payload or "", 5)

    if generatedAt == nil then
        generatedAt = isGuildMaster
        isGuildMaster = "0"
    end

    if not player or player == "" then
        return nil
    end

    local status = {
        addonVersion = "0",
        player = player,
        homeGuild = homeGuild,
        isGuildMaster = tostring(isGuildMaster or "0") == "1",
        generatedAt = tonumber(generatedAt) or 0,
        syncCapabilities = {},
        syncVersion = 0,
        tables = {},
    }

    for entry in string.gmatch(tableBlob or "", "([^;]+)") do
        local tableName, count, checksum, updatedAt = strsplit(",", entry)

        if tableName and tableName ~= "" then
            if tableName == "@addon" then
                status.addonVersion = checksum or "0"
            elseif tableName == "@sync" then
                status.syncVersion = tonumber(count) or 0
                status.syncCapabilities = splitCapabilityList(checksum)
            else
                status.tables[tableName] = {
                    tableName = tableName,
                    count = tonumber(count) or 0,
                    checksum = checksum or "0",
                    updatedAt = tonumber(updatedAt) or 0,
                }
            end
        end
    end

    if status.syncVersion <= 0 then
        status.syncVersion = 1
    end

    return status
end

function addon.Reconciler:MaybeAutoSyncPeerTable(sender, tableName, remoteStatus)
    if self:IsManualOnlySync() or not self:IsSnapshotTable(tableName) or type(remoteStatus) ~= "table" then
        return false
    end

    local compatible = self:IsPeerSyncCompatible(sender)

    if not compatible then
        return false
    end

    local localStatus = self:BuildTableStatus(tableName)
    if doTableStatusesMatch(remoteStatus, localStatus) then
        return false
    end

    if tableName ~= "roster" and (remoteStatus.updatedAt or 0) < (localStatus.updatedAt or 0) then
        return false
    end

    if self:GetOverlappingSyncError(sender, tableName) then
        return false
    end

    local ok = self:RequestDeltaSync(sender, tableName)
    return ok and true or false
end

function addon.Reconciler:RecordPeerStatus(sender, status)
    local isValidSender, normalizedSender = addon.Store:IsLikelyPlayerFullName(sender, GetRealmName() or "Unknown")

    if not isValidSender then
        return nil
    end

    local peers = addon.Store:GetSync().peers
    local existing = peers[normalizedSender] or {}
    local suppressAutoSync = existing.pending and true or false

    peers[normalizedSender] = {
        addonVersion = status.addonVersion,
        sender = normalizedSender,
        player = status.player,
        homeGuild = status.homeGuild,
        isGuildMaster = status.isGuildMaster and true or false,
        generatedAt = status.generatedAt,
        lastSeenAt = time(),
        pending = false,
        probeTimedOut = false,
        syncCapabilities = type(status.syncCapabilities) == "table" and status.syncCapabilities or {},
        syncVersion = tonumber(status.syncVersion) or 0,
        tables = status.tables,
    }

    local syncState = addon.Store:GetSync()
    syncState.partyMythicsNoResponse = type(syncState.partyMythicsNoResponse) == "table" and syncState.partyMythicsNoResponse or {}
    syncState.partyMythicsNoResponse[normalizedSender] = nil

    self:MaybeNotifyPeerUpdate(peers[normalizedSender])

    if suppressAutoSync then
        return
    end

    self:MaybeAutoSyncPeerTable(normalizedSender, "federation", status.tables and status.tables.federation)
    self:MaybeAutoSyncPeerTable(normalizedSender, "roster", status.tables and status.tables.roster)
    self:MaybeAutoSyncPeerTable(normalizedSender, "mytoons", status.tables and status.tables.mytoons)
    self:MaybeAutoSyncPeerTable(normalizedSender, "mythics", status.tables and status.tables.mythics)
end

function addon.Reconciler:MarkPeerSeen(sender, details)
    local isValidSender, normalizedSender = addon.Store:IsLikelyPlayerFullName(sender, GetRealmName() or "Unknown")

    if not isValidSender then
        return nil
    end

    local peers = addon.Store:GetSync().peers
    local existing = peers[normalizedSender] or {}
    local detailCapabilities = details and details.syncCapabilities or nil
    local homeGuild = details and details.homeGuild or existing.homeGuild
    local playerName = details and details.player or existing.player or normalizedSender
    local syncVersion = details and details.syncVersion ~= nil and tonumber(details.syncVersion) or tonumber(existing.syncVersion) or 0

    peers[normalizedSender] = {
        addonVersion = details and details.addonVersion or existing.addonVersion,
        sender = normalizedSender,
        player = playerName,
        homeGuild = homeGuild,
        isGuildMaster = details and details.isGuildMaster and true or (existing.isGuildMaster and true or false),
        generatedAt = details and details.generatedAt or existing.generatedAt or 0,
        lastSeenAt = time(),
        pending = false,
        probeTimedOut = false,
        syncCapabilities = type(detailCapabilities) == "table" and detailCapabilities or (type(existing.syncCapabilities) == "table" and existing.syncCapabilities or {}),
        syncVersion = syncVersion,
        tables = type(details and details.tables) == "table" and details.tables or (existing.tables or {}),
    }

    return peers[normalizedSender]
end

function addon.Reconciler:RetryKnownPeerAutoSync(tableNames)
    local requested = false

    for _, peer in pairs(addon.Store:GetSync().peers or {}) do
        if type(peer) == "table" and not peer.pending and type(peer.tables) == "table" then
            for _, tableName in ipairs(tableNames or {}) do
                local remoteStatus = peer.tables[tableName]

                if type(remoteStatus) == "table" then
                    requested = self:MaybeAutoSyncPeerTable(peer.sender, tableName, remoteStatus) or requested
                end
            end
        end
    end

    return requested
end

function addon.Reconciler:NormalizeIncomingRosterRecord(sender, incoming, current)
    if type(incoming) ~= "table" then
        return incoming
    end

    local normalized = deepCopy(incoming)
    normalized.officerNote = nil
    local currentNoteCapturedAt = type(current) == "table" and (tonumber(current.noteCapturedAt) or 0) or 0
    local incomingNoteCapturedAt = tonumber(normalized.noteCapturedAt) or 0

    if currentNoteCapturedAt > 0 and incomingNoteCapturedAt < currentNoteCapturedAt then
        normalized.note = type(current.note) == "string" and current.note or ""
        normalized.noteCapturedAt = currentNoteCapturedAt
    end

    local localHomeGuild = addon.Store:EnsureHomeGuild()
    local peerHomeGuild = self:GetPeerHomeGuild(sender)
    local currentGuildName = type(current) == "table" and current.guildName or nil
    local incomingGuildName = normalized.guildName
    local incomingVisible = incomingGuildName and addon.FederationService and addon.FederationService.IsVisibleGuild and addon.FederationService:IsVisibleGuild(incomingGuildName)
    local currentVisible = currentGuildName and addon.FederationService and addon.FederationService.IsVisibleGuild and addon.FederationService:IsVisibleGuild(currentGuildName)
    local peerVisible = peerHomeGuild and addon.FederationService and addon.FederationService.IsVisibleGuild and addon.FederationService:IsVisibleGuild(peerHomeGuild)

    if peerVisible and not incomingVisible then
        normalized.guildName = peerHomeGuild
    end

    if current and peerVisible and not currentVisible and normalized.guildName == peerHomeGuild then
        normalized.revision = math.max((normalized.revision or 0), (current.revision or 0) + 1)
        normalized.updatedAt = math.max((normalized.updatedAt or 0), (current.updatedAt or 0), time())
        normalized.updatedBy = normalized.updatedBy or sender
    end

    if normalized.guildName ~= localHomeGuild then
        normalized.online = false
    end

    return normalized
end

function addon.Reconciler:StagePeerStatusProbe(peer, homeGuild)
    local isValidPeer, normalizedPeer = addon.Store:IsLikelyPlayerFullName(peer, GetRealmName() or "Unknown")

    if not isValidPeer then
        return nil
    end

    self:ClearStalePendingPeers()

    local peers = addon.Store:GetSync().peers
    local existing = peers[normalizedPeer] or {}

    peers[normalizedPeer] = {
        sender = normalizedPeer,
        player = existing.player or normalizedPeer,
        homeGuild = homeGuild or existing.homeGuild,
        isGuildMaster = existing.isGuildMaster and true or false,
        generatedAt = existing.generatedAt or 0,
        lastSeenAt = time(),
        pending = true,
        probeTimedOut = false,
        tables = existing.tables or {},
    }

    return peers[normalizedPeer]
end

function addon.Reconciler:ClearStalePendingPeers()
    local peers = addon.Store:GetSync().peers
    local now = time()

    for sender, peer in pairs(peers) do
        if type(peer) == "table" and peer.pending and (now - (peer.lastSeenAt or 0)) >= PENDING_PROBE_TIMEOUT_SECONDS then
            peer.pending = false
            peer.probeTimedOut = true
            peer.lastSeenAt = now
        end
    end
end

function addon.Reconciler:GetPeer(sender)
    return addon.Store:GetSync().peers[sender]
end

function addon.Reconciler:GetPeerHomeGuild(peer)
    local peerRecord = type(peer) == "table" and peer or self:GetPeer(peer)

    if not peerRecord then
        return nil
    end

    local cachedHomeGuild = peerRecord.homeGuild

    if cachedHomeGuild and addon.FederationService and addon.FederationService.IsVisibleGuild and addon.FederationService:IsVisibleGuild(cachedHomeGuild) then
        return cachedHomeGuild
    end

    local sender = peerRecord.sender or (type(peer) == "string" and peer) or nil

    if sender and addon.RosterService and addon.RosterService.GetMember then
        local rosterRecord = addon.RosterService:GetMember(sender)
        local rosterGuild = rosterRecord and rosterRecord.guildName or nil

        if rosterGuild and addon.FederationService and addon.FederationService.IsVisibleGuild and addon.FederationService:IsVisibleGuild(rosterGuild) then
            peerRecord.homeGuild = rosterGuild
            return rosterGuild
        end
    end

    if addon.FederationService and addon.FederationService.GetLinkedGuilds then
        local approvedGuildKey = nil
        local approvedCount = 0

        for _, record in ipairs(addon.FederationService:GetLinkedGuilds()) do
            local guildKey = record.guildKey or record.name

            if guildKey and addon.FederationService:IsApprovedLinkedGuild(guildKey) then
                approvedGuildKey = guildKey
                approvedCount = approvedCount + 1

                if approvedCount > 1 then
                    break
                end
            end
        end

        if approvedCount == 1 then
            peerRecord.homeGuild = approvedGuildKey
            return approvedGuildKey
        end
    end

    return cachedHomeGuild
end

function addon.Reconciler:CanSyncPeerTable(peer, tableName)
    local localHomeGuild = addon.Store:EnsureHomeGuild()
    local peerRecord = type(peer) == "table" and peer or self:GetPeer(peer)
    local syncCompatible, syncError = self:IsPeerSyncCompatible(peerRecord)
    local peerHomeGuild = self:GetPeerHomeGuild(peerRecord)
    local sameGuild = peerHomeGuild and localHomeGuild and peerHomeGuild == localHomeGuild
    local peerName = peerRecord and peerRecord.sender or peer

    if not syncCompatible then
        return false, syncError
    end

    if tableName == "federation" then
        return true
    end

    if tableName == "mythics" and addon.FederationService and addon.FederationService.IsGroupedWithPeer and addon.FederationService:IsGroupedWithPeer(peerName) then
        return true
    end

    if tableName == "roster" and sameGuild then
        return false, "same-guild roster sync is disabled; live guild roster fills that data locally, so use My Toons sync for player-managed fields"
    end

    if sameGuild then
        return true
    end

    if peerHomeGuild and addon.FederationService and addon.FederationService:IsApprovedLinkedGuild(peerHomeGuild) then
        return true
    end

    return false, "cross-guild sync is locked until both guild masters approve the link"
end

function addon.Reconciler:GetPeerList()
    self:ClearStalePendingPeers()

    local peers = {}

    for _, peer in pairs(addon.Store:GetSync().peers) do
        if type(peer) == "table" and addon.Store:IsLikelyPlayerFullName(peer.sender, GetRealmName() or "Unknown") then
            peers[#peers + 1] = peer
        end
    end

    table.sort(peers, function(left, right)
        return (left.lastSeenAt or 0) > (right.lastSeenAt or 0)
    end)

    return peers
end

function addon.Reconciler:GetVisibleStatusRequestPeers()
    local peers = {}
    local seen = {}
    local playerName = addon.Store:GetPlayerName()
    local localGuildKey = addon.Store:EnsureHomeGuild()

    if not addon.RosterService or not addon.RosterService.ListVisibleMembers then
        return peers
    end

    for _, member in ipairs(addon.RosterService:ListVisibleMembers()) do
        local memberId = addon.Store:NormalizeFullName(member.id or member.fullName, GetRealmName() or "Unknown")
        local memberGuild = member.guildName
        local knownPeer = self:GetPeer(memberId)
        local isGrouped = addon.FederationService and addon.FederationService.IsGroupedWithPeer and addon.FederationService:IsGroupedWithPeer(memberId)
        local shouldWhisper = memberGuild
            and memberGuild ~= localGuildKey
            and (knownPeer ~= nil or isGrouped)
        local isValidPeer = addon.Store:IsLikelyPlayerFullName(memberId, GetRealmName() or "Unknown")

        if member.online and memberId ~= "" and memberId ~= playerName and isValidPeer and shouldWhisper and not seen[memberId] then
            seen[memberId] = true
            peers[#peers + 1] = memberId
        end
    end

    table.sort(peers)

    return peers
end

function addon.Reconciler:RequestPeerStatus(peer)
    local isValidPeer, normalizedPeer = addon.Store:IsLikelyPlayerFullName(peer, GetRealmName() or "Unknown")

    if not isValidPeer then
        return false, "no valid peer selected"
    end

    return addon.Transport:Send(normalizedPeer, "WHISPER", {
        op = "STATUS_REQ",
        sessionId = "peer-status:" .. normalizedPeer,
        tableName = "all",
        payload = "",
    })
end

function addon.Reconciler:RequestGuildStatus()
    self:ScheduleLocalStatusCacheWarm(0, STATUS_RESPONSE_CACHE_MAX_AGE_SECONDS)

    local now = time()

    if self.lastStatusRequestAt and (now - self.lastStatusRequestAt) < STATUS_REQUEST_COOLDOWN_SECONDS then
        return false, "status request cooldown active"
    end

    self.lastStatusRequestAt = now

    local sentAny = false
    local ok, errorMessage = addon.Transport:Send(nil, "GUILD", {
        op = "STATUS_REQ",
        sessionId = "guild-status",
        tableName = "all",
        payload = "",
    })

    if ok then
        sentAny = true
    end

    local linkedPeers = self:GetVisibleStatusRequestPeers()

    for _, peer in ipairs(linkedPeers) do
        local peerOk = self:RequestPeerStatus(peer)

        if peerOk then
            sentAny = true
        end
    end

    if sentAny then
        return true
    end

    return ok, errorMessage
end

function addon.Reconciler:BroadcastGroupStatus()
    local inRaid = IsInRaid and IsInRaid()
    local inGroup = IsInGroup and IsInGroup()

    if not inGroup then
        return false, "not grouped"
    end

    local now = time()
    self.lastGroupStatusBroadcastAt = tonumber(self.lastGroupStatusBroadcastAt) or 0

    if (now - self.lastGroupStatusBroadcastAt) < GROUP_STATUS_BROADCAST_COOLDOWN_SECONDS then
        return false, "group status broadcast cooldown active"
    end

    self.lastGroupStatusBroadcastAt = now

    return addon.Transport:Send(nil, inRaid and "RAID" or "PARTY", {
        op = "STATUS_RES",
        sessionId = "group-status:" .. tostring(now),
        tableName = "all",
        payload = self:GetCachedEncodedStatus(LOCAL_STATUS_CACHE_MAX_AGE_SECONDS),
    })
end

function addon.Reconciler:GetPreferredPeer()
    local peers = self:GetPeerList()

    if #peers == 0 then
        return nil
    end

    return peers[1].sender
end

function addon.Reconciler:IsSnapshotTable(tableName)
    return SNAPSHOT_TABLES[tableName] == true
end

function addon.Reconciler:HasApprovedFederationSyncTarget()
    if not addon.FederationService or not addon.FederationService.GetLinkedGuilds or not addon.FederationService.IsApprovedLinkedGuild then
        return false
    end

    for _, record in ipairs(addon.FederationService:GetLinkedGuilds()) do
        local guildKey = record.guildKey or record.name

        if guildKey and addon.FederationService:IsApprovedLinkedGuild(guildKey) then
            return true
        end
    end

    return false
end

function addon.Reconciler:GetSnapshotRequestOrder()
    local orderedTables = {}

    if self:HasApprovedFederationSyncTarget() then
        orderedTables[#orderedTables + 1] = "federation"
    end

    for _, tableName in ipairs(SNAPSHOT_REQUEST_ORDER) do
        orderedTables[#orderedTables + 1] = tableName
    end

    return orderedTables
end

local function appendSyncSummaryLine(lines, prefix, tableNames)
    if #tableNames > 0 then
        lines[#lines + 1] = string.format("%s %s", prefix, table.concat(tableNames, ", "))
    end
end

function addon.Reconciler:RequestMyToonsBundle(peer)
    if not peer or peer == "" then
        return false, "no peer selected"
    end

    local started = {}
    local matched = {}
    local issues = {}

    for _, tableName in ipairs({ "mytoons", "mythics" }) do
        local ok, message, startedSync = self:RequestBidirectionalSnapshot(peer, tableName)

        if ok then
            if startedSync == false then
                matched[#matched + 1] = tableName
            else
                started[#started + 1] = tableName
            end

            if startedSync ~= false and type(message) == "string" and message:find("failed", 1, true) then
                issues[#issues + 1] = string.format("%s (%s)", tableName, message)
            end
        else
            issues[#issues + 1] = string.format("%s (%s)", tableName, message or "failed")
        end
    end

    if #started == 0 and #matched == 0 then
        return false, table.concat(issues, "; ")
    end

    if #started == 0 and #issues == 0 then
        return true, string.format("Already matched %s.", table.concat(matched, ", "))
    end

    local parts = {}
    appendSyncSummaryLine(parts, "Started", started)
    appendSyncSummaryLine(parts, "Already matched", matched)

    if #issues > 0 then
        parts[#parts + 1] = string.format("Issues: %s", table.concat(issues, "; "))
    end

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

function addon.Reconciler:HandleGroupRosterUpdate()
    if self:IsManualOnlySync() then
        return false
    end

    if not addon:IsFeatureEnabled("mythics") then
        return false
    end

    local syncState = addon.Store:GetSync()
    syncState.partyMythicsAutoSync = type(syncState.partyMythicsAutoSync) == "table" and syncState.partyMythicsAutoSync or {}
    syncState.partyMythicsNoResponse = type(syncState.partyMythicsNoResponse) == "table" and syncState.partyMythicsNoResponse or {}

    local unitIds = {}

    if IsInRaid() then
        for index = 1, (GetNumGroupMembers() or 0) do
            unitIds[#unitIds + 1] = "raid" .. index
        end
    else
        for index = 1, (GetNumSubgroupMembers() or 0) do
            unitIds[#unitIds + 1] = "party" .. index
        end
    end

    self:ClearStalePendingPeers()

    local requestedAny = false
    local now = time()
    local partyPeers = {}

    for _, unitId in ipairs(unitIds) do
        if UnitExists(unitId) and UnitIsPlayer(unitId) and not UnitIsUnit(unitId, "player") then
            local unitName, unitRealm = UnitFullName(unitId)
            local isValidPeer, peerName = addon.Store:IsLikelyPlayerFullName(unitName, unitRealm or GetRealmName() or "Unknown")

            if isValidPeer then
                partyPeers[#partyPeers + 1] = peerName
            end
        end
    end

    table.sort(partyPeers)

    local rosterToken = table.concat(partyPeers, "|")

    if syncState.partyMythicsRosterToken ~= rosterToken then
        syncState.partyMythicsRosterToken = rosterToken
        syncState.partyMythicsAutoSync = {}
        syncState.partyMythicsNoResponse = {}
    end

    for _, peerName in ipairs(partyPeers) do
        local peerRecord = self:GetPeer(peerName)

        if type(peerRecord) == "table" and peerRecord.probeTimedOut then
            syncState.partyMythicsNoResponse[peerName] = true
        end

        if not syncState.partyMythicsNoResponse[peerName] then
            local lastRequestedAt = tonumber(syncState.partyMythicsAutoSync[peerName]) or 0

            if (now - lastRequestedAt) >= PARTY_MYTHICS_AUTOSYNC_COOLDOWN_SECONDS then
                syncState.partyMythicsAutoSync[peerName] = now

                if type(peerRecord) == "table" and not peerRecord.pending and type(peerRecord.tables) == "table" and type(peerRecord.tables.mythics) == "table" then
                    local ok = self:RequestBidirectionalSnapshot(peerName, "mythics")

                    if ok then
                        requestedAny = true
                    end
                elseif not (type(peerRecord) == "table" and peerRecord.pending) then
                    self:StagePeerStatusProbe(peerName, peerRecord and peerRecord.homeGuild or nil)

                    if self:RequestPeerStatus(peerName) then
                        requestedAny = true
                    end
                end
            end
        end
    end

    return requestedAny
end

function addon.Reconciler:SerializeTable(tableName)
    if not self:IsSnapshotTable(tableName) then
        return nil, "unsupported table"
    end

    if tableName == "federation" then
        local payload = addon.Json:Encode({
            homeGuild = addon.Store:EnsureHomeGuild(),
            linkedGuilds = addon.FederationService and addon.FederationService.GetLinkedGuilds and (function()
                local approved = {}

                for _, record in ipairs(addon.FederationService:GetLinkedGuilds()) do
                    local guildKey = record.guildKey or record.name

                    if guildKey and addon.FederationService:IsApprovedLinkedGuild(guildKey) then
                        approved[#approved + 1] = {
                            guildKey = guildKey,
                            name = guildKey,
                            requestedAt = record.requestedAt or record.addedAt,
                            requestedBy = record.requestedBy or record.addedBy,
                            remoteRequestedAt = record.remoteRequestedAt,
                            remoteRequestedBy = record.remoteRequestedBy,
                            approvedAt = record.approvedAt,
                            approvedBy = record.approvedBy,
                            remoteApprovedAt = record.remoteApprovedAt,
                            remoteApprovedBy = record.remoteApprovedBy,
                        }
                    end
                end

                return approved
            end)() or {},
            permissions = {},
        })
        return payload
    end

    if tableName == "mytoons" then
        local source = getTableStore(tableName) or {}
        local sanitized = {}

        for id, record in pairs(source) do
            sanitized[id] = sanitizeMyToonSnapshotRecord(record)
        end

        return addon.Json:Encode(sanitized)
    end

    if tableName == "mythics" then
        local source = getTableStore(tableName) or {}
        local sanitized = {}

        for id, record in pairs(source) do
            sanitized[id] = sanitizeMythicSnapshotRecord(record)
        end

        return addon.Json:Encode(sanitized)
    end

    if tableName == "roster" then
        local source = getTableStore(tableName) or {}
        local sanitized = {}

        for id, record in pairs(source) do
            sanitized[id] = sanitizeRosterSnapshotRecord(record)
        end

        return addon.Json:Encode(sanitized)
    end

    local payload = addon.Json:Encode(getTableStore(tableName) or {})
    return payload
end

function addon.Reconciler:DeserializeTable(payload)
    local decoded = addon.Json:Decode(payload)

    if type(decoded) ~= "table" then
        return nil, "snapshot payload was not a table"
    end

    return decoded
end

function addon.Reconciler:NormalizeFederationSnapshot(snapshot)
    local normalized = {
        homeGuild = nil,
        linkedGuilds = {},
        permissions = {},
    }

    if type(snapshot) ~= "table" then
        return normalized
    end

    if type(snapshot.homeGuild) == "string" and snapshot.homeGuild ~= "" then
        local isValidHomeGuild, normalizedHomeGuild = addon.FederationService:ValidateGuildKey(snapshot.homeGuild)

        if isValidHomeGuild then
            normalized.homeGuild = normalizedHomeGuild
        end
    end

    local seenGuildKeys = {}

    if normalized.homeGuild then
        seenGuildKeys[normalized.homeGuild] = true
    end

    if type(snapshot.linkedGuilds) == "table" then
        for _, record in ipairs(snapshot.linkedGuilds) do
            if type(record) == "table" then
                local guildKey = record.guildKey or record.name
                local isValidGuildKey, normalizedGuildKey = addon.FederationService:ValidateGuildKey(guildKey)

                if isValidGuildKey and not seenGuildKeys[normalizedGuildKey] then
                    local sanitizedRecord = deepCopy(record)
                    sanitizedRecord.guildKey = normalizedGuildKey
                    sanitizedRecord.name = normalizedGuildKey
                    normalized.linkedGuilds[#normalized.linkedGuilds + 1] = sanitizedRecord
                    seenGuildKeys[normalizedGuildKey] = true
                end
            end
        end
    end

    if type(snapshot.permissions) == "table" then
        normalized.permissions = deepCopy(snapshot.permissions)
    end

    return normalized
end

local function mergeFederationLinkedGuilds(existing, incoming, allowRemoval)
    local mergedByGuildKey = {}
    local orderedGuildKeys = {}

    local function upsertRecord(record)
        if type(record) ~= "table" then
            return
        end

        local guildKey = record.guildKey or record.name

        if not guildKey or guildKey == "" then
            return
        end

        if not mergedByGuildKey[guildKey] then
            orderedGuildKeys[#orderedGuildKeys + 1] = guildKey
        end

        mergedByGuildKey[guildKey] = deepCopy(record)
    end

    if not allowRemoval then
        for _, record in ipairs(existing or {}) do
            upsertRecord(record)
        end
    end

    for _, record in ipairs(incoming or {}) do
        upsertRecord(record)
    end

    table.sort(orderedGuildKeys)

    local merged = {}

    for _, guildKey in ipairs(orderedGuildKeys) do
        merged[#merged + 1] = mergedByGuildKey[guildKey]
    end

    return merged
end

function addon.Reconciler:ApplyFederationSnapshot(snapshot, sender)
    local target = addon.Store:GetGuild().federation
    local normalized = self:NormalizeFederationSnapshot(snapshot)
    local localHomeGuild = addon.Store:EnsureHomeGuild()
    local senderHomeGuild = self:GetPeerHomeGuild(sender)
    local peerRecord = self:GetPeer(sender)
    local senderIsGuildMaster = peerRecord and peerRecord.isGuildMaster and true or false

    if not localHomeGuild or localHomeGuild == "" then
        return false, "local guild is unavailable"
    end

    if normalized.homeGuild == localHomeGuild then
        target.homeGuild = localHomeGuild
        target.linkedGuilds = mergeFederationLinkedGuilds(target.linkedGuilds, normalized.linkedGuilds, senderIsGuildMaster)
        target.permissions = deepCopy(normalized.permissions)

        addon.Store:GetSync().tableMeta.federation.updatedAt = time()
        refreshAfterTableUpdate("federation")

        if senderIsGuildMaster then
            self:Notify("info", "Applied federation settings from", sender .. ".")
        else
            self:Notify("info", "Merged federation settings from", sender .. " without removing existing linked guild approvals.")
        end
        return true, nil, #target.linkedGuilds
    end

    if (not senderHomeGuild or senderHomeGuild == "") and normalized.homeGuild and normalized.homeGuild ~= "" then
        senderHomeGuild = normalized.homeGuild

        if peerRecord then
            peerRecord.homeGuild = senderHomeGuild
        end
    end

    if not senderHomeGuild or normalized.homeGuild ~= senderHomeGuild then
        return false, "federation snapshot home guild did not match the sending peer"
    end

    local reciprocalRecord = nil

    for _, record in ipairs(normalized.linkedGuilds or {}) do
        if record.guildKey == localHomeGuild then
            reciprocalRecord = record
            break
        end
    end

    if not reciprocalRecord then
        return false, "remote federation snapshot did not include your guild approval"
    end

    local reciprocalRequested = reciprocalRecord.requestedAt or reciprocalRecord.approvedAt
    local reciprocalRemoteRequested = reciprocalRecord.remoteRequestedAt or reciprocalRecord.remoteApprovedAt
    local reciprocalApproved = reciprocalRecord.approvedAt
    local reciprocalRemoteApproved = reciprocalRecord.remoteApprovedAt

    if not reciprocalRequested or not reciprocalRemoteRequested or not reciprocalApproved or not reciprocalRemoteApproved then
        return false, "remote federation snapshot did not include a fully approved reciprocal link"
    end

    local mergedRecord = addon.FederationService:UpsertLinkedGuildRecord(senderHomeGuild)
    mergedRecord.guildKey = senderHomeGuild
    mergedRecord.name = senderHomeGuild
    mergedRecord.requestedAt = reciprocalRecord.remoteRequestedAt or reciprocalRecord.remoteApprovedAt or mergedRecord.requestedAt
    mergedRecord.requestedBy = reciprocalRecord.remoteRequestedBy or reciprocalRecord.remoteApprovedBy or mergedRecord.requestedBy
    mergedRecord.approvedAt = reciprocalRecord.remoteApprovedAt or mergedRecord.approvedAt
    mergedRecord.approvedBy = reciprocalRecord.remoteApprovedBy or mergedRecord.approvedBy
    mergedRecord.remoteRequestedAt = reciprocalRecord.requestedAt or reciprocalRecord.approvedAt or mergedRecord.remoteRequestedAt
    mergedRecord.remoteRequestedBy = reciprocalRecord.requestedBy or reciprocalRecord.approvedBy or mergedRecord.remoteRequestedBy
    mergedRecord.remoteApprovedAt = reciprocalRecord.approvedAt or mergedRecord.remoteApprovedAt
    mergedRecord.remoteApprovedBy = reciprocalRecord.approvedBy or mergedRecord.remoteApprovedBy

    addon.Store:GetSync().tableMeta.federation.updatedAt = time()
    refreshAfterTableUpdate("federation")

    self:Notify("info", "Merged approved federation link from", sender .. ".")
    return true, nil, 1
end

function addon.Reconciler:ApplySnapshot(tableName, snapshot, sender, options)
    if not self:IsSnapshotTable(tableName) then
        return false, "unsupported table"
    end

    if tableName == "federation" then
        return self:ApplyFederationSnapshot(snapshot, sender)
    end

    local target = getTableStore(tableName)
    local applied = 0
    local forcedIds = options and options.forceRecordIds or nil

    for id, incoming in pairs(snapshot) do
        local current = target[id]

        if type(incoming) == "table" then
            local normalizedIncoming = tableName == "roster"
                and self:NormalizeIncomingRosterRecord(sender, incoming, current)
                or (tableName == "mytoons" and sanitizeMyToonSnapshotRecord(incoming)
                    or (tableName == "mythics" and sanitizeMythicSnapshotRecord(incoming) or incoming))
            local localMeta = self:BuildRecordMeta(tableName, id, current)
            local incomingMeta = self:BuildRecordMeta(tableName, id, normalizedIncoming)
            local isForced = forcedIds and forcedIds[id]
            local action = isForced and "pull" or self:ResolveManifestAction(tableName, sender, localMeta, incomingMeta)

            if action == "pull" then
                target[id] = normalizedIncoming
                applied = applied + 1
                self:ClearConflict(tableName, id, sender)
                self:ClearResolution(tableName, id, sender)
            elseif action == "match" or action == "keep" then
                self:ClearConflict(tableName, id, sender)

                if action == "match" then
                    self:ClearResolution(tableName, id, sender)
                end
            else
                self:RecordConflict(tableName, id, sender, localMeta, incomingMeta, buildConflictReason(tableName, current, normalizedIncoming, "incoming snapshot conflicted"), buildConflictCompareText(tableName, id, sender, localMeta, incomingMeta, current, normalizedIncoming))
            end
        end
    end

    addon.Store:GetSync().tableMeta[tableName].updatedAt = time()

    if tableName == "roster" then
        if addon.MaintenanceService and addon.MaintenanceService.MarkRosterScanDirty then
            addon.MaintenanceService:MarkRosterScanDirty()
        else
            addon.MaintenanceService:ScanRoster()
        end
    elseif tableName == "mytoons" and addon.RosterService and addon.RosterService.ApplySyncedMyToonsToRoster then
        addon.RosterService:ApplySyncedMyToonsToRoster()
    elseif tableName == "mythics" and addon.MythicPlusService and addon.MythicPlusService.HandleSyncedProfiles then
        addon.MythicPlusService:HandleSyncedProfiles()
    end

    refreshAfterTableUpdate(tableName)

    self:Notify("info", "Applied", applied, tableName, "records from", sender .. ".")
    return true
end

function addon.Reconciler:ApplySnapshotAsync(tableName, snapshot, sender, options, onComplete)
    if not self:IsSnapshotTable(tableName) then
        if onComplete then
            onComplete(false, "unsupported table")
        end
        return false, "unsupported table"
    end

    if tableName == "federation" then
        local ok, message, applied = self:ApplyFederationSnapshot(snapshot, sender)

        if onComplete then
            onComplete(ok, message, applied)
        end

        return ok, message
    end

    local target = getTableStore(tableName)
    local sessionId = options and options.sessionId or nil
    local forcedIds = options and options.forceRecordIds or nil
    local ids = {}
    local applied = 0

    for id, incoming in pairs(snapshot or {}) do
        if type(incoming) == "table" then
            ids[#ids + 1] = id
        end
    end

    table.sort(ids)

    local index = 1

    local function finish(ok, message)
        if ok then
            addon.Store:GetSync().tableMeta[tableName].updatedAt = time()

            if tableName == "roster" then
                if addon.MaintenanceService and addon.MaintenanceService.MarkRosterScanDirty then
                    addon.MaintenanceService:MarkRosterScanDirty()
                else
                    addon.MaintenanceService:ScanRoster()
                end
            elseif tableName == "mytoons" and addon.RosterService and addon.RosterService.ApplySyncedMyToonsToRoster then
                addon.RosterService:ApplySyncedMyToonsToRoster()
            elseif tableName == "mythics" and addon.MythicPlusService and addon.MythicPlusService.HandleSyncedProfiles then
                addon.MythicPlusService:HandleSyncedProfiles()
            end

            refreshAfterTableUpdate(tableName)

            addon.Reconciler:Notify("info", "Applied", applied, tableName, "records from", sender .. ".")
        end

        if onComplete then
            onComplete(ok, message, applied)
        end
    end

    local function processBatch()
        local session = sessionId and addon.SessionManager:GetSession(sessionId) or nil

        if session and session.status == "cancelled" then
            finish(false, "sync cancelled")
            return
        end

        local processed = 0

        while index <= #ids and processed < APPLY_BATCH_SIZE do
            local id = ids[index]
            local incoming = snapshot[id]
            local current = target[id]
            local normalizedIncoming = tableName == "roster"
                and self:NormalizeIncomingRosterRecord(sender, incoming, current)
                or (tableName == "mytoons" and sanitizeMyToonSnapshotRecord(incoming)
                    or (tableName == "mythics" and sanitizeMythicSnapshotRecord(incoming) or incoming))
            local localMeta = self:BuildRecordMeta(tableName, id, current)
            local incomingMeta = self:BuildRecordMeta(tableName, id, normalizedIncoming)
            local isForced = forcedIds and forcedIds[id]
            local action = isForced and "pull" or self:ResolveManifestAction(tableName, sender, localMeta, incomingMeta)

            if action == "pull" then
                target[id] = normalizedIncoming
                applied = applied + 1
                self:ClearConflict(tableName, id, sender)
                self:ClearResolution(tableName, id, sender)
            elseif action == "match" or action == "keep" then
                self:ClearConflict(tableName, id, sender)

                if action == "match" then
                    self:ClearResolution(tableName, id, sender)
                end
            else
                self:RecordConflict(tableName, id, sender, localMeta, incomingMeta, buildConflictReason(tableName, current, normalizedIncoming, "incoming snapshot conflicted"), buildConflictCompareText(tableName, id, sender, localMeta, incomingMeta, current, normalizedIncoming))
            end

            index = index + 1
            processed = processed + 1
        end

        if index <= #ids then
            C_Timer.After(APPLY_BATCH_DELAY_SECONDS, processBatch)
            return
        end

        finish(true)
    end

    if #ids == 0 then
        finish(true)
        return true
    end

    C_Timer.After(0, processBatch)
    return true
end

function addon.Reconciler:GetOverlappingSyncError(peer, tableName)
    local activeSession = addon.SessionManager:FindActiveSession(peer, tableName)

    if activeSession then
        return string.format("sync already active for %s with %s", activeSession.tableName or tableName or "unknown", peer or "unknown")
    end

    return nil
end

function addon.Reconciler:RequestRemoteRecords(peer, tableName, ids)
    if not peer or peer == "" then
        return false, "peer missing for conflict"
    end

    if not self:IsSnapshotTable(tableName) then
        return false, "unsupported table"
    end

    local canSync, permissionError = self:CanSyncPeerTable(peer, tableName)

    if not canSync then
        return false, permissionError
    end

    local requestIds = {}
    local forcedIds = {}

    for _, id in ipairs(ids or {}) do
        if id ~= nil and forcedIds[id] == nil then
            requestIds[#requestIds + 1] = id
            forcedIds[id] = true
        end
    end

    if #requestIds == 0 then
        return false, "no conflict records selected"
    end

    local overlapError = self:GetOverlappingSyncError(peer, tableName)
    if overlapError then
        return false, overlapError
    end

    local sessionId = addon.SessionManager:StartSession(peer, tableName, 0, {
        direction = "outgoing",
        forceRecordIds = forcedIds,
        resolution = #requestIds == 1 and "accept-remote" or "accept-remote-bulk",
        recordId = #requestIds == 1 and requestIds[1] or nil,
        recordIds = requestIds,
    })

    local requestPayload = addon.Json:Encode(requestIds)
    local ok, errorMessage = addon.Transport:SendRecordRequest(peer, tableName, sessionId, requestPayload)

    if not ok then
        addon.SessionManager:FailSession(sessionId, errorMessage)
        return false, errorMessage
    end

    return true, sessionId
end

function addon.Reconciler:BuildSyncDiagnostics()
    return {
        sessions = addon.SessionManager:BuildDiagnostics(),
        transport = addon.Transport:BuildDiagnostics(),
        peerCount = #self:GetPeerList(),
        conflictCount = #self:ListConflicts(),
        manualOnly = self:IsManualOnlySync(),
    }
end

local function formatReportBytes(byteCount)
    local bytes = math.max(0, tonumber(byteCount) or 0)

    if bytes >= (1024 * 1024) then
        return string.format("%.2f MB", bytes / (1024 * 1024))
    end

    if bytes >= 1024 then
        return string.format("%.1f KB", bytes / 1024)
    end

    return string.format("%d B", bytes)
end

function addon.Reconciler:RemovePeer(peer)
    local normalizedPeer = addon.Store:NormalizeFullName(peer, GetRealmName() or "Unknown")

    if normalizedPeer == "" then
        return false, "no peer selected"
    end

    local peers = addon.Store:GetSync().peers

    if not peers[normalizedPeer] then
        return false, "peer not found"
    end

    peers[normalizedPeer] = nil
    self:ClearConflicts({ sender = normalizedPeer })
    return true
end

function addon.Reconciler:BuildLastRosterSyncReport()
    local summary = addon.Store:GetSync().lastRosterSync

    if type(summary) ~= "table" or (summary.updatedAt or 0) <= 0 then
        return "No roster sync summary captured yet. Run a roster sync first, then open this report again."
    end

    local lines = {
        "GH3 Last Roster Sync",
        string.format("Peer: %s", summary.peer or "Unknown"),
        string.format("Status: %s", summary.lastStatus or "unknown"),
        string.format("Started: %s", (summary.startedAt or 0) > 0 and date("%Y-%m-%d %H:%M:%S", summary.startedAt) or "Unknown"),
        string.format("Completed: %s", (summary.completedAt or 0) > 0 and date("%Y-%m-%d %H:%M:%S", summary.completedAt) or "Not completed"),
        string.format("Updated: %s", (summary.updatedAt or 0) > 0 and date("%Y-%m-%d %H:%M:%S", summary.updatedAt) or "Unknown"),
        "",
        string.format(
            "Sessions: completed %d | failed %d | cancelled %d | pull %d | push %d",
            summary.completedSessions or 0,
            summary.failedSessions or 0,
            summary.cancelledSessions or 0,
            summary.pullSessions or 0,
            summary.pushSessions or 0
        ),
        string.format(
            "Manifest: sent %d chunks (%s) | received %d chunks (%s)",
            summary.manifestChunksSent or 0,
            formatReportBytes(summary.manifestBytesSent),
            summary.manifestChunksReceived or 0,
            formatReportBytes(summary.manifestBytesReceived)
        ),
        string.format(
            "Record requests: sent %d chunks (%s) | received %d chunks (%s)",
            summary.recordRequestChunksSent or 0,
            formatReportBytes(summary.recordRequestBytesSent),
            summary.recordRequestChunksReceived or 0,
            formatReportBytes(summary.recordRequestBytesReceived)
        ),
        string.format(
            "Record responses: sent %d chunks (%s) | received %d chunks (%s)",
            summary.recordResponseChunksSent or 0,
            formatReportBytes(summary.recordResponseBytesSent),
            summary.recordResponseChunksReceived or 0,
            formatReportBytes(summary.recordResponseBytesReceived)
        ),
        string.format(
            "Snapshots: sent %d chunks (%s) | received %d chunks (%s)",
            summary.snapshotChunksSent or 0,
            formatReportBytes(summary.snapshotBytesSent),
            summary.snapshotChunksReceived or 0,
            formatReportBytes(summary.snapshotBytesReceived)
        ),
        string.format("Records requested from peer: %d", summary.recordsRequestedFromPeer or 0),
        string.format("Records requested by peer: %d", summary.recordsRequestedByPeer or 0),
    }

    if summary.lastError and summary.lastError ~= "" then
        lines[#lines + 1] = string.format("Reason: %s", summary.lastError)
    end

    if (time() - (summary.updatedAt or 0)) > (24 * 3600) then
        lines[#lines + 1] = "Note: this roster sync summary is stale and may describe an older session rather than current sync activity."
    end

    if type(summary.notes) == "table" and #summary.notes > 0 then
        lines[#lines + 1] = ""
        lines[#lines + 1] = "Recent sessions:"

        for _, note in ipairs(summary.notes) do
            lines[#lines + 1] = "- " .. tostring(note)
        end
    end

    return table.concat(lines, "\n")
end

function addon.Reconciler:CancelActiveSyncs(reason)
    local cancelled = addon.Transport:CancelAllSessions(reason)

    if addon.MainFrame then
        if addon.IsSyncSuspended and addon:IsSyncSuspended() and addon.MainFrame.MarkBackgroundRefreshPending then
            addon.MainFrame:MarkBackgroundRefreshPending()
        elseif addon.MainFrame.RefreshCurrentView then
            addon.MainFrame:RefreshCurrentView()
        end
    end

    return cancelled
end

function addon.Reconciler:RequestSnapshot(peer, tableName)
    if not peer or peer == "" then
        return false, "no peer selected"
    end

    if not self:IsSnapshotTable(tableName) then
        return false, "unsupported table"
    end

    local canSync, permissionError = self:CanSyncPeerTable(peer, tableName)

    if not canSync then
        return false, permissionError
    end

    local overlapError = self:GetOverlappingSyncError(peer, tableName)
    if overlapError then
        return false, overlapError
    end

    local sessionId = addon.SessionManager:StartSession(peer, tableName, 0, {
        direction = "outgoing",
        resolution = "snapshot-pull",
    })
    local ok, errorMessage = addon.Transport:Send(peer, "WHISPER", {
        op = "SNAPSHOT_REQ",
        sessionId = sessionId,
        tableName = tableName,
        payload = "",
    })

    if not ok then
        addon.SessionManager:FailSession(sessionId, errorMessage)
        return false, errorMessage
    end

    return true, sessionId
end

function addon.Reconciler:PushSnapshot(peer, tableName)
    if not peer or peer == "" then
        return false, "no peer selected"
    end

    if not self:IsSnapshotTable(tableName) then
        return false, "unsupported table"
    end

    local canSync, permissionError = self:CanSyncPeerTable(peer, tableName)

    if not canSync then
        return false, permissionError
    end

    local payload, errorMessage = self:SerializeManifest(tableName)

    if not payload then
        return false, errorMessage
    end

    local sessionId = addon.SessionManager:StartSession(peer, tableName, 0, {
        direction = "outgoing",
        resolution = "push-delta",
    })
    local ok, pushSessionOrError = addon.Transport:SendManifestPush(peer, tableName, sessionId, payload)

    if not ok then
        addon.SessionManager:FailSession(sessionId, pushSessionOrError)
        return false, pushSessionOrError
    end

    return true, pushSessionOrError
end

function addon.Reconciler:RequestBidirectionalSnapshot(peer, tableName)
    local peerRecord = self:GetPeer(peer)
    local remoteStatus = peerRecord and peerRecord.tables and peerRecord.tables[tableName] or nil
    local localStatus = self:BuildTableStatus(tableName)

    if doTableStatusesMatch(remoteStatus, localStatus) then
        return true, string.format("%s already matched cached peer status.", tableName or "table"), false
    end

    local ok, sessionIdOrError = self:RequestDeltaSync(peer, tableName)

    if not ok then
        return false, sessionIdOrError
    end

    if tableName == "roster" then
        return true, sessionIdOrError, true
    end

    local pushOk, pushError = self:PushSnapshot(peer, tableName)

    if not pushOk then
        local fallbackOk, fallbackError = addon.Transport:Send(peer, "WHISPER", {
            op = "SYNC_BACK_REQ",
            sessionId = "",
            tableName = tableName,
            payload = "",
        })

        if not fallbackOk then
            return true, string.format("Started %s delta pull. Direct push failed: %s. Peer sync-back fallback failed: %s", tableName or "table", pushError or "unknown error", fallbackError or "unknown error"), true
        end

        return true, string.format("Started %s delta pull. Direct push failed, so peer sync-back fallback was requested.", tableName or "table"), true
    end

    return true, sessionIdOrError, true
end

function addon.Reconciler:RequestAllSnapshots(peer)
    if not peer or peer == "" then
        return false, "no peer selected"
    end

    local startedTables = {}
    local failedTables = {}

    for _, tableName in ipairs(self:GetSnapshotRequestOrder()) do
        local ok, message = self:RequestSnapshot(peer, tableName)

        if ok then
            startedTables[#startedTables + 1] = tableName
        else
            failedTables[#failedTables + 1] = string.format("%s (%s)", tableName, message or "failed")
        end
    end

    if #startedTables == 0 then
        return false, table.concat(failedTables, "; ")
    end

    if #failedTables > 0 then
        return true, string.format("Started %s. Skipped %s.", table.concat(startedTables, ", "), table.concat(failedTables, "; "))
    end

    return true, string.format("Started %s.", table.concat(startedTables, ", "))
end

function addon.Reconciler:RequestAllBidirectionalSnapshots(peer)
    if not peer or peer == "" then
        return false, "no peer selected"
    end

    local startedTables = {}
    local matchedTables = {}
    local failedTables = {}

    for _, tableName in ipairs(self:GetSnapshotRequestOrder()) do
        local ok, message, startedSync = self:RequestBidirectionalSnapshot(peer, tableName)

        if ok then
            if startedSync == false then
                matchedTables[#matchedTables + 1] = tableName
            else
                startedTables[#startedTables + 1] = tableName
            end

            if startedSync ~= false and type(message) == "string" and message:find("Peer sync%-back request failed", 1, false) then
                failedTables[#failedTables + 1] = string.format("%s (%s)", tableName, message)
            end
        else
            failedTables[#failedTables + 1] = string.format("%s (%s)", tableName, message or "failed")
        end
    end

    if #startedTables == 0 and #matchedTables == 0 then
        return false, table.concat(failedTables, "; ")
    end

    if #startedTables == 0 and #failedTables == 0 then
        return true, string.format("Already matched %s.", table.concat(matchedTables, ", "))
    end

    local parts = {}
    appendSyncSummaryLine(parts, "Started", startedTables)
    appendSyncSummaryLine(parts, "Already matched", matchedTables)

    if #failedTables > 0 then
        parts[#parts + 1] = string.format("Issues: %s", table.concat(failedTables, "; "))
    end

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

function addon.Reconciler:RequestSnapshotSync(peer, tableName)
    return self:RequestSnapshot(peer, tableName)
end

function addon.Reconciler:RequestDeltaSync(peer, tableName)
    if not peer or peer == "" then
        return false, "no peer selected"
    end

    if not self:IsSnapshotTable(tableName) then
        return false, "unsupported table"
    end

    local overlapError = self:GetOverlappingSyncError(peer, tableName)
    if overlapError then
        return false, overlapError
    end

    local canSync, permissionError = self:CanSyncPeerTable(peer, tableName)

    if not canSync then
        return false, permissionError
    end

    if tableName == "roster" then
        local peerHomeGuild = self:GetPeerHomeGuild(peer)

        if not peerHomeGuild or peerHomeGuild == "" then
            return false, "peer home guild is unavailable"
        end

        if not self:HasPeerCapability(peer, "rosterJournal") then
            local sessionId = addon.SessionManager:StartSession(peer, tableName, 0, {
                direction = "outgoing",
                resolution = "delta-pull",
            })
            local ok, errorMessage = addon.Transport:Send(peer, "WHISPER", {
                op = "MANIFEST_REQ",
                sessionId = sessionId,
                tableName = tableName,
                payload = "",
            })

            if not ok then
                addon.SessionManager:FailSession(sessionId, errorMessage)
                return false, errorMessage
            end

            return true, sessionId
        end

        local sessionId = addon.SessionManager:StartSession(peer, tableName, 0, {
            direction = "outgoing",
            resolution = "roster-journal-pull",
        })
        local payload = encodeRosterJournalRequest({
            guildKey = peerHomeGuild,
            afterSequence = self:GetRosterJournalWatermark(peerHomeGuild),
            localStatus = self:BuildRosterGuildStatus(peerHomeGuild),
        })
        local ok, errorMessage = addon.Transport:Send(peer, "WHISPER", {
            op = "ROSTER_JOURNAL_REQ",
            sessionId = sessionId,
            tableName = tableName,
            payload = payload,
        })

        if not ok then
            addon.SessionManager:FailSession(sessionId, errorMessage)
            return false, errorMessage
        end

        return true, sessionId
    end

    local sessionId = addon.SessionManager:StartSession(peer, tableName, 0, {
        direction = "outgoing",
        resolution = "delta-pull",
    })
    local ok, errorMessage = addon.Transport:Send(peer, "WHISPER", {
        op = "MANIFEST_REQ",
        sessionId = sessionId,
        tableName = tableName,
        payload = "",
    })

    if not ok then
        addon.SessionManager:FailSession(sessionId, errorMessage)
        return false, errorMessage
    end

    return true, sessionId
end

function addon.Reconciler:ResolveConflictKeepLocal(tableName, id, sender)
    local conflict = self:GetConflict(tableName, id, sender)

    if not conflict then
        return false, "conflict not found"
    end

    self:StoreResolution(tableName, id, sender, {
        signature = conflict.localSignature,
    }, {
        signature = conflict.remoteSignature,
    }, "keep-local")
    self:ClearConflict(tableName, id, sender)

    if addon.MainFrame and addon.MainFrame.RefreshCurrentView then
        addon.MainFrame:RefreshCurrentView()
    end

    return true, "kept local record"
end

function addon.Reconciler:ResolveAllConflictsKeepLocal(filter)
    local resolved = 0

    for _, conflict in ipairs(self:ListConflicts()) do
        local matchesTable = not filter or not filter.tableName or conflict.tableName == filter.tableName
        local matchesSender = not filter or not filter.sender or conflict.sender == filter.sender
        local matchesRecord = not filter or not filter.recordId or conflict.recordId == filter.recordId

        if matchesTable and matchesSender and matchesRecord then
            self:StoreResolution(conflict.tableName, conflict.recordId, conflict.sender, {
                signature = conflict.localSignature,
            }, {
                signature = conflict.remoteSignature,
            }, "keep-local")
            self:ClearConflict(conflict.tableName, conflict.recordId, conflict.sender)
            resolved = resolved + 1
        end
    end

    return true, resolved
end

function addon.Reconciler:ResolveConflictAcceptRemote(tableName, id, sender)
    local conflict = self:GetConflict(tableName, id, sender)

    if not conflict then
        return false, "conflict not found"
    end

    if not sender or sender == "" then
        return false, "peer missing for conflict"
    end

    return self:RequestRemoteRecords(sender, tableName, { id })
end

function addon.Reconciler:ResolveAllConflictsAcceptRemote(filter)
    local grouped = {}
    local orderedGroups = {}
    local totalRecords = 0

    for _, conflict in ipairs(self:ListConflicts()) do
        local matchesTable = not filter or not filter.tableName or conflict.tableName == filter.tableName
        local matchesSender = not filter or not filter.sender or conflict.sender == filter.sender
        local matchesRecord = not filter or not filter.recordId or conflict.recordId == filter.recordId

        if matchesTable and matchesSender and matchesRecord then
            local groupKey = table.concat({ conflict.sender or "unknown", conflict.tableName or "na" }, "::")
            local group = grouped[groupKey]

            if not group then
                group = {
                    sender = conflict.sender,
                    tableName = conflict.tableName,
                    ids = {},
                    seen = {},
                }
                grouped[groupKey] = group
                orderedGroups[#orderedGroups + 1] = group
            end

            if not group.seen[conflict.recordId] then
                group.seen[conflict.recordId] = true
                group.ids[#group.ids + 1] = conflict.recordId
                totalRecords = totalRecords + 1
            end
        end
    end

    if #orderedGroups == 0 then
        return false, "no conflicts found"
    end

    local started = 0
    local firstError = nil

    for _, group in ipairs(orderedGroups) do
        local ok, message = self:RequestRemoteRecords(group.sender, group.tableName, group.ids)

        if ok then
            started = started + 1
        elseif not firstError then
            firstError = message
        end
    end

    if started == 0 then
        return false, firstError or "failed to request remote conflicts"
    end

    return true, string.format("requested %d conflict records across %d sync sessions", totalRecords, started)
end
