// frontend/src/lib/models/AgentChat.ConnectionManager.svelte.ts
// #region AgentChat.ConnectionManager [C:4] [TYPE Class] [SEMANTICS agent-chat,connection,reconnect]
// @ingroup AgentChat
// @BRIEF Manages Gradio client connection lifecycle — disconnect detection, exponential reconnect loop, and retry.
// @RELATION CALLED_BY -> [AgentChat.Model]
// @INVARIANT Max 5 reconnect attempts before permanent disconnected state.
// @INVARIANT Reconnect loop is idempotent: only one timer runs at a time.
// @RATIONALE Extracted from AgentChat.Model (630→~340 lines) per decomposition gate. Owns reconnect timer + attempt tracking, delegates client creation and state mutation via callbacks.
import { Client, type Client as GradioClient } from "@gradio/client";
import { log } from "$lib/cot-logger";
import type { ConnectionState, StreamingState } from "./AgentChatTypes.js";

// ── Reconnect constants ──────────────────────────────────────────

export const RECONNECT_MAX_ATTEMPTS = 5;
export const RECONNECT_INTERVAL_MS = 5_000;

// ── Callback interface ───────────────────────────────────────────

export interface ConnectionManagerCallbacks {
  onConnected: (client: GradioClient) => void;
  onDisconnected: () => void;
  onDisconnectedPermanent: () => void;
  onStreamingStateChange: (s: StreamingState) => void;
}

// ═══ ConnectionManager ═══════════════════════════════════════════

export class ConnectionManager {
  private _reconnectAttempts = 0;
  private _reconnectTimer: ReturnType<typeof setInterval> | null = null;

  constructor(private cb: ConnectionManagerCallbacks) {}

  // ── Public actions ──────────────────────────────────────────────

  async retryConnection(): Promise<void> {
    log("AgentChat.ConnectionManager", "REASON", "Manual reconnect");
    this._reconnectAttempts = 0;
    try {
      const client = await Client.connect("/api/agent/gradio");
      this.cb.onConnected(client);
      this.cb.onStreamingStateChange("idle");
      log("AgentChat.ConnectionManager", "REFLECT", "Reconnected successfully");
    } catch (e: unknown) {
      this.cb.onDisconnectedPermanent();
      log("AgentChat.ConnectionManager", "EXPLORE", "Reconnect failed", {}, e instanceof Error ? e.message : "Unknown");
    }
  }

  /** Call when the Gradio client emits a disconnect event. */
  onDisconnect(): void {
    this.cb.onDisconnected();
    this._reconnectAttempts = 0;
    log("AgentChat.ConnectionManager", "EXPLORE", "Gradio disconnected", {}, "Gradio SSE disconnected");
    this._startReconnectLoop();
  }

  /** Call when the Gradio client reconnects. Resets attempt counter and clears timer. */
  onReconnect(client: GradioClient): void {
    this.cb.onConnected(client);
    this.cb.onStreamingStateChange("idle");
    this._reconnectAttempts = 0;
    this._clearTimer();
    log("AgentChat.ConnectionManager", "REFLECT", "Gradio reconnected");
  }

  /** Stop the reconnect loop. */
  stopReconnect(): void {
    this._clearTimer();
    this._reconnectAttempts = 0;
  }

  /** Bump attempts to force stop the reconnect loop (legacy escape hatch). */
  setReconnectAttempts(n: number): void {
    this._reconnectAttempts = n;
  }

  // ── Private ─────────────────────────────────────────────────────

  private _startReconnectLoop(): void {
    if (this._reconnectTimer) return;
    this._reconnectTimer = setInterval(async () => {
      this._reconnectAttempts++;
      if (this._reconnectAttempts > RECONNECT_MAX_ATTEMPTS) {
        this.cb.onDisconnectedPermanent();
        this._clearTimer();
        log("AgentChat.ConnectionManager", "EXPLORE", "Max reconnect attempts reached", { attempts: this._reconnectAttempts }, "Max 5 reconnect attempts exhausted");
        return;
      }
      try {
        const client = await Client.connect("/api/agent/gradio");
        this.onReconnect(client);
      } catch {
        log("AgentChat.ConnectionManager", "EXPLORE", "Reconnect attempt failed", { attempt: this._reconnectAttempts }, "Client.connect threw");
      }
    }, RECONNECT_INTERVAL_MS);
  }

  private _clearTimer(): void {
    if (this._reconnectTimer) {
      clearInterval(this._reconnectTimer);
      this._reconnectTimer = null;
    }
  }
}
// #endregion AgentChat.ConnectionManager
