import { addTracingToMethods, ConflictError, NotFoundError, TracedEventEmitter, } from "@superblocksteam/shared";
import { unwrapResponseDto } from "@superblocksteam/shared";
import { applyErrorHandling, } from "../errors/error-handler.js";
import { getErrorMeta } from "../util/logger.js";
import { ActivityTracker } from "./activity-tracker.js";
const LOCK_HEARTBEAT_INTERVAL_MS = 30 * 1000; // 30 seconds
const LOCK_BUFFER_MS = 30 * 1000; // 30 seconds
const STARTUP_CONNECTION_TIMEOUT_MS = 90 * 1000; // 90 seconds
export var LockServiceStatus;
(function (LockServiceStatus) {
    /** The lock service is not currently active */
    LockServiceStatus["IDLE"] = "IDLE";
    /** The lock service is acquiring a lock */
    LockServiceStatus["ACQUIRING"] = "ACQUIRING";
    /** The lock service has acquired a lock */
    LockServiceStatus["LOCKED"] = "LOCKED";
    /** The lock service is releasing a lock */
    LockServiceStatus["RELEASING"] = "RELEASING";
    /** The lock service is waiting for a lock to be released */
    LockServiceStatus["FAILED_TO_ACQUIRE_LOCK"] = "FAILED_TO_ACQUIRE_LOCK";
    /** The lock is invalid */
    LockServiceStatus["LOCK_INVALID"] = "LOCK_INVALID";
    /** The lock is released */
    LockServiceStatus["LOCK_RELEASED"] = "LOCK_RELEASED";
})(LockServiceStatus || (LockServiceStatus = {}));
export var LockType;
(function (LockType) {
    LockType["LOCAL"] = "local";
    LockType["CSB"] = "csb";
})(LockType || (LockType = {}));
export class LockService extends TracedEventEmitter {
    applicationId;
    branchName;
    lockType;
    heartbeatIntervalMs;
    syncCallback;
    waitForGenerationCompleteCallback;
    _status = LockServiceStatus.IDLE;
    _lockId;
    _heartbeatTimeout;
    _inactivityTimeout;
    _bufferPeriodTimeout;
    _isRestarting = false;
    _tracer;
    _logger;
    _rpcClient;
    _inactivityThresholdMs;
    _lockBufferMs;
    _startupConnectionTimeoutMs;
    _startupConnectionTimeout;
    _startupConnectionTimeoutStartedAt;
    _startupConnectionTimeoutExpired = false;
    _activityTracker;
    constructor(config) {
        super(config.tracer);
        this.applicationId = config.applicationId;
        this.branchName = config.branchName;
        this.lockType = config.lockType;
        this._inactivityThresholdMs = config.inactivityThresholdMs;
        this.heartbeatIntervalMs =
            config.heartbeatIntervalMs || LOCK_HEARTBEAT_INTERVAL_MS;
        this._lockBufferMs = config.lockBufferMs || LOCK_BUFFER_MS;
        this._startupConnectionTimeoutMs =
            config.startupConnectionTimeoutMs ?? STARTUP_CONNECTION_TIMEOUT_MS;
        this._rpcClient = config.rpcClient;
        this._tracer = config.tracer;
        this._logger = config.logger;
        // Initialize activity tracker
        this._activityTracker = new ActivityTracker({
            logger: config.logger,
            inactivityThresholdMs: this._inactivityThresholdMs,
        });
        applyErrorHandling(this, {
            acquireLock: { operation: "acquiring lock" },
            releaseLock: { operation: "releasing lock" },
        });
        const methods = [
            "acquireLock",
            "releaseLock",
            "checkAndUpdateLockIfPossible",
            "shutdown",
            "shutdownAndExit",
            "restartInactivityTimer",
        ];
        addTracingToMethods(this, methods, this._tracer);
        void this.restartInactivityTimer();
    }
    getLogger() {
        return this._logger;
    }
    setSyncCallback(callback) {
        this.syncCallback = callback;
    }
    setWaitForGenerationCompleteCallback(callback) {
        this.waitForGenerationCompleteCallback = callback;
    }
    get status() {
        return this._status;
    }
    set status(status) {
        if (this._status !== status) {
            this._status = status;
            this.emit("statusChange", status);
        }
    }
    get isLocked() {
        return this._status === LockServiceStatus.LOCKED;
    }
    get lockId() {
        return this._lockId;
    }
    get currentBranchName() {
        return this.branchName;
    }
    get connectedUsers() {
        return this._activityTracker.connectedUsers;
    }
    get isRestarting() {
        return this._isRestarting;
    }
    get wasRecentlyActive() {
        return this._activityTracker.wasRecentlyActive;
    }
    get timeSinceLastActivity() {
        return this._activityTracker.timeSinceLastActivity;
    }
    /**
     * Check if the lock service is in a state where it can reacquire a lock.
     * This happens when the lock was released but dev server is still alive.
     */
    get canReacquireLock() {
        return (this._status === LockServiceStatus.LOCK_RELEASED ||
            this._status === LockServiceStatus.IDLE);
    }
    async switchBranch(nextBranchName, options) {
        if (this.branchName === nextBranchName) {
            return;
        }
        const previousBranchName = this.branchName;
        const lockIdBeforeSwitch = this._lockId;
        if (options?.reacquireLock && this.isLocked && lockIdBeforeSwitch) {
            this.stopHeartbeat();
            try {
                const lockResponse = await this._rpcClient.call(async (client) => {
                    return unwrapResponseDto(client.call.v3.lock.transfer({
                        applicationId: this.applicationId,
                        fromBranchName: previousBranchName,
                        toBranchName: nextBranchName,
                        lockId: lockIdBeforeSwitch,
                    }));
                });
                const transferredLock = lockResponse.appBranchLock[0];
                if (!transferredLock) {
                    throw new Error(`[lock-service] lock transfer returned no lock for branch switch '${previousBranchName}' -> '${nextBranchName}'`);
                }
                this.branchName = nextBranchName;
                this._lockId = transferredLock.lockId;
                this.status = LockServiceStatus.LOCKED;
                this.getLogger().info(`[lock-service] atomically transferred lock branch context from '${previousBranchName}' to '${nextBranchName}'`);
                void this.scheduleNextHeartbeat();
                return;
            }
            catch (error) {
                // Preserve lock liveness when transfer fails: we still hold the original
                // lock and must resume heartbeat to avoid silent lock expiration.
                if (this.isLocked && this._lockId === lockIdBeforeSwitch) {
                    this.getLogger().warn(`[lock-service] lock transfer failed, resuming heartbeat on original lock '${lockIdBeforeSwitch}'`);
                    void this.scheduleNextHeartbeat();
                }
                throw error;
            }
        }
        if (this.isLocked) {
            await this.releaseLock();
        }
        else {
            this.stopHeartbeat();
            this._lockId = undefined;
            this.status = LockServiceStatus.IDLE;
        }
        this.branchName = nextBranchName;
        this.getLogger().info(`[lock-service] switched branch context from '${previousBranchName}' to '${nextBranchName}'`);
        if (options?.reacquireLock) {
            await this.acquireLock();
        }
    }
    /**
     * Acquire a lock on the specified application and branch
     */
    async acquireLock() {
        this.startStartupConnectionTimeoutIfNeeded();
        this.status = LockServiceStatus.ACQUIRING;
        // Stop any existing heartbeat before acquiring a new lock to prevent duplicate heartbeat loops
        this.stopHeartbeat();
        try {
            const lockResponse = await this._rpcClient.call(async (client) => {
                return unwrapResponseDto(client.call.v3.lock.acquire({
                    applicationId: this.applicationId,
                    branchName: this.branchName,
                    sessionType: this.lockType,
                }));
            });
            // Currently lock response only returns one lock
            if (lockResponse.appBranchLock[0]) {
                const lock = lockResponse.appBranchLock[0];
                this._lockId = lock.lockId;
                this.getLogger().info(`[lock-service] lock acquired: ${this._lockId}`);
                this.status = LockServiceStatus.LOCKED;
                this.emit("lockAcquired", this._lockId);
                this.evaluateStartupConnectionTimeoutIfNeeded();
                void this.scheduleNextHeartbeat();
                return;
            }
        }
        catch (error) {
            if (error instanceof ConflictError) {
                this.getLogger().error(`[lock-service] ${error.message}`);
                this.emit("statusChange", LockServiceStatus.FAILED_TO_ACQUIRE_LOCK);
                this.status = LockServiceStatus.FAILED_TO_ACQUIRE_LOCK;
                await this.shutdown({ serverInitiated: false });
                throw error;
            }
            else {
                throw new Error(`${error}. Please try again later.`);
            }
        }
    }
    /**
     * Release the currently held lock
     */
    async releaseLock() {
        if (this._status !== LockServiceStatus.LOCKED) {
            return false;
        }
        this.status = LockServiceStatus.RELEASING;
        this.emit("statusChange", LockServiceStatus.RELEASING);
        // Stop heartbeat immediately and clear local lock id first to prevent any
        // in-flight grab calls from using a stale lock id during branch switching.
        this.stopHeartbeat();
        const lockIdToRelease = this._lockId;
        this._lockId = undefined;
        try {
            if (!lockIdToRelease) {
                this.status = LockServiceStatus.LOCK_RELEASED;
                this.emit("statusChange", LockServiceStatus.LOCK_RELEASED);
                return true;
            }
            // Try to release the lock when this class is destructed
            await this._rpcClient.call(async (client) => {
                await unwrapResponseDto(client.call.v3.lock.release({
                    applicationId: this.applicationId,
                    branchName: this.branchName,
                    lockId: lockIdToRelease,
                }));
            });
            this.status = LockServiceStatus.LOCK_RELEASED;
            this.emit("statusChange", LockServiceStatus.LOCK_RELEASED);
            this.getLogger().info("[lock-service] lock released");
            return true;
        }
        catch (error) {
            if (error instanceof NotFoundError) {
                this.status = LockServiceStatus.LOCK_RELEASED;
                this.emit("statusChange", LockServiceStatus.LOCK_RELEASED);
                this.getLogger().warn("[lock-service] lock already released on server");
                return true;
            }
            this.status = LockServiceStatus.IDLE;
            this.getLogger().error("[lock-service] error releasing lock", getErrorMeta(error));
            throw error;
        }
        finally {
            this.stopHeartbeat();
            // Clear any pending buffer or inactivity timeouts since lock is being released
            if (this._bufferPeriodTimeout) {
                clearTimeout(this._bufferPeriodTimeout);
                this._bufferPeriodTimeout = undefined;
            }
            if (this._inactivityTimeout) {
                clearTimeout(this._inactivityTimeout);
                this._inactivityTimeout = undefined;
            }
            this.clearStartupConnectionTimeout();
        }
    }
    async grabLockImpl() {
        if (this._lockId) {
            const lockResponse = await this._rpcClient.call(async (client) => {
                return unwrapResponseDto(client.call.v3.lock.grab({
                    applicationId: this.applicationId,
                    branchName: this.branchName,
                    lockId: this._lockId,
                }));
            });
            return lockResponse;
        }
        return null;
    }
    /**
     * Inspect lock information for the current application and branch
     */
    async inspectLock() {
        const lockInfo = await this._rpcClient.call(async (client) => {
            return unwrapResponseDto(client.call.v3.lock.inspect({
                applicationId: this.applicationId,
                branchName: this.branchName,
            }));
        });
        return lockInfo;
    }
    /**
     * Force takeover of the lock from another user (only allowed when user is inactive)
     */
    async forceTakeover() {
        this.startStartupConnectionTimeoutIfNeeded();
        this.status = LockServiceStatus.ACQUIRING;
        try {
            const lockResponse = await this._rpcClient.call(async (client) => {
                return unwrapResponseDto(client.call.v3.lock.forceTake({
                    applicationId: this.applicationId,
                    branchName: this.branchName,
                    sessionType: this.lockType,
                }));
            });
            // Currently lock response only returns one lock
            if (lockResponse.appBranchLock[0]) {
                const lock = lockResponse.appBranchLock[0];
                this._lockId = lock.lockId;
                this.getLogger().info(`[lock-service] lock acquired via force takeover: ${this._lockId}`);
                this.status = LockServiceStatus.LOCKED;
                this.emit("lockAcquired", this._lockId);
                this.evaluateStartupConnectionTimeoutIfNeeded();
                void this.scheduleNextHeartbeat();
                return;
            }
        }
        catch (error) {
            this.getLogger().error(`[lock-service] ${getErrorMeta(error).error.message}`);
            this.status = LockServiceStatus.FAILED_TO_ACQUIRE_LOCK;
            throw error;
        }
    }
    async checkAndUpdateLockIfPossible() {
        const retryTimeoutMs = 1000;
        const maxAttempts = 3;
        // Retry up to maxAttempts times to update the lock
        for (let attempt = 1; attempt <= maxAttempts; attempt++) {
            try {
                const response = await this.grabLockImpl();
                if (response?.appBranchLock?.[0]) {
                    const returnedLock = response.appBranchLock[0];
                    if (returnedLock.sessionType &&
                        returnedLock.sessionType !== this.lockType) {
                        this.getLogger().warn(`[lock-service] lock fallback returned mismatched session type (${returnedLock.sessionType}) for expected ${this.lockType}; ignoring fallback lock`);
                        throw new NotFoundError("Lock not found");
                    }
                    // The server may return a different lock after a cross-branch
                    // session transfer. Adopt the new lock ID so future heartbeats
                    // reference the correct row.
                    if (returnedLock.lockId && returnedLock.lockId !== this._lockId) {
                        this.getLogger().info(`[lock-service] lock ID changed from ${this._lockId} to ${returnedLock.lockId} (cross-branch transfer)`);
                        this._lockId = returnedLock.lockId;
                    }
                    this.getLogger().info(`[lock-service] lock is still valid with expiration timestamp: ${returnedLock.expirationTimestamp}`);
                    return true;
                }
            }
            catch (error) {
                if (error instanceof NotFoundError) {
                    this.getLogger().warn("[lock-service] lock not found during heartbeat grab, attempting to reacquire before shutdown");
                    this.stopHeartbeat();
                    this._lockId = undefined;
                    this.status = LockServiceStatus.IDLE;
                    try {
                        await this.acquireLock();
                        this.getLogger().info("[lock-service] lock reacquired after heartbeat grab miss");
                        return true;
                    }
                    catch (reacquireError) {
                        if (reacquireError instanceof ConflictError) {
                            const message = "⚠️  Edit access lost – another user has taken over this branch. Shutting down server.";
                            if (process.env.SUPERBLOCKS_VSCODE === "true") {
                                this.getLogger().error(`clierr: ${JSON.stringify({ message })}`);
                            }
                            else {
                                this.getLogger().error(message);
                            }
                            this.emit("statusChange", LockServiceStatus.LOCK_INVALID);
                            this.status = LockServiceStatus.LOCK_INVALID;
                            await this.shutdownAndExit();
                            return false;
                        }
                        if (attempt < maxAttempts) {
                            this.getLogger().warn(`[lock-service] lock reacquire attempt ${attempt} failed with error: ${getErrorMeta(reacquireError).error.message}, retrying in ${retryTimeoutMs}ms...`);
                            await new Promise((resolve) => setTimeout(resolve, retryTimeoutMs));
                            continue;
                        }
                        throw reacquireError;
                    }
                }
                if (attempt < maxAttempts) {
                    this.getLogger().warn(`[lock-service] Attempt ${attempt} failed with error: ${getErrorMeta(error).error.message}, retrying in ${retryTimeoutMs}ms...`);
                    await new Promise((resolve) => setTimeout(resolve, retryTimeoutMs));
                    continue;
                }
                this.getLogger().error("[lock-service] error checking and updating lock", getErrorMeta(error));
                this.status = LockServiceStatus.IDLE;
                this.stopHeartbeat();
                throw error;
            }
        }
        this.status = LockServiceStatus.IDLE;
        this.stopHeartbeat();
        return false;
    }
    /**
     * Cleanup method to be called when the class is destroyed
     * This ensures the lock is released
     */
    async shutdown({ serverInitiated, switchingTo, initiatedByEmail, }) {
        if (serverInitiated) {
            this.emit("closedByServer", { initiatedByEmail, switchingTo });
        }
        // Perform one last sync using the sync callback if it is set
        if (this.syncCallback && this.isLocked) {
            // If the lock is still valid, then we can perform one last sync
            this.getLogger().info("[lock-service] performing one last sync before shutdown");
            try {
                await this.syncCallback();
                this.getLogger().info("[lock-service] sync callback completed");
            }
            catch (error) {
                this.getLogger().error("[lock-service] sync callback failed during shutdown, continuing with lock release", getErrorMeta(error));
            }
        }
        else {
            this.getLogger().info("[lock-service] no sync callback because lock is not valid anymore");
        }
        this.stopHeartbeat();
        this.clearStartupConnectionTimeout();
        if (this.isLocked && !serverInitiated) {
            // Server initiated shutdown means lock is already getting released
            this.getLogger().info("[lock-service] releasing lock before shutdown");
            await this.releaseLock();
            this.getLogger().info("[lock-service] lock released before shutdown");
        }
        this.getLogger().info("[lock-service] closing rpc client before shutdown");
        await this._rpcClient.close();
        this.getLogger().info("[lock-service] shutting down and exiting dev server");
    }
    /**
     * Shutdown the lock service and exit the dev server
     */
    async shutdownAndExit() {
        try {
            await this.shutdown({ serverInitiated: false });
        }
        catch (error) {
            this.getLogger().error("[lock-service] Lock service failed to gracefully shutdown and will forcefully exit", getErrorMeta(error));
        }
        finally {
            process.exit(1);
        }
    }
    /**
     * Add a connected user to the activity tracker.
     */
    addConnectedPeerIdToUser(userId, peerId) {
        this._activityTracker.addConnectedPeerIdToUser(userId, peerId);
        this._isRestarting = false;
        this.clearStartupConnectionTimeout();
        // Always clear any pending timeouts when user reconnects
        if (this._bufferPeriodTimeout) {
            clearTimeout(this._bufferPeriodTimeout);
            this._bufferPeriodTimeout = undefined;
            this.getLogger().info("[lock-service] user reconnected, cleared pending buffer period timeout");
        }
        if (this._inactivityTimeout) {
            clearTimeout(this._inactivityTimeout);
            this._inactivityTimeout = undefined;
            this.getLogger().info("[lock-service] user reconnected, cleared pending inactivity timeout");
        }
        // If lock was released or idle from previous session, reacquire lock
        if (this.status === LockServiceStatus.LOCK_RELEASED ||
            this.status === LockServiceStatus.IDLE) {
            this.getLogger().info("[lock-service] lock was released or idle, need to reacquire lock");
            void this.acquireLock();
            return;
        }
    }
    /**
     * Remove a connected user from the activity tracker.
     */
    removeConnectedPeerIdFromUser(userId, peerId) {
        this._activityTracker.removeConnectedPeerIdFromUser(userId, peerId);
        if (this._activityTracker.isDisconnected) {
            this.getLogger().info("[lock-service] all users disconnected, evaluating lock state");
            void this.evaluateLockState();
        }
    }
    async scheduleNextHeartbeat() {
        try {
            if (this._status === LockServiceStatus.LOCKED) {
                await this.sendHeartbeat();
                // Clear any existing timeout before scheduling new one to prevent duplicate heartbeat loops
                if (this._heartbeatTimeout) {
                    clearTimeout(this._heartbeatTimeout);
                }
                this._heartbeatTimeout = setTimeout(() => void this.scheduleNextHeartbeat(), this.heartbeatIntervalMs);
                // This is to avoid the process from staying alive to wait for the heartbeat timeout
                this._heartbeatTimeout.unref();
            }
        }
        catch (error) {
            this.getLogger().error("[lock-service] Error sending heartbeat, shutting down", getErrorMeta(error));
            this.emit("statusChange", LockServiceStatus.LOCK_INVALID);
            this.status = LockServiceStatus.LOCK_INVALID;
            clearTimeout(this._heartbeatTimeout);
            this._heartbeatTimeout = undefined;
            await this.shutdownAndExit();
        }
    }
    async sendHeartbeat() {
        if (this._lockId) {
            await this.checkAndUpdateLockIfPossible();
        }
        else {
            // We shouldn't be here, since we should always have a lockId if we are trying to send a heartbeat
            throw new Error("Lock ID is undefined");
        }
    }
    /**
     * Stop the heartbeat
     */
    stopHeartbeat() {
        if (this._heartbeatTimeout) {
            clearTimeout(this._heartbeatTimeout);
            this._heartbeatTimeout = undefined;
            this.getLogger().info("[lock-service] heartbeat stopped");
        }
    }
    clearStartupConnectionTimeout() {
        if (this._startupConnectionTimeout) {
            clearTimeout(this._startupConnectionTimeout);
            this._startupConnectionTimeout = undefined;
        }
        this._startupConnectionTimeoutExpired = false;
        this._startupConnectionTimeoutStartedAt = undefined;
    }
    startStartupConnectionTimeoutIfNeeded() {
        if (this._startupConnectionTimeoutStartedAt !== undefined ||
            this._startupConnectionTimeoutMs === undefined ||
            this._startupConnectionTimeoutMs <= 0) {
            return;
        }
        this.clearStartupConnectionTimeout();
        this._startupConnectionTimeoutStartedAt = Date.now();
        this.getLogger().debug(`[lock-service] starting initial connection timeout window (${this._startupConnectionTimeoutMs}ms)`);
        this._startupConnectionTimeout = setTimeout(() => {
            this._startupConnectionTimeout = undefined;
            this._startupConnectionTimeoutExpired = true;
            this.evaluateStartupConnectionTimeoutIfNeeded();
        }, this._startupConnectionTimeoutMs);
        this._startupConnectionTimeout.unref();
    }
    evaluateStartupConnectionTimeoutIfNeeded() {
        if (!this._startupConnectionTimeoutExpired) {
            return;
        }
        if (!this.isLocked) {
            return;
        }
        if (this._activityTracker.connectedUsers.length > 0) {
            return;
        }
        this.getLogger().warn(`[lock-service] no socket connection detected within ${this._startupConnectionTimeoutMs}ms after initial lock acquisition, marking session inactive`);
        this._activityTracker.markDisconnectedDueToStartupTimeout();
        void this.evaluateLockState().catch((error) => {
            this.getLogger().error("[lock-service] failed to evaluate lock state after startup connection timeout", getErrorMeta(error));
        });
    }
    /**
     * Evaluate if the lock should be released (user disconnected) or marked as ready for takeover (user inactive but connected)
     */
    async evaluateLockState() {
        // Skip if already evaluating lock release (buffer timeout exists)
        if (this._bufferPeriodTimeout) {
            this.getLogger().info("[lock-service] lock release already in progress, skipping duplicate evaluation");
            return;
        }
        // 1. Wait for Clark generation to complete if it's generating
        if (this.waitForGenerationCompleteCallback) {
            this.getLogger().debug("[lock-service] waiting for Clark generation to complete before evaluating lock state");
            await this.waitForGenerationCompleteCallback();
            this.getLogger().debug("[lock-service] Clark generation complete");
        }
        // Check if user is still connected AND active, or became active again
        const shouldAbortRelease = this._activityTracker.connectedUsers.length > 0 &&
            this._activityTracker.wasRecentlyActive;
        if (!this._isRestarting && shouldAbortRelease) {
            this.getLogger().debug("[lock-service] user is still active, not changing lock state");
            return;
        }
        const isUserDisconnected = this._activityTracker.isDisconnected;
        // Case 1: User is disconnected - release the lock
        if (isUserDisconnected) {
            if (this._activityTracker.wasRecentlyActive) {
                // User was recently active - give buffer period before releasing
                this.getLogger().debug(`[lock-service] user disconnected but was recently active, waiting for ${this._lockBufferMs}ms buffer period before releasing lock`);
                this._bufferPeriodTimeout = setTimeout(() => {
                    this.getLogger().debug("[lock-service] buffer period expired, releasing lock");
                    if (this.lockType === LockType.LOCAL) {
                        void this.shutdownAndExit();
                    }
                    else {
                        void this.shutdown({ serverInitiated: false });
                    }
                }, this._lockBufferMs);
                this._bufferPeriodTimeout.unref();
            }
            else {
                // User was NOT recently active - release immediately
                this.getLogger().debug("[lock-service] user disconnected and was not recently active, releasing lock immediately");
                if (this.lockType === LockType.LOCAL) {
                    await this.shutdownAndExit();
                }
                else {
                    await this.shutdown({ serverInitiated: false });
                }
            }
        }
        else {
            // Case 2: User is still connected but inactive - mark as ready for takeover immediately
            // No buffer period needed since we're not releasing the lock
            this.getLogger().debug("[lock-service] user inactive but still connected, marking lock as ready for takeover");
            this._activityTracker.markReadyForTakeover();
        }
    }
    /**
     * Restart the inactivity timer when user activity is detected.
     * This timer tracks if the user goes inactive (no pings for N minutes).
     */
    async restartInactivityTimer() {
        // Clear any existing inactivity timer since we have activity
        if (this._inactivityTimeout) {
            clearTimeout(this._inactivityTimeout);
            this._inactivityTimeout = undefined;
        }
        // Clear any buffer period timeout since we're receiving activity pings
        // This prevents shutdown when keep-alive pings continue after a disconnect event
        if (this._bufferPeriodTimeout) {
            clearTimeout(this._bufferPeriodTimeout);
            this._bufferPeriodTimeout = undefined;
            this.getLogger().info("[lock-service] activity detected, cleared pending buffer period timeout");
        }
        // Start a new inactivity timer to release the lock if the user is inactive for too long
        this._inactivityTimeout = setTimeout(() => {
            this.getLogger().debug(`[lock-service] No activity for ${this._inactivityThresholdMs}ms, evaluating lock state`);
            void this.evaluateLockState();
        }, this._inactivityThresholdMs);
        this._inactivityTimeout.unref();
    }
    /**
     * Mark the user's activity from UI ping
     */
    markActivity(isUserActive) {
        this._activityTracker.markActivity(isUserActive);
    }
    /** when the dev server is restarting, we will use the 5-minute shutdown timer instead of the instant one */
    async setIsRestarting() {
        this._isRestarting = true;
    }
}
//# sourceMappingURL=index.js.map