import type { NormalizedAgentEvent } from '../agent-events';
import type { streamExecInContainer } from './docker';
import type { AdapterWorkspace } from './provider';

export type AgentRuntimeName = 'codex' | 'opencode' | 'claude';

export type ExecStreamFn = typeof streamExecInContainer;

export type TurnResult = {
  // Assistant text the adapter captured out-of-band (Codex --output-last-message
  // file, Claude `result` event). Empty/undefined when everything streamed via onEvent.
  finalMessage?: string;
  // Runtime session id to persist for continuity (resume/--session/--resume).
  sessionId?: string;
  // Non-empty when the runtime reported a hard failure the caller must surface.
  error?: string;
};

export type AgentRuntime = {
  readonly name: AgentRuntimeName;
  prepareAuth(workspace: AdapterWorkspace): Promise<void>;
  runTurn(
    workspace: AdapterWorkspace,
    prompt: string,
    onEvent: (event: NormalizedAgentEvent) => Promise<void>,
  ): Promise<TurnResult>;
  abort(workspace: AdapterWorkspace): Promise<void>;
};

export type AdapterFactory = (deps?: {
  execStream?: ExecStreamFn;
}) => AgentRuntime;

const factories = new Map<AgentRuntimeName, AdapterFactory>();

export const registerAdapter = (
  name: AgentRuntimeName,
  factory: AdapterFactory,
): void => {
  factories.set(name, factory);
};

export const getAdapter = (name: AgentRuntimeName): AgentRuntime => {
  const factory = factories.get(name);
  if (!factory) {
    throw new Error(`No agent runtime adapter registered for "${name}".`);
  }
  return factory();
};
