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

// The OpenCode analog of send-claude-code.ts: it drives a turn through the officer-opencode sidecar,
// which spawns `opencode run … --format json` (tools hard-anchored to the chat cwd via --dir), maps its
// output to TurnMessages, commits each one to chat_session_events and streams the finished message plus
// its cursor id back over the sidecar WS. We subscribe (filtered by sessionKey) and forward to the
// caller's onMessage — the same contract sendClaudeCodeStreaming uses.

type OpenCodeStreamingParams = {
  userId: number;
  email: string;
  username: string;
  prompt: string;
  sessionKey: string;
  cwd?: string;
  model?: string;
  role?: string;
  resumeSessionId?: string;
  durable?: boolean;
  images?: PromptImage[];
  // Finished turn messages, already committed by the sidecar; `seq` is the cursor id to deliver them under.
  onMessage: (msg: TurnMessage, seq?: number) => void;
};

type OpenCodeStreamingHandle = {
  /** End the agent's session upstream and stop listening. For an explicit disconnect. */
  kill: () => void;
  /** Stop listening and leave the agent running — see the same pair in `send-claude-code.ts`. */
  detach: () => void;
};

export async function sendOpenCodeStreaming(params: OpenCodeStreamingParams): Promise<OpenCodeStreamingHandle> {
  logger.info('OpenCode streaming exec (via sidecar)', { sessionKey: params.sessionKey, model: params.model });

  // Forward this session's turn messages; unsubscribe on the terminal one. Unlike the Claude harness
  // there is nothing after `result` here — `opencode run` exits with the turn, so it has no background
  // tasks that could report later.
  const unsub = sidecar.onOpenCodeMessage((sessionKey, msg, seq) => {
    if (sessionKey !== params.sessionKey) return;
    params.onMessage(msg, seq);
    if (msg.type === 'result' || msg.type === 'error' || msg.type === 'stopped') unsub();
  });

  // Resume an existing OpenCode session when we know its id: a stored mapping (set from the sidecar's
  // opencode:session report), the sessionKey itself when it's already a `ses_…` id (history resume), or
  // an explicit resumeSessionId. Otherwise the sidecar's `run` creates a fresh session.
  const resumeSessionId =
    getOpenCodeSession(params.sessionKey) ??
    (params.sessionKey.startsWith('ses_') ? params.sessionKey : undefined) ??
    params.resumeSessionId;

  try {
    await sidecar.spawnOpenCodeStreaming({
      sessionKey: params.sessionKey,
      prompt: params.prompt,
      cwd: params.cwd,
      model: params.model,
      resumeSessionId,
      durable: params.durable,
      images: params.images,
    });
  } catch (err) {
    unsub();
    throw err;
  }

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