//! Python bindings for ToolRegistry and related types

use pyo3::prelude::*;

use flowgentra_ai::core::tools::{ToolRegistry, JsonSchema};

use crate::error::to_py_err;
use crate::get_runtime;
use crate::{json_to_py, py_to_json};

// ─── PyJsonSchema ──────────────────────────────────────────────────────────

/// JSON Schema for tool input/output validation.
///
/// Example:
///     schema = JsonSchema.object()
///     schema.description = "Calculator input"
#[pyclass(name = "JsonSchema")]
#[derive(Clone)]
pub struct PyJsonSchema {
    pub(crate) inner: JsonSchema,
}

#[pymethods]
impl PyJsonSchema {
    /// Create an object schema.
    #[staticmethod]
    fn object() -> Self {
        PyJsonSchema { inner: JsonSchema::object() }
    }

    /// Create a string schema.
    #[staticmethod]
    fn string() -> Self {
        PyJsonSchema { inner: JsonSchema::string() }
    }

    /// Create a number schema.
    #[staticmethod]
    fn number() -> Self {
        PyJsonSchema { inner: JsonSchema::number() }
    }

    /// Create an integer schema.
    #[staticmethod]
    fn integer() -> Self {
        PyJsonSchema { inner: JsonSchema::integer() }
    }

    /// Create a boolean schema.
    #[staticmethod]
    fn boolean() -> Self {
        PyJsonSchema { inner: JsonSchema::boolean() }
    }

    /// Create an array schema.
    #[staticmethod]
    fn array() -> Self {
        PyJsonSchema { inner: JsonSchema::array() }
    }

    /// Set description.
    fn with_description(&mut self, desc: &str) {
        self.inner.description = Some(desc.to_string());
    }

    /// Set required fields (for object schemas).
    fn with_required(&mut self, fields: Vec<String>) {
        self.inner.required = Some(fields);
    }

    /// Validate a value against this schema.
    fn validate(&self, value: &Bound<'_, PyAny>) -> PyResult<()> {
        let val = py_to_json(value)?;
        self.inner.validate(&val).map_err(to_py_err)
    }

    #[getter]
    fn schema_type(&self) -> String {
        self.inner.schema_type.clone()
    }

    #[getter]
    fn description(&self) -> Option<String> {
        self.inner.description.clone()
    }

    fn __repr__(&self) -> String {
        format!("JsonSchema(type='{}')", self.inner.schema_type)
    }
}

// ─── PyToolRegistry ────────────────────────────────────────────────────────

/// Central registry for managing tools.
///
/// Example:
///     registry = ToolRegistry.with_builtins()
///     print(registry.list_names())
///     result = registry.call_tool("calculator", {"operation": "add", "a": 2, "b": 3})
#[pyclass(name = "ToolRegistry")]
pub struct PyToolRegistry {
    inner: ToolRegistry,
}

#[pymethods]
impl PyToolRegistry {
    /// Create an empty tool registry.
    #[new]
    fn new() -> Self {
        PyToolRegistry { inner: ToolRegistry::new() }
    }

    /// Create a registry with all built-in tools pre-registered.
    #[staticmethod]
    fn with_builtins() -> Self {
        PyToolRegistry { inner: ToolRegistry::with_builtins() }
    }

    /// Call a tool by name with JSON input.
    fn call_tool(&self, py: Python<'_>, name: &str, input: &Bound<'_, PyAny>) -> PyResult<PyObject> {
        let val = py_to_json(input)?;
        let rt = get_runtime();
        let result = rt
            .block_on(self.inner.call_tool(name, val))
            .map_err(to_py_err)?;
        json_to_py(py, &result)
    }

    /// Validate tool input without calling it.
    fn validate_input(&self, name: &str, input: &Bound<'_, PyAny>) -> PyResult<()> {
        let val = py_to_json(input)?;
        self.inner.validate_input(name, &val).map_err(to_py_err)
    }

    /// Get all tool definition names.
    fn list_names(&self) -> Vec<String> {
        self.inner
            .list_definitions()
            .into_iter()
            .map(|d| d.name)
            .collect()
    }

    /// Get the number of registered tools.
    fn __len__(&self) -> usize {
        self.inner.len()
    }

    fn __repr__(&self) -> String {
        format!("ToolRegistry(tools={})", self.inner.len())
    }
}
