/**
 * Harness — provider-agnostic agent runtime contract.
 *
 * Each AI provider that ships an "agent" experience (Claude Agent SDK,
 * Codex app-server, …) implements this interface. The dispatcher in
 * `supervisor/bloby-agent.ts` selects which one to use based on
 * `cfg.ai.provider` so the rest of the supervisor never needs to care.
 *
 * The two execution shapes mirror what the supervisor already needs:
 *   • startConversation / pushMessage / endConversation — long-lived chat
 *     with streaming events. Used by the dashboard and admin WhatsApp.
 *   • startBlobyAgentQuery / stopBlobyAgentQuery — one-shot request/response
 *     used by customer WhatsApp and the scheduler.
 *
 * Event names emitted via the `onMessage` callback are the same vocabulary
 * `supervisor/index.ts` and `channels/manager.ts` already consume — every
 * harness translates its native events into this set:
 *   bot:typing, bot:token, bot:tool, bot:response, bot:turn-complete,
 *   bot:done, bot:error, bot:task-created, bot:task-progress, bot:task-done,
 *   bot:conversation-ended.
 */

import type { SavedFile } from '../file-saver.js';

export interface RecentMessage {
  role: 'user' | 'assistant';
  content: string;
}

export interface AgentAttachment {
  type: 'image' | 'file';
  name: string;
  mediaType: string;
  data: string; // base64
}

export type OnAgentMessage = (type: string, data: any) => void;

export interface Harness {
  /* ── Live conversation API ── */
  startConversation(
    conversationId: string,
    model: string,
    onMessage: OnAgentMessage,
    names?: { botName: string; humanName: string },
    recentMessages?: RecentMessage[],
  ): Promise<boolean>;

  pushMessage(
    conversationId: string,
    content: string,
    attachments?: AgentAttachment[],
    savedFiles?: SavedFile[],
  ): boolean;

  hasConversation(conversationId: string): boolean;
  endConversation(conversationId: string): void;
  endAllConversations(): void;
  isConversationBusy(conversationId: string): boolean;
  /** True if ANY conversation in this harness is mid-turn (no id — used to defer backend restarts). */
  anyConversationBusy(): boolean;
  /** True if ANY one-shot query (startBlobyAgentQuery: pulse/cron, customer WhatsApp) is in flight.
   *  These do NOT register as live conversations, so anyConversationBusy() can't see them — the
   *  supervisor ORs this in so a queued self-update / backend restart defers past one-shot turns too. */
  anyOneShotActive(): boolean;
  stopSubAgentTask(conversationId: string, taskId: string): Promise<void>;
  warmUpForLiveConversation(
    model: string,
    names?: { botName: string; humanName: string },
  ): Promise<void>;

  /* ── One-shot API (request/response) ── */
  startBlobyAgentQuery(
    conversationId: string,
    prompt: string,
    model: string,
    onMessage: OnAgentMessage,
    attachments?: AgentAttachment[],
    savedFiles?: SavedFile[],
    names?: { botName: string; humanName: string },
    recentMessages?: RecentMessage[],
    supportPrompt?: string,
    maxTurns?: number,
  ): Promise<void>;

  stopBlobyAgentQuery(conversationId: string): void;

  /* ── Workspace agent endpoint (POST /api/agent/query) ── */
  runAgentQuery(req: AgentQueryRequest): Promise<AgentQueryResult>;
}

export interface AgentQueryRequest {
  message: string;
  /** Already-resolved system prompt content (not a path). Empty/omitted → use the harness's default coding-agent prompt. */
  systemPrompt?: string;
  /** Provider-specific session id to resume (Claude: SDK session_id; Codex: threadId). */
  sessionId?: string;
  /** Max turns (Claude only — Codex has no equivalent). */
  maxTurns?: number;
  /** Hard timeout in ms. */
  timeout?: number;
}

export interface AgentQueryResult {
  ok: boolean;
  response?: string;
  /** Provider-specific session id the caller can pass back to resume this conversation. */
  sessionId?: string;
  toolsUsed?: string[];
  usedFileTools?: boolean;
  error?: string;
}
