/**
 * agent-loop/types.ts — shared type surface for the agent-loop modules.
 *
 * These interfaces were previously declared in ../agent-loop.ts, which the
 * focused modules (context-control / loop-safety / movement-setup / …) then
 * imported back as `import type … from '../agent-loop.js'` — a type-level
 * circular reference. Centralising them here lets every module (and the
 * barrel) depend on a leaf with no back-edge. agent-loop.ts re-exports them
 * so external callers keep importing from './agent-loop.js' unchanged.
 */
import type { ContextAction, ContextManager } from '../context-manager.js';
import type { SafetyConfig } from '../../config.js';
import type { ToolResultCache } from '../context/tool-result-cache.js';
import type { Conversation } from '../context/conversation.js';
import type { Message } from '../../llm/openai-compat.js';

export interface Movement {
  name: string;
  edit: boolean;
  persona: string;
  instruction: string;
  allowedTools: string[];
  /**
   * SSH connection allowlist resolved from the workspace tool policy (settings →
   * SSH connection registrations for this job's space). A UUID list; empty when
   * ssh is disabled in the policy or no connections are registered, in which case
   * SSH tools reject cleanly. (Legacy `['*']` wildcard still honored if present.)
   */
  allowedSshConnections?: string[];
  rules: Array<{ condition: string; next: string }>;
  defaultNext?: string; // フォールバック遷移先
}

export interface MovementResult {
  next: string | null;       // 次の movement 名 or 'COMPLETE' or 'ABORT' or null
  output: string;            // LLM の最終出力テキスト
  toolsUsed: string[];       // 使用したツール名リスト
  lessons?: string | null;   // このステップで得た教訓
  waitReason?: string | null; // waiting_human の場合の待機理由（例: 'browser_login'）
  browserSessionId?: string | null; // InteractiveBrowse で確保したセッションID
  // next='WAIT_SUBTASKS'（WaitSubTask ツール / complete 時の自動停車）で、再開する
  // movement 名を運ぶ。未指定なら piece-runner が movementDef.default_next に落とす。
  // WaitSubTask は現在の movement 名を入れ、子完了後に同じステップへ再入させる。
  resumeMovement?: string | null;
  // next='ABORT' のときに、どの経路で abort したかを示す細分コード。
  // piece-runner が PieceRunResult.abortReason に伝搬する。未指定なら
  // 'movement_abort'（後方互換）。
  abortCode?: string;
}

export interface ToolResultInfo {
  isError: boolean;
  result: string;
  /** Wall-clock duration of the tool dispatch in ms (incl. parallel batch). */
  durationMs: number;
  /** True when served from ToolResultCache (no real tool execution). */
  cacheHit: boolean;
}

export interface LLMCallInfo {
  /** Iteration index within the current movement (0-based). */
  iteration: number;
  /** Stream wall-clock time from request send to last chunk. */
  durationMs: number;
  /** Tokens reported by the provider for THIS call. May be 0/undefined. */
  promptTokens?: number;
  completionTokens?: number;
  /** Number of tool_calls returned. 0 means text-only response. */
  toolCalls: number;
  /** Characters of accumulated assistant text (text-only signal). */
  textChars: number;
  /** True if the stream surfaced an error mid-flight. */
  hadError: boolean;
}

export interface HandoffContext {
  /** Piece name of the previous job in the same local_task. */
  prevPiece: string;
  /** Latest "result" or "ask" comment body from the previous job, or null
   * when none was posted (rare edge: prev job ended without final output). */
  prevResult: string | null;
}

export interface AgentLoopCallbacks {
  onToolUse?: (toolName: string, input: Record<string, unknown>, callId?: string) => void;
  onToolCallDelta?: (callId: string, name: string, chunk: string) => void;
  onToolResult?: (toolName: string, info: ToolResultInfo, callId?: string) => void;
  onText?: (text: string) => void;
  onTextPreview?: (movementName: string, preview: string) => void;
  onContextAction?: (action: ContextAction) => void;
  onContextUpdate?: (payload: { promptTokens: number; limitTokens: number }) => void;
  onMovementComplete?: (movementName: string, result: MovementResult) => void;
  onMemoryCheckpoint?: (toolCount: number) => void;
  /**
   * Fires once per completed LLM call (one iteration of the agent loop)
   * so the reporter can attribute the wall-clock gap between consecutive
   * tool calls to LLM time vs. tool time.
   */
  onPromptProgress?: (progress: { processed: number; total: number; timeMs: number; cache: number }) => void;
  onLLMCall?: (info: LLMCallInfo) => void;
  /**
   * Fires when a proxy-mode LLM client resolves the physical backend that
   * handled the call (see OpenAICompatClient + LLMEvent 'backend'). The
   * worker uses this to record the sticky `lastBackendId` on the job
   * for Pet mapping / NodeStatus widgets. Direct workers never fire it.
   *
   * Fired on every proxied call; consumers are responsible for any
   * sticky-once semantics they need (the DB worker only writes on the
   * first non-null event).
   */
  onBackendResolved?: (info: { backendId: string; cacheKey: string | null }) => void;
  /**
   * delegate ライブコンソール: サブエージェントの開始/状態変化を通知。
   * status は 'running'（開始時）→ 終了時に success/aborted/needs_user_input。
   * 親 onText には流さず、この専用チャンネルだけに流す（メインチャット非汚染）。
   */
  onDelegateLifecycle?: (info: {
    delegateRunId: string;
    parentRunId: string | null;
    depth: number;
    description: string;
    status: 'running' | 'success' | 'aborted' | 'needs_user_input';
  }) => void;
  /** delegate サブのライブ文字（トークン単位）。delegateRunId でタグ付け。 */
  onDelegateText?: (delegateRunId: string, text: string) => void;
  /** delegate サブが使用中のツール（"WebFetch 実行中" 表示用）。 */
  onDelegateTool?: (delegateRunId: string, toolName: string, summary: string) => void;
}

export interface ExecuteMovementOptions {
  callbacks?: AgentLoopCallbacks;
  maxIterations?: number;
  contextManager?: ContextManager;
  cancelSignal?: AbortSignal;
  cancelCheck?: () => boolean;
  visitCount?: number;
  maxVisits?: number;
  safetyConfig?: SafetyConfig;
  /**
   * Cross-movement tool result cache. Owned by the caller (typically
   * runPiece) so a single instance survives every movement in one piece run.
   * Phase 1: only Read results are stored / served.
   */
  toolResultCache?: ToolResultCache;
  /**
   * Handoff context when this job continues from a previous piece in the
   * same local_task. When set, the system prompt receives a "前 piece からの
   * 引き継ぎ" block with the previous piece name and final result. The static
   * "Continue 機能" block is always present regardless of this field.
   */
  handoffContext?: HandoffContext;
  /**
   * Called between iterations to check for user interjections.
   * Receives the current movement name. Returns user messages to inject.
   * The caller is responsible for marking them as injected in the DB.
   */
  checkInterjections?: (movementName: string) => Promise<Array<{ id: number; body: string }>>;
  /**
   * Phase A: ジョブ全体で共有する連続スレッド。渡された場合 messages はこれが所有する。
   * 初回 movement では seed()、以降は enterMovement() で guidance を追記する。
   */
  conversation?: Conversation;
  /** P2: prior conversational turns (sanitized) to replay at the first seed of a
   * continuation job. Non-empty only for continuation jobs with an existing
   * transcript. When present, the handoff(LIMIT 1) preamble block is suppressed
   * (the replayed turns supersede it). */
  priorTurns?: Message[];
}
