//! Shared inference core used by all three entry points (API, streaming, channel).
//!
//! `prepare_inference` builds a `PreparedInference` from an `InferenceInput`, handling:
//! model selection, embedding, RAG retrieval, history, system prompt, HMAC, context building.
//!
//! `run_react_loop` drives the Think→Act→Observe→Finish cycle on top of a prepared request.
//!
//! `post_turn_ingest` spawns background memory + embedding work.

mod cache;
mod context_builder;
mod guard_fallback;
mod guard_retry;
mod inference_pipeline;
mod post_turn;
mod react_loop;
mod tool_prune;
mod types;

// Re-export public surface — all callers within the crate use `core::*`.
// The allow(unused_imports) suppresses false-positive lint warnings: these
// re-exports are consumed by sibling modules (pipeline/, etc.), not by code
// inside core/ itself, so rustc's unused-import lint cannot see the usage.
#[allow(unused_imports)]
pub(crate) use {
    cache::{check_cache, store_in_cache},
    context_builder::prepare_inference,
    guard_fallback::{deterministic_guard_fallback, is_task_like_turn},
    guard_retry::build_retry_request,
    inference_pipeline::{execute_inference_pipeline, record_cost},
    post_turn::{post_turn_ingest, refine_session_nickname},
    react_loop::{run_inference_and_react, sanitize_model_output},
    tool_prune::always_include_operational_tools,
    types::{
        InferenceInput, InferenceOutput, PipelineResult, PreparedContextSnapshot, PreparedInference,
    },
};

#[cfg(test)]
mod tests {
    use super::{
        always_include_operational_tools, build_retry_request, deterministic_guard_fallback,
        is_task_like_turn,
    };
    use crate::api::routes::agent::decomposition::DelegationProvenance;
    use crate::api::routes::agent::guard_registry::{GuardContext, GuardId};
    use crate::api::routes::agent::intent_registry::Intent;
    use roboticus_llm::format::{ToolDefinition, UnifiedMessage, UnifiedRequest};

    #[test]
    fn operational_tools_remain_visible_to_task_workflow() {
        let tools = always_include_operational_tools();
        for required in [
            "get_memory_stats",
            "get_runtime_context",
            "list-subagent-roster",
            "list-available-skills",
            "compose-subagent",
            "compose-skill",
            "orchestrate-subagents",
        ] {
            assert!(
                tools.iter().any(|tool| tool == required),
                "missing required always-include tool: {required}"
            );
        }
    }

    fn test_prepared_inference() -> super::PreparedInference {
        super::PreparedInference {
            model: "test-model".to_string(),
            request: UnifiedRequest {
                model: "test-model".to_string(),
                messages: vec![UnifiedMessage {
                    role: "user".to_string(),
                    content: "Do the task".to_string(),
                    parts: None,
                }],
                max_tokens: Some(512),
                temperature: None,
                system: None,
                quality_target: None,
                tools: vec![ToolDefinition {
                    name: "compose-subagent".to_string(),
                    description: "compose".to_string(),
                    parameters: serde_json::json!({"type":"object"}),
                }],
            },
            cache_hash: "cache".to_string(),
            system_prompt_hash: "system".to_string(),
            query_embedding: None,
            intents: vec![Intent::Execution],
            previous_assistant: None,
            context_snapshot: super::PreparedContextSnapshot {
                complexity_level: "medium".to_string(),
                token_budget: 0,
                system_prompt_tokens: 0,
                memory_tokens: 0,
                history_tokens: 0,
                history_depth: 0,
                memory_tiers_json: None,
                retrieved_memories_json: None,
            },
            retrieval_metrics: roboticus_agent::retrieval::RetrievalMetrics::default(),
            tool_search_stats: None,
            delegated_execution_result: None,
        }
    }

    fn test_guard_context<'a>(tool_results: &'a [(String, String)]) -> GuardContext<'a> {
        let provenance = Box::leak(Box::new(DelegationProvenance::default()));
        GuardContext {
            user_prompt: "Do the task",
            intents: &[Intent::Execution],
            tool_results,
            agent_name: "Duncan",
            resolved_model: "test-model",
            delegation_provenance: provenance,
            previous_assistant: None,
            prior_assistant_messages: &[],
            semantic_guard_scores: std::collections::HashMap::new(),
            subagent_names: vec![],
        }
    }

    #[test]
    fn task_deferral_retry_preserves_tools_and_state() {
        let prepared = test_prepared_inference();
        let tool_results = vec![(
            "list-subagent-roster".to_string(),
            "{\"subagent_count\":0,\"subagents\":[]}".to_string(),
        )];
        let ctx = test_guard_context(&tool_results);

        let retry = build_retry_request(GuardId::TaskDeferral, &prepared, &ctx);

        assert_eq!(retry.tools.len(), prepared.request.tools.len());
        let directive = retry.messages.last().expect("retry directive");
        assert!(directive.content.contains("Prior tool state:"));
        assert!(directive.content.contains("subagent_count"));
        assert!(directive.content.contains("compose what is missing"));
    }

    #[test]
    fn output_contract_retry_uses_prior_execution_state() {
        let prepared = test_prepared_inference();
        let tool_results = vec![(
            "list_directory".to_string(),
            "FIRMWARE.toml Governance.md Ledger.md".to_string(),
        )];
        let ctx = test_guard_context(&tool_results);

        let retry = build_retry_request(GuardId::OutputContract, &prepared, &ctx);

        assert!(retry.tools.is_empty());
        let directive = retry.messages.last().expect("retry directive");
        assert!(
            directive
                .content
                .contains("exact requested output contract")
        );
        assert!(directive.content.contains("Prior execution state:"));
        assert!(directive.content.contains("no headings"));
    }

    #[test]
    fn low_value_retry_does_not_preserve_tools() {
        let prepared = test_prepared_inference();
        let empty_results: Vec<(String, String)> = Vec::new();
        let ctx = test_guard_context(&empty_results);

        let retry = build_retry_request(GuardId::LowValueParroting, &prepared, &ctx);

        assert!(retry.tools.is_empty());
    }

    #[test]
    fn internal_protocol_retry_preserves_tools() {
        let prepared = test_prepared_inference();
        let empty_results: Vec<(String, String)> = Vec::new();
        let ctx = test_guard_context(&empty_results);

        let retry = build_retry_request(GuardId::InternalProtocol, &prepared, &ctx);

        assert_eq!(retry.tools.len(), prepared.request.tools.len());
        let directive = retry.messages.last().expect("retry directive");
        assert!(directive.content.contains("executable tool call"));
        assert!(
            directive
                .content
                .contains("Do not narrate internal protocol")
        );
    }

    #[test]
    fn task_like_turn_detects_operational_work() {
        assert!(is_task_like_turn(&[Intent::Execution]));
        assert!(is_task_like_turn(&[Intent::Delegation]));
        assert!(!is_task_like_turn(&[Intent::CapabilitySummary]));
    }

    #[test]
    fn output_contract_guard_fallback_salvages_exact_bullets() {
        let rescued = deterministic_guard_fallback(
            GuardId::OutputContract,
            "## Summary\n- one\n- two\n- three",
            &[],
            "Return exactly three bullet points.",
            "Duncan",
        );
        assert_eq!(rescued, "- one\n- two\n- three");
    }

    #[test]
    fn internal_protocol_guard_fallback_salvages_delegate_outputs_into_exact_bullets() {
        let tool_results = vec![
            (
                "delegate-subagent".to_string(),
                "delegated_subagent=revenue_strategist\nsubtask 1 -> revenue_strategist\n1. Retainer Micro-Agency\n2. Finance Flash Newsletter".to_string(),
            ),
            (
                "delegate-subagent".to_string(),
                "delegated_subagent=saas_ideator\nsubtask 1 -> saas_ideator\n**Product:** ScopeGuard".to_string(),
            ),
        ];
        let rescued = deterministic_guard_fallback(
            GuardId::InternalProtocol,
            "",
            &tool_results,
            "Return exactly three bullet points: two revenue ideas and one SaaS idea.",
            "Duncan",
        );
        assert_eq!(
            rescued,
            "- Retainer Micro-Agency\n- Finance Flash Newsletter\n- Product: ScopeGuard"
        );
    }

    #[test]
    fn task_deferral_guard_fallback_surfaces_runtime_boundary_blocker() {
        let tool_results = vec![(
            "get_runtime_context".to_string(),
            "{\"workspace\":\"/Users/jmachen/.roboticus/workspace\"}".to_string(),
        )];
        let rescued = deterministic_guard_fallback(
            GuardId::TaskDeferral,
            "I'll inspect the runtime context first, then read the forbidden file.",
            &tool_results,
            "This is a task. Inspect what you need, then read /root/forbidden.txt and return exactly one bullet point with the result.",
            "Duncan",
        );
        assert_eq!(
            rescued,
            "- Blocked: /root/forbidden.txt is outside my allowed runtime boundaries in this environment, so I cannot read it directly."
        );
    }

    #[test]
    fn generic_degraded_fallback_is_detected() {
        assert!(super::guard_fallback::is_generic_degraded_fallback(
            "Duncan here. The prior generation degraded. I am returning a concrete fallback: state the exact outcome format you want."
        ));
        assert!(super::guard_fallback::is_generic_degraded_fallback(
            "I wasn't able to produce a good response to that. Could you rephrase or try again?"
        ));
        assert!(!super::guard_fallback::is_generic_degraded_fallback(
            "- concrete result"
        ));
    }
}
