//! Observability hooks for the ReAct agent loop.
//!
//! Hooks allow injecting custom behavior (logging, tracing, filtering, termination)
//! at each step of the reasoning loop without coupling to the core agent logic.
//!
//! Inspired by rig's `PromptHook` trait.
//!
//! # Example
//!
//! ```ignore
//! #[derive(Clone)]
//! struct MyHook;
//!
//! impl Hook for MyHook {
//!     async fn on_thought(&self, step: usize, thought: &str) -> HookAction {
//!         tracing::info!("[step {}] thinking: {}", step, thought);
//!         HookAction::Continue
//!     }
//! }
//!
//! let agent = ReactAgent::new(prompt, tools, config).with_hook(MyHook);
//! ```

use async_trait::async_trait;

/// Controls whether the agent loop continues after a hook callback.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HookAction {
    /// Continue processing normally.
    Continue,
    /// Skip the current step and continue.
    Skip,
    /// Terminate the loop immediately with the given reason.
    Terminate(&'static str),
}

/// Controls behavior after a tool call hook callback.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ToolCallAction {
    /// Execute the tool normally.
    Continue,
    /// Skip tool execution and inject a custom result.
    Skip(String),
    /// Terminate the loop with the given reason.
    Terminate(&'static str),
}

/// Default no-op hook that does nothing.
#[derive(Debug, Clone, Copy, Default)]
pub struct NoopHook;

impl Hook for NoopHook {}

impl Hook for () {}

/// A hook that logs everything to stderr using `eprintln`.
/// No external dependencies required.
#[derive(Debug, Clone, Copy, Default)]
pub struct TracingHook;

impl TracingHook {
    pub fn new() -> Self {
        Self
    }
}

#[async_trait]
impl Hook for TracingHook {
    async fn on_thought(&self, step: usize, thought: &str) -> HookAction {
        eprintln!("[step {}] thought: {}", step, thought);
        HookAction::Continue
    }

    async fn on_tool_call(&self, step: usize, name: &str, args_json: &str) -> ToolCallAction {
        eprintln!("[step {}] tool_call: {}({})", step, name, args_json);
        ToolCallAction::Continue
    }

    async fn on_observation(&self, step: usize, observation: &str) -> HookAction {
        eprintln!("[step {}] observation: {}", step, observation);
        HookAction::Continue
    }

    async fn on_answer(&self, step: usize, answer: &str) -> HookAction {
        eprintln!("[step {}] answer: {}", step, answer);
        HookAction::Continue
    }
}

/// Hook trait for observing and controlling the ReAct agent loop.
///
/// Implement this trait to inject custom behavior at each step:
/// - Log thoughts, tool calls, observations, and final answers
/// - Filter or redact sensitive data
/// - Dynamically terminate the loop based on content
/// - Inject custom tool results (e.g., for testing or sandboxing)
///
/// All methods have default no-op implementations, so you only need to
/// override the ones you care about.
///
/// The hook is called synchronously during the agent loop. Keep hook
/// callbacks fast — avoid blocking I/O. For heavy work, spawn a task
/// and return immediately.
#[async_trait]
pub trait Hook: Send + Sync {
    /// Called when the agent emits a thought/reasoning step.
    ///
    /// Return `HookAction::Terminate` to stop the loop early.
    async fn on_thought(&self, _step: usize, _thought: &str) -> HookAction {
        HookAction::Continue
    }

    /// Called just before a tool is executed.
    ///
    /// Return `ToolCallAction::Skip(result)` to skip execution and inject `result` instead.
    /// Return `ToolCallAction::Terminate` to stop the loop without executing the tool.
    async fn on_tool_call(&self, _step: usize, _name: &str, _args_json: &str) -> ToolCallAction {
        ToolCallAction::Continue
    }

    /// Called after a tool returns an observation.
    async fn on_observation(&self, _step: usize, _observation: &str) -> HookAction {
        HookAction::Continue
    }

    /// Called when the agent produces a final answer.
    async fn on_answer(&self, _step: usize, _answer: &str) -> HookAction {
        HookAction::Continue
    }
}
