//! Procedure registry and trait definitions.

use std::collections::HashMap;
use crate::graph::GraphBackend;
use crate::result::{CypherValue, Record};
use crate::error::ExecutionResult;

/// Procedure execution mode (READ, WRITE, SCHEMA, DBMS).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcedureMode {
    /// Read-only procedure (doesn't modify graph).
    Read,
    /// Write procedure (may modify graph).
    Write,
    /// Schema procedure (modifies schema/indexes).
    Schema,
    /// DBMS procedure (system management).
    Dbms,
}

/// Parameter information for procedure signature.
#[derive(Debug, Clone)]
pub struct ParameterInfo {
    pub name: String,
    pub type_name: String,
    pub default: Option<CypherValue>,
}

/// Yield column information for procedure signature.
#[derive(Debug, Clone)]
pub struct YieldInfo {
    pub name: String,
    pub type_name: String,
}

/// Procedure metadata and signature.
#[derive(Debug, Clone)]
pub struct ProcedureInfo {
    pub name: String,
    pub description: String,
    pub mode: ProcedureMode,
    pub parameters: Vec<ParameterInfo>,
    pub yields: Vec<YieldInfo>,
}

/// Context passed to procedure during execution.
pub struct ProcedureContext<'a, G: GraphBackend> {
    /// Reference to the graph backend.
    pub graph: &'a G,
    /// Procedure arguments.
    pub parameters: Vec<CypherValue>,
}

/// Trait for stored procedures.
///
/// Implement this trait to create custom procedures that can be called
/// via Cypher's CALL clause.
pub trait Procedure<G: GraphBackend>: Send + Sync {
    /// Get procedure metadata and signature.
    fn info(&self) -> ProcedureInfo;

    /// Execute the procedure with given context.
    ///
    /// Returns a vector of records matching the yield specification.
    fn call(&self, ctx: &ProcedureContext<G>) -> ExecutionResult<Vec<Record>>;
}

/// Registry for stored procedures.
///
/// Manages procedure registration and execution.
pub struct ProcedureRegistry<G: GraphBackend> {
    procedures: HashMap<String, Box<dyn Procedure<G>>>,
}

impl<G: GraphBackend> ProcedureRegistry<G> {
    /// Create a new empty procedure registry.
    pub fn new() -> Self {
        Self {
            procedures: HashMap::new(),
        }
    }

    /// Register a procedure.
    ///
    /// The procedure name is taken from the procedure's info().
    pub fn register(&mut self, procedure: Box<dyn Procedure<G>>) {
        let name = procedure.info().name.clone();
        self.procedures.insert(name, procedure);
    }

    /// Call a procedure by name.
    ///
    /// Returns an error if the procedure is not found.
    pub fn call(
        &self,
        name: &str,
        args: Vec<CypherValue>,
        graph: &G,
    ) -> ExecutionResult<Vec<Record>> {
        let proc = self.procedures.get(name).ok_or_else(|| {
            crate::error::ExecutionError::ProcedureNotFound(name.to_string())
        })?;

        let ctx = ProcedureContext {
            graph,
            parameters: args,
        };

        proc.call(&ctx)
    }

    /// List all registered procedures.
    pub fn list_procedures(&self) -> Vec<ProcedureInfo> {
        self.procedures.values().map(|p| p.info()).collect()
    }

    /// Check if a procedure exists.
    pub fn has_procedure(&self, name: &str) -> bool {
        self.procedures.contains_key(name)
    }

    /// Get procedure info by name.
    pub fn get_info(&self, name: &str) -> Option<ProcedureInfo> {
        self.procedures.get(name).map(|p| p.info())
    }
}

impl<G: GraphBackend> Default for ProcedureRegistry<G> {
    fn default() -> Self {
        Self::new()
    }
}
