//! Python bindings for tool_node helpers (create_tool_node, store_tool_calls, tools_condition)

use pyo3::prelude::*;
use std::sync::Arc;

use flowgentra_ai::core::state::PlainState;
use flowgentra_ai::core::state_graph::tool_node::{
    create_tool_node, store_tool_calls, tools_condition, ToolExecutorFn,
};
use flowgentra_ai::core::state_graph::node::Node;

use crate::state::PyPlainState;
use crate::llm::PyMessage;

/// Create a tool execution node from a Python callable.
///
/// The callable signature is: `(tool_name: str, arguments: dict) -> str`
/// It should return the tool result as a string, or raise an exception on error.
///
/// Example:
///     def executor(name, args):
///         if name == "calculator":
///             return str(args["a"] + args["b"])
///         raise ValueError(f"Unknown tool: {name}")
///
///     tool_node = create_tool_node_py(executor)
#[pyfunction]
pub fn py_create_tool_node(executor: PyObject) -> PyToolNode {
    let func = Python::with_gil(|py| executor.clone_ref(py));

    let rust_executor: ToolExecutorFn = Arc::new(move |name: String, args: serde_json::Value| {
        let func = Python::with_gil(|py| func.clone_ref(py));
        Box::pin(async move {
            Python::with_gil(|py| -> Result<String, String> {
                let py_args = crate::json_to_py(py, &args).map_err(|e| format!("{}", e))?;
                let result = func
                    .call1(py, (name.clone(), py_args))
                    .map_err(|e| format!("Tool executor error: {}", e))?;
                let s: String = result
                    .extract(py)
                    .map_err(|e| format!("Tool executor must return str: {}", e))?;
                Ok(s)
            })
        })
    });

    let node = create_tool_node(rust_executor);
    PyToolNode { inner: node }
}

/// A tool execution node for use with StateGraphBuilder.
///
/// Created via `create_tool_node()`. Pass to `StateGraphBuilder.add_tool_node()`.
#[pyclass(name = "ToolNode")]
pub struct PyToolNode {
    #[allow(dead_code)]
    pub(crate) inner: Arc<dyn Node<PlainState>>,
}

/// Extract tool calls from an LLM response and store them in a PlainState.
///
/// Example:
///     response = client.chat_with_tools(messages, tools)
///     state = store_tool_calls_py(state, response)
#[pyfunction]
pub fn py_store_tool_calls(state: &PyPlainState, message: &PyMessage) -> PyPlainState {
    let new_state = store_tool_calls(state.inner.clone(), &message.inner);
    PyPlainState { inner: new_state }
}

/// Get the name of the tool node or "__end__" based on whether tool_calls exist.
///
/// This is a simple helper — returns tool_node_name if state has tool_calls,
/// or "__end__" otherwise. Use in a Python router function.
///
/// Example:
///     def tools_router(state):
///         return check_tools_condition(state, "tools")
///     builder.add_conditional_edge("agent", tools_router)
#[pyfunction]
pub fn py_check_tools_condition(state: &PyPlainState, tool_node_name: &str) -> PyResult<String> {
    let router = tools_condition(tool_node_name);
    router(&state.inner)
        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{}", e)))
}
