import { logger } from '@@/api/chat/logger';
import type { MessageCost, PromptImage, TurnMessage } from '@@/api/chat/types';
import * as sidecar from '@@/sidecar-registry';

type ClaudeCodeParams = {
  userId: number;
  email: string;
  username: string;
  prompt: string;
  sessionKey: string;
  model?: string;
};

type ClaudeCodeResult = {
  text: string;
  sessionId: string;
  model: string;
  cost: MessageCost;
};

export function clearClaudeCodeSession(sessionKey: string, userId: number): void {
  sidecar.clearClaudeSession(sessionKey, userId);
}

export async function sendClaudeCode(params: ClaudeCodeParams): Promise<ClaudeCodeResult> {
  logger.info('Claude Code exec (via sidecar)', { sessionKey: params.sessionKey });
  return sidecar.spawnClaude(params);
}

// ── Streaming variant for Chat Panel WebSocket ──

type ClaudeCodeStreamingParams = {
  userId: number;
  email: string;
  username: string;
  prompt: string;
  images?: PromptImage[];
  sessionKey: string;
  cwd?: string;
  model?: string;
  resumeSessionId?: string;
  durable?: boolean;
  /** Whose Linux account the turn runs as. Undefined for the owner; resolved by the caller, never a client. */
  member?: { osUser: string; home: string };
  // Finished turn messages, already committed by the sidecar; `seq` is the cursor id to deliver them under.
  onMessage: (msg: TurnMessage, seq?: number) => void;
};

type ClaudeCodeStreamingHandle = {
  /** End the agent's session upstream and stop listening. For an explicit disconnect. */
  kill: () => void;
  /**
   * Stop listening and leave the agent running.
   *
   * These are separate because officer's idle GC and a user's "disconnect" want different things, and
   * for a long time they could not have them: `unsub` was a closure reachable only through `kill`, so
   * letting go of a session necessarily killed it. That is why an idle browser took a live agent down
   * with it — including one the sidecar had deliberately protected because background work was still
   * in flight.
   */
  detach: () => void;
};

export async function sendClaudeCodeStreaming(params: ClaudeCodeStreamingParams): Promise<ClaudeCodeStreamingHandle> {
  const { onMessage, ...spawnParams } = params;

  logger.info('Claude Code streaming exec (via sidecar)', { sessionKey: params.sessionKey });

  // Session-scoped subscription. The persistent session outlives the turn, so background task events
  // (task:notification) arrive AFTER 'result' — do NOT unsubscribe on a terminal turn event; only on
  // an explicit kill/teardown (the returned handle, called from deleteSession/disconnect).
  const unsub = sidecar.onClaudeMessage((sessionKey, msg, seq) => {
    if (sessionKey === params.sessionKey) onMessage(msg, seq);
  });

  await sidecar.spawnClaudeStreaming(spawnParams);

  return {
    kill: () => {
      sidecar.killClaude(params.sessionKey, params.userId);
      unsub();
    },
    detach: unsub,
  };
}
