/**
 * SessionPool — per-chatId session isolation for multi-tenancy
 *
 * Each chat tab (chatId) gets its own isolated:
 *   - ContextManager  (message history, token tracking)
 *   - AgentOrchestrator  (todos, planMode, isFirstMessage, activeStream)
 *   - AbortController  (abort only affects the calling chat)
 *
 * Heavy shared modules (toolRegistry, symbolIndex, blueprintContext, etc.)
 * are injected once and shared across all sessions — zero memory overhead.
 *
 * Sessions are garbage-collected after 30 minutes of inactivity.
 */
import { ContextManager } from './context-manager.js';
import { AgentOrchestrator } from './orchestrator.js';
import { PromptAssembler } from './prompt-assembler.js';

const GC_INTERVAL_MS = 5 * 60 * 1000;   // GC check every 5 min
const SESSION_TTL_MS = 2 * 60 * 60 * 1000; // Expire after 2 hours idle (restore from disk anyway)

export class SessionPool {
  /**
   * @param {Object} shared  — modules shared across all sessions
   * @param {Object} shared.config
   * @param {Object} shared.toolRegistry
   * @param {Object} shared.projectContext
   * @param {Object} shared.cli
   * @param {Object} shared.symbolIndex
   * @param {Object} shared.impactAnalyzer
   * @param {Object} shared.blueprintContext
   * @param {Object} shared.generationPipeline
   * @param {Object} shared.sessionManager
   * @param {Object} shared.smartContext
   * @param {Object} shared.hooks
   * @param {Function} shared.getBroadcast  — () => broadcast fn (set after server starts)
   */
  constructor(shared) {
    this._shared = shared;
    /** @type {Map<string, SessionEntry>} */
    this._sessions = new Map();
    /** @type {Function|null} loadAutosave fn injected by server */
    this._loadAutosave = null;

    // Periodic GC
    this._gcTimer = setInterval(() => this._gc(), GC_INTERVAL_MS);
    if (this._gcTimer.unref) this._gcTimer.unref(); // Don't keep process alive
  }

  /**
   * Get or create a session for the given chatId.
   * @param {string|number} chatId
   * @returns {SessionEntry}
   */
  getOrCreate(chatId) {
    const key = String(chatId ?? '0');
    if (this._sessions.has(key)) {
      const s = this._sessions.get(key);
      s.lastActivity = Date.now();
      return s;
    }
    return this._create(key);
  }

  /**
   * Get an existing session (no create). Returns null if not found.
   * @param {string|number} chatId
   */
  get(chatId) {
    return this._sessions.get(String(chatId ?? '0')) || null;
  }

  /**
   * Delete (destroy) a session by chatId.
   */
  delete(chatId) {
    this._sessions.delete(String(chatId ?? '0'));
  }

  /**
   * List all active sessions with basic stats.
   */
  list() {
    return [...this._sessions.entries()].map(([chatId, s]) => ({
      chatId,
      name: s.convName || `Chat ${Number(chatId) + 1}`,
      status: s.convStatus || 'active',
      msgCount: s.contextManager.messages.length,
      lastActivity: new Date(s.lastActivity).toISOString(),
      createdAt: s.createdAt ? new Date(s.createdAt).toISOString() : null,
    }));
  }

  /** Find a session by conversation name (returns first match or null) */
  findByName(name) {
    for (const [, s] of this._sessions) {
      if (s.convName === name) return s;
    }
    return null;
  }

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

  _create(key) {
    const { config, toolRegistry, projectContext, cli,
            symbolIndex, blueprintContext, generationPipeline,
            sessionManager, smartContext, hooks,
            getBroadcast } = this._shared;

    const contextManager = new ContextManager(config);
    const promptAssembler = new PromptAssembler(config, projectContext);

    const orchestrator = new AgentOrchestrator({
      config,
      toolRegistry,
      contextManager,
      promptAssembler,
      cli,
      projectContext,
    });

    // Wire optional shared modules
    if (symbolIndex)         orchestrator.symbolIndex = symbolIndex;
    if (blueprintContext)    orchestrator.blueprintContext = blueprintContext;
    if (generationPipeline)  orchestrator.generationPipeline = generationPipeline;
    if (sessionManager)      orchestrator.sessionManager = sessionManager;
    if (smartContext)        orchestrator.smartContext = smartContext;
    if (hooks)               orchestrator.hooks = hooks;

    // Wire broadcast (lazy — resolved at call time via getBroadcast)
    if (getBroadcast) {
      orchestrator._broadcast = (msg) => {
        const fn = getBroadcast();
        if (fn) fn(msg);
      };
    }

    /** @type {SessionEntry} */
    const entry = {
      chatId: key,
      contextManager,
      orchestrator,
      abortController: null,   // set per-request in server.js
      lastActivity: Date.now(),
      // Conversation metadata (v3.1 Named Conversations)
      convName: null,          // human-readable name (auto-generated or set by VLClaw)
      convStatus: 'active',    // active | idle | archived
      createdAt: Date.now(),
    };

    // Auto-restore from disk if autosave exists
    if (this._loadAutosave) {
      const saved = this._loadAutosave(key);
      if (saved?.messages?.length) {
        contextManager.messages = saved.messages;
        contextManager.turnCount = saved.turnCount || Math.ceil(saved.messages.length / 2);
        contextManager.compressionCount = saved.compressionCount || 0;
        contextManager.totalTokensSaved = saved.totalTokensSaved || 0;
        contextManager.totalInputTokens = saved.totalInputTokens || 0;
        contextManager.totalOutputTokens = saved.totalOutputTokens || 0;
        contextManager.estimateTokens();
        // Restore orchestrator state
        if (saved.todos?.length) orchestrator.todos = saved.todos;
        if (saved.planMode) orchestrator.planMode = true;
        if (saved.planContent) orchestrator.planContent = saved.planContent;
        orchestrator.isFirstMessage = saved.isFirstMessage ?? false;
        // Restore session metadata
        if (saved.convName) entry.convName = saved.convName;
        if (saved.convStatus) entry.convStatus = saved.convStatus;
      }
    }

    this._sessions.set(key, entry);
    return entry;
  }

  _gc() {
    const now = Date.now();
    for (const [key, s] of this._sessions) {
      if (now - s.lastActivity > SESSION_TTL_MS) {
        this._sessions.delete(key);
      }
    }
  }

  /** Stop the GC timer (call on server shutdown). */
  destroy() {
    clearInterval(this._gcTimer);
  }
}

/**
 * @typedef {Object} SessionEntry
 * @property {string} chatId
 * @property {ContextManager} contextManager
 * @property {AgentOrchestrator} orchestrator
 * @property {AbortController|null} abortController
 * @property {number} lastActivity
 * @property {string|null} convName       — human-readable conversation name
 * @property {string} convStatus          — active | idle | archived
 * @property {number} createdAt
 */
