// v1.12.4: shared inter-phase types/constants for the extracted phase files.
// Lives here so stream-phase, tool-phase, and the summary functions still in
// inference.ts can all reference the same definitions without circular imports.
//
// P5: the shared pipeline types (InferenceContext / TurnArgs / StreamResult /
// InferenceFrame / FramePublisher) moved here from turn.ts. turn.ts was both the
// type hub (every phase imported these from './turn.js') AND the orchestration
// leaf (it imports functions back from payload/stream-phase/tool-phase/
// error-handler/sentinel-summaries) — a hub-and-leaf near-cycle. Hosting the
// shared types here (this module imports no inference functions) breaks it.

import type { FastifyBaseLogger } from 'fastify';
import type { Sql } from '../../db.js';
import type { Config } from '../../config.js';
import type {
  ErrorReason,
  MessageMetadata,
  ToolCall,
  UserStreamFrame,
} from '../../types/api.js';
import type { Broker } from '../broker.js';
import type { HookRunner } from '../hooks.js';
import type { MistakeState } from './mistake-tracker.js';

export interface StreamPhaseState {
  accumulated: string;
  startedAt: string | null;
}

export const DB_FLUSH_INTERVAL_MS = 500;

export interface InferenceFrame {
  type:
    | 'message_started'
    | 'delta'
    | 'tool_call'
    | 'tool_result'
    | 'message_complete'
    | 'usage'
    | 'messages_deleted'
    | 'session_renamed'
    | 'chat_renamed'
    | 'error'
    | 'flow_run_started'
    | 'flow_run_step_updated'
    // tool trace frames
    | 'tool_trace_start'
    | 'tool_trace_finish'
    // arena frames
    | 'battle_started'
    | 'contestant_updated'
    | 'battle_updated'
    // inter-agent message
    | 'agent_message';
  message_id?: string;
  message_ids?: string[];
  chat_id?: string;
  tool_message_id?: string;
  tool_call_id?: string;
  // v1.8.2: 'system' added so cap-hit sentinel messages can announce themselves
  // through the normal message_started → delta → message_complete sequence.
  role?: 'assistant' | 'tool' | 'user' | 'system';
  content?: string;
  tool_call?: ToolCall;
  output?: unknown;
  truncated?: boolean;
  error?: string;
  // v1.8.2: structured error reason. Set on `type: 'error'` so the UI can
  // surface a specific message; `error` stays the human-readable text.
  reason?: ErrorReason;
  // v1.8.2: piggybacks on `message_complete` so static or terminally-resolved
  // messages can carry their persisted metadata to the live stream without a
  // refetch (sentinels carry { kind: 'cap_hit', ... }; failed messages carry
  // { kind: 'error', ... }).
  metadata?: MessageMetadata | null;
  tokens_used?: number | null;
  ctx_used?: number | null;
  ctx_max?: number | null;
  completion_tokens?: number | null;
  started_at?: string | null;
  finished_at?: string | null;
  model?: string;
  cache_tokens?: number | null;
  reasoning_tokens?: number | null;
  session_id?: string;
  name?: string;
  // tool trace frames
  trace_id?: string;
  tool_name?: string;
  tool_input?: Record<string, unknown>;
  tool_output?: string | null;
  latency_ms?: number;
  outcome?: string;
  // agent snapshot restore
  agent?: string | null;
  // orchestrator frames ([D-6])
  run_id?: string;
  flow_name?: string;
  band?: 'small' | 'medium' | 'large';
  steps?: Array<{ step_id: string; agent: string; kind: 'agent' | 'code'; chat_id: string; label: string }>;
  step_id?: string;
  status?: string;
  run_status?: 'running' | 'completed' | 'failed' | 'cancelled';
  report?: string;
  // v2.8-compare: groups messages belonging to the same compare operation.
  compare_group_id?: string;
  // inter-agent message
  sender_step_id?: string;
  channel?: string;
  // arena frames
  battle_id?: string;
  battle_type?: 'coding' | 'qa';
  prompt?: string;
  contestants?: Array<{ id: string; identity: string; model: string; lane: 'local' | 'cloud' }>;
  contestant_id?: string;
  battle_status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
  duration_ms?: number;
  tokens_per_sec?: number;
  winner_contestant_id?: string | null;
  analysis_ready?: boolean;
  cross_exam_id?: string;
  delta?: string;
  stream_seq?: number;
}

export type FramePublisher = (sessionId: string, frame: InferenceFrame) => void;

export interface InferenceContext {
  sql: Sql;
  config: Config;
  log: FastifyBaseLogger;
  publish: FramePublisher;
  publishUser: (frame: UserStreamFrame) => void;
  // v1.11: passed through so compaction.process can publish 'compacted'
  // frames on the same session WS channel useSessionStream subscribes to.
  // Compaction is the only path that needs the raw broker handle (regular
  // inference goes through `publish`); keeping a separate field avoids
  // tempting other code paths into bypassing the session-id binding.
  broker: Broker;
  // vWhale: lifecycle hooks runner. Undefined when no hooks configured.
  // Hook calls are best-effort — a failing hook never blocks inference.
  hooks?: HookRunner;
}

export interface StreamResult {
  finishReason: string | null;
  content: string;
  toolCalls: ToolCall[];
  promptTokens: number | null;
  completionTokens: number | null;
  // v1.13.1-C: reasoning text accumulated across reasoning-delta parts.
  // Empty string when the model doesn't emit reasoning (most cases).
  reasoning: string;
  // Phase 2 (anthropic): reasoning split per thinking block, each with its
  // Anthropic signature. Adaptive thinking auto-enables interleaved thinking,
  // so a turn can carry several signed blocks — they MUST be replayed verbatim
  // per block (a joined string + one signature would 400). Empty/undefined for
  // models without signed reasoning.
  reasoningBlocks?: Array<{ text: string; signature?: string }>;
  // vDeepSeek: optional cache-hit token count from DeepSeek's API.
  // Only populated when using @ai-sdk/deepseek provider (not llama-swap).
  cacheReadTokens?: number;
  // vDeepSeek: optional reasoning token count from DeepSeek's API.
  // Only populated when using @ai-sdk/deepseek provider (not llama-swap).
  reasoningTokens?: number;
}

export interface TurnArgs {
  sessionId: string;
  chatId: string;
  assistantMessageId: string;
  // v1.8.2: cumulative tool calls executed this run. Compared against the
  // resolved budget at the top of each turn. Replaces the older `depth`
  // counter (which counted iterations, not invocations).
  toolsUsed: number;
  // v1.11.6: ordered tool calls executed in this user-message turn (across
  // recursive runAssistantTurn invocations). Reset to [] at user-message
  // boundaries by runInference, same as toolsUsed. Doom-loop check at the
  // top of runAssistantTurn slices the last DOOM_LOOP_THRESHOLD entries.
  recentToolCalls: ToolCall[];
  // v#12 MistakeTracker: heterogeneous-failure recovery state. Loop-local,
  // reset per runInference (user-message boundary) like recentToolCalls. Folds
  // tool-phase outcomes via recordStep each iteration; detectMistakePattern
  // gates the nudge/escalate decision.
  mistakeTracker: MistakeState;
  // v#12: transient model-facing recovery note set when a nudge fires. Consumed
  // (appended as a role:'system' message + cleared) on the NEXT payload build.
  // Never persisted — mirrors how the cap-hit/doom-loop notes live only inside
  // the summary call's messages array.
  pendingRecoveryNote?: string;
  modelOverride?: string;
  // v2.8-compare: opaque group id that rides on every published frame.
  compareGroupId?: string;
  signal: AbortSignal | undefined;
}
