//! Shared data types for the inference core.

use roboticus_core::config::TierAdaptConfig;

use super::super::AppState;
use super::super::flight_recorder::ReactTrace;

/// Caller-supplied context that differs across the three entry points.
pub(crate) struct InferenceInput<'a> {
    pub state: &'a AppState,
    pub session_id: &'a str,
    pub user_content: &'a str,
    pub turn_id: &'a str,
    /// Label for model audit trail ("api", "api-stream", "telegram", etc.)
    pub channel_label: &'a str,
    /// System prompt fragments from caller
    pub agent_name: String,
    pub agent_id: String,
    pub os_text: String,
    pub firmware_text: String,
    pub primary_model: String,
    pub tier_adapt: TierAdaptConfig,
    /// Optional delegation workflow note injected into system prompt
    pub delegation_workflow_note: Option<String>,
    /// Whether to inject runtime diagnostics (API yes, channels no)
    pub inject_diagnostics: bool,
    /// Optional gate system note for channels
    pub gate_system_note: Option<String>,
    /// Optional delegated execution note for channels
    pub delegated_execution_note: Option<String>,
    /// Optional delegated execution result to treat as authoritative prior execution state.
    pub delegated_execution_result: Option<String>,
    /// Optional behavioral variation hint from TaskOperatingState.
    /// Injected as a system message when structural repetition, echo, or engagement
    /// decline is detected. Tells the model to vary its response pattern.
    pub behavioral_note: Option<String>,
    /// Multimodal content parts (images, audio transcriptions) from channel enrichment.
    /// Merged into the user `UnifiedMessage.parts` at context assembly time.
    pub content_parts: Option<Vec<roboticus_llm::format::ContentPart>>,
    /// Topic tag for conversation segmentation. Propagated to all assistant
    /// messages stored during this inference so topic-aware context assembly
    /// can properly segment the conversation.
    /// Read via the `topic_tag` parameter on `execute_inference_pipeline`, not
    /// directly from this struct (hence the allow).
    #[allow(dead_code)]
    pub topic_tag: Option<String>,
}

/// Result of `prepare_inference` — everything needed to call the LLM.
pub(crate) struct PreparedInference {
    pub model: String,
    pub request: roboticus_llm::format::UnifiedRequest,
    pub previous_assistant: Option<String>,
    pub query_embedding: Option<Vec<f32>>,
    pub cache_hash: String,
    /// Hash of the system prompt used for this inference, threaded into checkpoint saves.
    pub system_prompt_hash: String,
    /// Unified intent classification results from `IntentRegistry::classify()`.
    /// Sorted by priority (highest first). Computed once in `prepare_inference()`
    /// and threaded through shortcuts, guards, and cache bypass decisions.
    pub intents: Vec<super::super::intent_registry::Intent>,
    /// Context snapshot derived from finalized request messages.
    pub context_snapshot: PreparedContextSnapshot,
    /// Retrieval metrics captured during memory retrieval, for trace annotation.
    pub retrieval_metrics: roboticus_agent::retrieval::RetrievalMetrics,
    /// Tool-search pruning stats captured during request preparation.
    pub tool_search_stats: Option<roboticus_agent::tool_search::ToolSearchStats>,
    /// Delegated execution result carried forward as authoritative prior execution state.
    pub delegated_execution_result: Option<String>,
}

#[derive(Debug, Clone)]
pub(crate) struct PreparedContextSnapshot {
    pub complexity_level: String,
    pub token_budget: i64,
    pub system_prompt_tokens: i64,
    pub memory_tokens: i64,
    pub history_tokens: i64,
    pub history_depth: i64,
    /// JSON-serialized tier breakdown for `context_snapshots.memory_tiers_json`.
    pub memory_tiers_json: Option<String>,
    /// JSON-serialized retrieval summary for `context_snapshots.retrieved_memories_json`.
    pub retrieved_memories_json: Option<String>,
}

/// Result of a completed (non-streaming) inference cycle.
pub(crate) struct InferenceOutput {
    pub content: String,
    pub model: String,
    pub tokens_in: i64,
    pub tokens_out: i64,
    pub cost: f64,
    pub react_turns: usize,
    pub latency_ms: u64,
    pub quality_score: f64,
    pub escalated: bool,
    /// Tool calls executed during the ReAct loop: (tool_name, result_text).
    pub tool_results: Vec<(String, String)>,
    /// Full ReAct execution trace for the flight recorder.
    pub react_trace: Box<ReactTrace>,
}

/// Result of the unified inference pipeline (cache check → inference → post-turn ops).
pub(crate) struct PipelineResult {
    pub content: String,
    /// Model selected by routing before execution.
    pub selected_model: String,
    /// Model that actually produced the response (may differ on fallback/cache hit).
    pub model: String,
    /// When actual model differs, contains the originally selected model.
    pub model_shift_from: Option<String>,
    pub tokens_in: i64,
    pub tokens_out: i64,
    pub cost: f64,
    pub react_turns: usize,
    #[allow(dead_code)]
    // populated during pipeline execution; consumed by upcoming routing audit feature
    pub latency_ms: u64,
    #[allow(dead_code)]
    // populated during pipeline execution; consumed by upcoming routing audit feature
    pub quality_score: f64,
    #[allow(dead_code)]
    // populated during pipeline execution; consumed by upcoming routing audit feature
    pub escalated: bool,
    pub cached: bool,
    pub tokens_saved: u32,
    pub assistant_message_id: String,
    /// Tool calls executed during inference: (tool_name, result_text).
    #[allow(dead_code)]
    // populated during pipeline execution; consumed by upcoming routing audit feature
    pub tool_results: Vec<(String, String)>,
    pub react_trace: Box<ReactTrace>,
}

impl PipelineResult {
    /// Create from a shortcut or inference output with full metrics.
    pub fn from_inference(
        output: &InferenceOutput,
        selected_model: String,
        assistant_message_id: String,
    ) -> Self {
        let model_shift_from = if output.model != selected_model {
            Some(selected_model.clone())
        } else {
            None
        };
        Self {
            content: output.content.clone(),
            selected_model,
            model: output.model.clone(),
            model_shift_from,
            tokens_in: output.tokens_in,
            tokens_out: output.tokens_out,
            cost: output.cost,
            react_turns: output.react_turns,
            latency_ms: output.latency_ms,
            quality_score: output.quality_score,
            escalated: output.escalated,
            cached: false,
            tokens_saved: 0,
            assistant_message_id,
            tool_results: output.tool_results.clone(),
            react_trace: output.react_trace.clone(),
        }
    }

    /// Create a zero-cost synthetic result (skill-first, delegation, etc.).
    pub fn synthetic(content: String, model: String, assistant_message_id: String) -> Self {
        Self {
            content,
            selected_model: String::new(),
            model,
            model_shift_from: None,
            cached: false,
            tokens_saved: 0,
            tokens_in: 0,
            tokens_out: 0,
            cost: 0.0,
            react_turns: 0,
            latency_ms: 0,
            quality_score: 0.0,
            escalated: false,
            assistant_message_id,
            tool_results: vec![],
            react_trace: Box::new(ReactTrace::new("synthetic")),
        }
    }
}
