//! Agent worker for Athen.
//!
//! Executes tasks through LLM-driven steps, calling tools as needed.
//! This crate provides the core execution loop, audit logging,
//! timeout guards, and resource monitoring.

pub mod auditor;
pub mod estimator;
pub mod executor;
pub mod reminders;
pub mod resource;
pub mod runtimes;
pub mod timeout;
pub mod tool_error_hints;
pub mod tool_grouping;
pub mod tool_truncation;
pub mod toolbox;
pub mod tools;
pub mod tools_doc;

pub use auditor::InMemoryAuditor;
pub use executor::DefaultExecutor;
pub use resource::DefaultResourceMonitor;
pub use timeout::DefaultTimeoutGuard;
pub use toolbox::{InstalledPackage, Runtime as ToolboxRuntime, RuntimeProbe, ToolboxManifest};
pub use tools::{
    detect_shell_kind, kill_all_spawned, kill_spawned_pid, EmailSendApprovalGate, EmailSendSummary,
    OwnerDestinationCheck, ShellToolRegistry, SpawnPersistenceHook, SpawnedProcess,
    SpawnedProcessMap, ToolboxApprovalGate,
};

use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::time::Duration;

use athen_core::error::{AthenError, Result};
use athen_core::llm::ChatMessage;
use athen_core::traits::agent::StepAuditor;
use athen_core::traits::llm::LlmRouter;
use athen_core::traits::reminder::SystemReminderBuilder;
use athen_core::traits::tool::ToolRegistry;

/// Builder for constructing a [`DefaultExecutor`] with sensible defaults.
pub struct AgentBuilder {
    llm_router: Option<Box<dyn LlmRouter>>,
    tool_registry: Option<Box<dyn ToolRegistry>>,
    auditor: Option<Box<dyn StepAuditor>>,
    max_steps: u32,
    timeout: Duration,
    context_messages: Vec<ChatMessage>,
    stream_sender: Option<tokio::sync::mpsc::UnboundedSender<String>>,
    cancel_flag: Option<Arc<AtomicBool>>,
    tool_doc_path: Option<PathBuf>,
    active_profile: Option<athen_core::agent_profile::ResolvedAgentProfile>,
    toolbox_info: Option<toolbox::ToolboxPromptInfo>,
    shell_kind: Option<&'static str>,
    autonomous_mode: bool,
    initial_user_images: Vec<athen_core::llm::ImageInput>,
    external_system_suffix: Option<String>,
    default_temperature: Option<f32>,
    identity_block: Option<String>,
    endpoints_block: Option<String>,
    reminder_builder: Option<Arc<dyn SystemReminderBuilder>>,
    auto_reminders: bool,
    default_reasoning_effort: athen_core::llm::ReasoningEffort,
    default_tier: athen_core::llm::ModelProfile,
}

impl AgentBuilder {
    /// Create a new builder with default values.
    ///
    /// Defaults:
    /// - `max_steps`: 50
    /// - `timeout`: 5 minutes
    /// - `auditor`: [`InMemoryAuditor`]
    pub fn new() -> Self {
        Self {
            llm_router: None,
            tool_registry: None,
            auditor: None,
            max_steps: 50,
            timeout: Duration::from_secs(300),
            context_messages: Vec::new(),
            stream_sender: None,
            cancel_flag: None,
            tool_doc_path: None,
            active_profile: None,
            toolbox_info: None,
            shell_kind: None,
            autonomous_mode: false,
            initial_user_images: Vec::new(),
            external_system_suffix: None,
            default_temperature: None,
            identity_block: None,
            endpoints_block: None,
            reminder_builder: None,
            auto_reminders: false,
            default_reasoning_effort: athen_core::llm::ReasoningEffort::Default,
            default_tier: athen_core::llm::ModelProfile::Fast,
        }
    }

    /// Attach a custom `SystemReminderBuilder` (trajectory-aware /
    /// conditional reminders). Takes precedence over
    /// `enable_default_reminders`. For the standard per-profile
    /// re-anchor, prefer `enable_default_reminders(true)` so the
    /// executor builds the default impl from its own state.
    pub fn reminder_builder(mut self, builder: Arc<dyn SystemReminderBuilder>) -> Self {
        self.reminder_builder = Some(builder);
        self
    }

    /// Turn on the default per-profile reminder. The executor builds a
    /// `ProfileSystemReminderBuilder` lazily at the start of `execute()`
    /// from the active profile + post-filter tool list + identity
    /// block, and injects its output every 3rd iteration. Off by
    /// default to preserve byte-identical behavior for tests; production
    /// call sites should flip it on.
    pub fn enable_default_reminders(mut self, value: bool) -> Self {
        self.auto_reminders = value;
        self
    }

    /// Inject a pre-rendered identity block (the user's hand-maintained
    /// personality / rules / knowledge / team statements, profile-filtered
    /// upstream). The host (athen-app) reads the identity store and
    /// formats this string; the executor splices it as-is into the static
    /// system header. Empty / `None` reproduces today's prompt byte-for-byte.
    pub fn identity_block(mut self, block: Option<String>) -> Self {
        self.identity_block = block.filter(|s| !s.trim().is_empty());
        self
    }

    /// Inject a pre-rendered registered-HTTP-endpoints block — the
    /// user's enabled cloud APIs (ElevenLabs, Jina, Firecrawl, …),
    /// formatted as one line per endpoint with name, base URL, and a
    /// short blurb. Pinned in the static prefix so the agent always
    /// knows which APIs are pre-configured (with credentials), instead
    /// of reflexively trying to install Python SDKs and shell-out.
    ///
    /// The host (athen-app) reads the endpoint store, matches against
    /// the preset library to derive the blurb, and ships the rendered
    /// markdown in. Empty / `None` reproduces today's prompt
    /// byte-for-byte. The executor itself gates the section on
    /// `http_request` being in the agent's tool surface — profiles
    /// without that tool get zero bytes here regardless.
    pub fn endpoints_block(mut self, block: Option<String>) -> Self {
        self.endpoints_block = block.filter(|s| !s.trim().is_empty());
        self
    }

    /// Sampling temperature for the main agent loop. `None` keeps the
    /// historical 0.7 default; `Some(t)` overrides it. The cheap
    /// completion-judge and summarization helpers keep their own
    /// hardcoded settings so determinism guarantees there don't drift.
    pub fn default_temperature(mut self, t: Option<f32>) -> Self {
        self.default_temperature = t;
        self
    }

    /// Cross-provider reasoning-effort knob applied to the main agent
    /// loop's LLM requests. `Default` (the default) omits the field on
    /// the wire so the provider applies its built-in default. The cheap
    /// helpers (completion-judge, summarisation) deliberately stay at
    /// `Default` — burning reasoning tokens on a one-word verdict is
    /// pure waste.
    pub fn default_reasoning_effort(mut self, effort: athen_core::llm::ReasoningEffort) -> Self {
        self.default_reasoning_effort = effort;
        self
    }

    /// Resolved `ModelProfile` for the main agent loop. The host computes
    /// this via `state::resolve_effective_tier_for_arc` (arc override >
    /// task complexity > static call-site label `Fast`) and threads it
    /// through. The cheap helpers (completion judge, etc.) keep their
    /// hardcoded tiers regardless.
    pub fn default_tier(mut self, tier: athen_core::llm::ModelProfile) -> Self {
        self.default_tier = tier;
        self
    }

    /// Set host-supplied volatile content (memory recall, attachment
    /// summaries, compaction state) to ride along inside the leading
    /// system message. Use this instead of pushing mid-stream
    /// `Role::System` messages — those are rejected by strict chat
    /// templates (Qwen, Llama). Empty / `None` clears.
    pub fn external_system_suffix(mut self, suffix: Option<String>) -> Self {
        self.external_system_suffix = suffix.filter(|s| !s.is_empty());
        self
    }

    /// Attach images to the first user turn. Vision-capable LLMs see
    /// them as `MessageContent::Multimodal`; non-capable providers
    /// reject the request with a clear error.
    pub fn initial_user_images(mut self, images: Vec<athen_core::llm::ImageInput>) -> Self {
        self.initial_user_images = images;
        self
    }

    /// Run this executor in autonomous mode — i.e. driven by a sense
    /// event with no live user. Defaults to `false` to preserve today's
    /// behavior for user-driven `send_message` flows.
    pub fn autonomous_mode(mut self, value: bool) -> Self {
        self.autonomous_mode = value;
        self
    }

    /// Inject pre-fetched toolbox runtime probe + manifest summary so
    /// the built executor's system prompt can surface what the agent
    /// already has installed and which language runtimes are available.
    /// Without this, the toolbox prompt slot is omitted.
    pub fn toolbox_info(mut self, info: toolbox::ToolboxPromptInfo) -> Self {
        self.toolbox_info = Some(info);
        self
    }

    /// Tell the agent which shell `shell_execute` actually routes
    /// commands through (typically `ShellToolRegistry::shell_kind()`).
    /// Pass `"nushell"`, `"sh"`, or `"cmd"`. Without this the SHELL
    /// ENVIRONMENT prompt slot is omitted and the agent will assume
    /// bash, generating commands that silently fail under nushell or
    /// Windows cmd.
    pub fn shell_kind(mut self, kind: &'static str) -> Self {
        self.shell_kind = Some(kind);
        self
    }

    /// Run this executor under the given resolved agent profile. The
    /// profile's persona templates replace the hardcoded "You are Athen"
    /// identity; its `tool_selection` filters the tool surface. Omit (or
    /// pass the seeded default profile) to get today's behavior.
    pub fn active_profile(
        mut self,
        profile: athen_core::agent_profile::ResolvedAgentProfile,
    ) -> Self {
        self.active_profile = Some(profile);
        self
    }

    /// Set the LLM router.
    pub fn llm_router(mut self, router: Box<dyn LlmRouter>) -> Self {
        self.llm_router = Some(router);
        self
    }

    /// Set the tool registry.
    pub fn tool_registry(mut self, registry: Box<dyn ToolRegistry>) -> Self {
        self.tool_registry = Some(registry);
        self
    }

    /// Set a custom step auditor. Defaults to [`InMemoryAuditor`].
    pub fn auditor(mut self, auditor: Box<dyn StepAuditor>) -> Self {
        self.auditor = Some(auditor);
        self
    }

    /// Set the maximum number of steps before the executor gives up.
    pub fn max_steps(mut self, n: u32) -> Self {
        self.max_steps = n;
        self
    }

    /// Set the maximum execution time for a task.
    pub fn timeout(mut self, d: Duration) -> Self {
        self.timeout = d;
        self
    }

    /// Set context messages to prepend to the conversation.
    ///
    /// These messages represent prior conversation history and are inserted
    /// before the current task's user message, giving the agent memory of
    /// earlier exchanges within the session.
    pub fn context_messages(mut self, messages: Vec<ChatMessage>) -> Self {
        self.context_messages = messages;
        self
    }

    /// Set a channel sender for streaming text chunks from the final LLM response.
    ///
    /// When provided, the executor will use `LlmRouter::route_streaming()` and
    /// forward each text delta through this sender, enabling progressive
    /// rendering in the UI.
    pub fn stream_sender(mut self, sender: tokio::sync::mpsc::UnboundedSender<String>) -> Self {
        self.stream_sender = Some(sender);
        self
    }

    /// Set a cancellation flag that the executor checks at the top of each
    /// loop iteration and between tool calls. Setting the flag to `true`
    /// causes the executor to return immediately with a "cancelled" result.
    pub fn cancel_flag(mut self, flag: Arc<AtomicBool>) -> Self {
        self.cancel_flag = Some(flag);
        self
    }

    /// Directory containing per-group markdown references (e.g. `calendar.md`,
    /// `files.md`). When set, the system prompt instructs the agent to
    /// `read` the relevant group file for full schemas of any tool whose
    /// arguments it doesn't already know.
    pub fn tool_doc_dir(mut self, dir: PathBuf) -> Self {
        self.tool_doc_path = Some(dir);
        self
    }

    /// Build the [`DefaultExecutor`].
    ///
    /// Returns an error if `llm_router` or `tool_registry` are not set.
    pub fn build(self) -> Result<DefaultExecutor> {
        let llm_router = self
            .llm_router
            .ok_or_else(|| AthenError::Config("llm_router is required".to_string()))?;

        let tool_registry = self
            .tool_registry
            .ok_or_else(|| AthenError::Config("tool_registry is required".to_string()))?;

        let auditor: Box<dyn StepAuditor> = self
            .auditor
            .unwrap_or_else(|| Box::new(InMemoryAuditor::new()));

        let mut executor = DefaultExecutor::new(
            llm_router,
            tool_registry,
            auditor,
            self.max_steps,
            self.timeout,
            self.context_messages,
        );

        if let Some(sender) = self.stream_sender {
            executor.set_stream_sender(sender);
        }

        if let Some(flag) = self.cancel_flag {
            executor.set_cancel_flag(flag);
        }

        if let Some(path) = self.tool_doc_path {
            executor.set_tool_doc_dir(path);
        }

        if let Some(profile) = self.active_profile {
            executor.set_active_profile(profile);
        }

        if let Some(info) = self.toolbox_info {
            executor.set_toolbox_info(info);
        }

        if let Some(kind) = self.shell_kind {
            executor.set_shell_kind(kind);
        }

        if self.autonomous_mode {
            executor.set_autonomous_mode(true);
        }

        if !self.initial_user_images.is_empty() {
            executor.set_initial_user_images(self.initial_user_images);
        }

        if let Some(suffix) = self.external_system_suffix {
            executor.set_external_system_suffix(Some(suffix));
        }

        if self.default_temperature.is_some() {
            executor.set_default_temperature(self.default_temperature);
        }

        if self.identity_block.is_some() {
            executor.set_identity_block(self.identity_block);
        }

        if self.endpoints_block.is_some() {
            executor.set_endpoints_block(self.endpoints_block);
        }

        if let Some(rb) = self.reminder_builder {
            executor.set_reminder_builder(rb);
        }

        if self.auto_reminders {
            executor.enable_default_reminders(true);
        }

        executor.set_default_reasoning_effort(self.default_reasoning_effort);
        executor.set_default_tier(self.default_tier);

        Ok(executor)
    }
}

impl Default for AgentBuilder {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use async_trait::async_trait;
    use athen_core::llm::{BudgetStatus, LlmRequest, LlmResponse};
    use athen_core::tool::{ToolDefinition, ToolResult as CoreToolResult};

    struct DummyRouter;

    #[async_trait]
    impl LlmRouter for DummyRouter {
        async fn route(&self, _request: &LlmRequest) -> Result<LlmResponse> {
            unimplemented!()
        }
        async fn budget_remaining(&self) -> Result<BudgetStatus> {
            unimplemented!()
        }
    }

    struct DummyRegistry;

    #[async_trait]
    impl ToolRegistry for DummyRegistry {
        async fn list_tools(&self) -> Result<Vec<ToolDefinition>> {
            Ok(vec![])
        }
        async fn call_tool(&self, _name: &str, _args: serde_json::Value) -> Result<CoreToolResult> {
            unimplemented!()
        }
    }

    #[test]
    fn test_builder_missing_router() {
        let result = AgentBuilder::new()
            .tool_registry(Box::new(DummyRegistry))
            .build();
        assert!(result.is_err());
    }

    #[test]
    fn test_builder_missing_registry() {
        let result = AgentBuilder::new()
            .llm_router(Box::new(DummyRouter))
            .build();
        assert!(result.is_err());
    }

    #[test]
    fn test_builder_success() {
        let result = AgentBuilder::new()
            .llm_router(Box::new(DummyRouter))
            .tool_registry(Box::new(DummyRegistry))
            .max_steps(100)
            .timeout(Duration::from_secs(600))
            .build();
        assert!(result.is_ok());
    }

    #[test]
    fn test_builder_defaults() {
        let result = AgentBuilder::new()
            .llm_router(Box::new(DummyRouter))
            .tool_registry(Box::new(DummyRegistry))
            .build();
        assert!(result.is_ok());
    }
}
