// v2.6 Phase 1-UX §9b — chat-scoped agent-session state.
//
// Reads GET /api/coder/sessions/:id/agent-sessions (the per-(chat,agent)
// backend-session rows) and drives the AgentComposerBar resumed/new-session
// chip. Module-singleton external store keyed by sessionId — same shape as
// useProviderSnapshot — so the two consumers (CoderPane, which owns the
// message_complete WS signal, and AgentComposerBar, which renders the chip)
// share one cache and one fetch per chat. CoderPane calls
// refreshAgentSessions(sessionId) on each message_complete (the same trigger
// usePendingChanges already keys off); the chip then reflects the freshly
// resumed/created session.

import { useEffect, useSyncExternalStore } from 'react';
import { api, type AgentSessionInfo } from '@/api/client';

type Entry = {
  data: AgentSessionInfo[];
  inflight: Promise<AgentSessionInfo[]> | null;
};

const store = new Map<string, Entry>();
const listeners = new Set<() => void>();
const EMPTY: AgentSessionInfo[] = [];

function notify(): void {
  for (const fn of listeners) fn();
}

function subscribe(fn: () => void): () => void {
  listeners.add(fn);
  return () => listeners.delete(fn);
}

function getEntry(sessionId: string): Entry {
  let entry = store.get(sessionId);
  if (!entry) {
    entry = { data: EMPTY, inflight: null };
    store.set(sessionId, entry);
  }
  return entry;
}

async function doFetch(sessionId: string): Promise<AgentSessionInfo[]> {
  const data = await api.coder.agentSessions(sessionId);
  const entry = getEntry(sessionId);
  entry.data = data;
  entry.inflight = null;
  notify();
  return data;
}

function ensureLoaded(sessionId: string): void {
  const entry = getEntry(sessionId);
  if (entry.data !== EMPTY || entry.inflight) return;
  entry.inflight = doFetch(sessionId).catch(() => {
    // boocoder may be down or the chat has no agent-session rows yet; treat as
    // empty (the chip falls back to "new session" / hides).
    const e = getEntry(sessionId);
    e.inflight = null;
    return EMPTY;
  });
}

/** Force a refetch for one chat. Wired to message_complete by CoderPane. */
export function refreshAgentSessions(sessionId: string): Promise<AgentSessionInfo[]> {
  const entry = getEntry(sessionId);
  entry.inflight = null;
  return doFetch(sessionId);
}

/**
 * Chat-scoped agent-session rows. Pass `undefined` to opt out (no fetch, empty
 * result) — AgentComposerBar does this for BooChat callers and fresh chats so
 * the chip stays hidden. Fetches on mount (and on sessionId change); refetch on
 * message_complete is driven externally via refreshAgentSessions.
 */
export function useAgentSessions(sessionId: string | undefined): {
  sessions: AgentSessionInfo[];
} {
  const sessions = useSyncExternalStore(
    subscribe,
    () => (sessionId ? getEntry(sessionId).data : EMPTY),
  );
  useEffect(() => {
    if (sessionId) ensureLoaded(sessionId);
  }, [sessionId]);
  return { sessions: sessionId ? sessions : EMPTY };
}
