use std::sync::Arc;
use std::time::Instant;

use crate::plugin::ToolResultHookContext;
use crate::{PreparedToolCall, ProgressSender, ToolContext, ToolFailureClass};

use super::context::{ToolDispatchContext, ToolDispatchOutcome, outcome, runtime_failure};
use super::directives::apply_after_tool_directives;
use super::preparation::resolve_callable_manifest;
use super::retry::execute_tool_call;

pub(crate) async fn dispatch_prepared_tool_call_with_execution_context<'run>(
    context: &ToolDispatchContext<'run>,
    prepared: PreparedToolCall,
    progress: Option<&ProgressSender>,
    tool_context: ToolContext<'run>,
) -> ToolDispatchOutcome {
    let tool_name = prepared.tool_name.clone();
    let args = prepared.args.clone();
    let Some(manifest) = resolve_callable_manifest(context, &tool_name) else {
        return outcome(
            tool_name,
            args,
            runtime_failure(
                ToolFailureClass::Unavailable,
                "tool_unavailable",
                "Tool is unavailable in this session",
            ),
            0,
        );
    };

    let tool_start = Instant::now();
    let result = Box::pin(execute_tool_call(
        context,
        &manifest,
        &prepared,
        progress,
        tool_context.with_prepared_payload(prepared.prepared_payload.clone()),
    ))
    .await;
    let duration_ms = tool_start.elapsed().as_millis() as u64;

    let result = match context
        .plugins
        .after_tool_call(ToolResultHookContext::new(
            context.session_id.clone(),
            tool_name.clone(),
            args.clone(),
            result.clone(),
            duration_ms,
            context.turn_context.clone(),
            Arc::clone(&context.sessions),
        ))
        .await
    {
        Ok(directives) => apply_after_tool_directives(context, result, directives).await,
        Err(err) => runtime_failure(
            ToolFailureClass::Internal,
            "after_tool_call_failed",
            err.to_string(),
        ),
    };

    outcome(tool_name, args, result, duration_ms)
}
