"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SummarizerClientElection = void 0;
const client_utils_1 = require("@fluid-internal/client-utils");
const internal_1 = require("@fluidframework/driver-definitions/internal");
const summarizerTypes_js_1 = require("./summarizerTypes.js");
/**
 * This class encapsulates logic around tracking the elected summarizer client.
 * It will handle updating the elected client when a summary ack hasn't been seen
 * for some configured number of ops.
 */
class SummarizerClientElection extends client_utils_1.TypedEventEmitter {
    get electedClientId() {
        return this.clientElection.electedClient?.clientId;
    }
    get electedParentId() {
        return this.clientElection.electedParent?.clientId;
    }
    constructor(logger, summaryCollection, clientElection, maxOpsSinceLastSummary) {
        super();
        this.logger = logger;
        this.summaryCollection = summaryCollection;
        this.clientElection = clientElection;
        this.maxOpsSinceLastSummary = maxOpsSinceLastSummary;
        /**
         * Used to prevent excess logging by recording the sequence number that we last reported at,
         * and making sure we don't report another event to telemetry. If things work as intended,
         * this is not needed, otherwise it could report an event on every op in worst case scenario.
         */
        this.lastReportedSeq = 0;
        // On every inbound op, if enough ops pass without seeing a summary ack (per elected client),
        // elect a new client and log to telemetry.
        this.summaryCollection.on("default", ({ sequenceNumber }) => {
            const electedClientId = this.electedClientId;
            if (electedClientId === undefined) {
                // Reset election if no elected client, but eligible clients are connected.
                // This should be uncommon, but is possible if the initial state of the
                // ordered client election contains an undefined client id or one not found
                // in the quorum (the latter would already log an error).
                if (this.clientElection.eligibleCount > 0) {
                    this.clientElection.resetElectedClient(sequenceNumber);
                }
                return;
            }
            const electionSequenceNumber = this.clientElection.electionSequenceNumber;
            const opsWithoutSummary = sequenceNumber - (this.lastSummaryAckSeqForClient ?? electionSequenceNumber);
            if (opsWithoutSummary > this.maxOpsSinceLastSummary) {
                // Log and elect a new summarizer client.
                const opsSinceLastReport = sequenceNumber - this.lastReportedSeq;
                if (opsSinceLastReport > this.maxOpsSinceLastSummary) {
                    this.logger.sendTelemetryEvent({
                        eventName: "ElectedClientNotSummarizing",
                        electedClientId,
                        lastSummaryAckSeqForClient: this.lastSummaryAckSeqForClient,
                        electionSequenceNumber,
                        nextElectedClientId: this.clientElection.peekNextElectedClient()?.clientId,
                        opsWithoutSummary,
                    });
                    this.lastReportedSeq = sequenceNumber;
                }
            }
        });
        // When a summary ack comes in, reset our op seq counter.
        this.summaryCollection.on(internal_1.MessageType.SummaryAck, (op) => {
            this.lastSummaryAckSeqForClient = op.sequenceNumber;
        });
        // Use oldest client election for unanimously and deterministically deciding
        // which client should summarize.
        this.clientElection.on("election", (client, sequenceNumber) => {
            this.lastSummaryAckSeqForClient = undefined;
            if (client === undefined && this.clientElection.eligibleCount > 0) {
                // If no client is valid for election, reset to the oldest again.
                // Also make extra sure not to get stuck in an infinite loop here:
                // If there are no eligible clients, just wait until a client joins
                // and will be auto-elected.
                this.clientElection.resetElectedClient(sequenceNumber);
            }
            // Election can trigger a change in SummaryManager state.
            this.emit("electedSummarizerChanged");
        });
    }
    serialize() {
        const { electedClientId, electedParentId, electionSequenceNumber } = this.clientElection.serialize();
        return {
            electedClientId,
            electedParentId,
            electionSequenceNumber: this.lastSummaryAckSeqForClient ?? electionSequenceNumber,
        };
    }
    static isClientEligible(client) {
        const details = client.client.details;
        if (details === undefined) {
            // Very old clients back-compat
            return true;
        }
        return SummarizerClientElection.clientDetailsPermitElection(details);
    }
}
exports.SummarizerClientElection = SummarizerClientElection;
SummarizerClientElection.clientDetailsPermitElection = (details) => details.capabilities.interactive || details.type === summarizerTypes_js_1.summarizerClientType;
//# sourceMappingURL=summarizerClientElection.js.map