//! Python Schema class wrapping altimate-core SchemaDefinition.

use altimate_core::schema::types::SchemaDefinition;
use pyo3::prelude::*;

#[pyclass]
#[derive(Clone)]
pub struct Schema {
    pub(crate) inner: SchemaDefinition,
}

#[pymethods]
impl Schema {
    /// Load schema from a YAML string.
    #[staticmethod]
    fn from_yaml(yaml_str: &str) -> PyResult<Self> {
        let inner = SchemaDefinition::from_yaml(yaml_str)
            .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
        Ok(Schema { inner })
    }

    /// Load schema from a JSON string.
    #[staticmethod]
    fn from_json(json_str: &str) -> PyResult<Self> {
        let inner = SchemaDefinition::from_json(json_str)
            .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
        Ok(Schema { inner })
    }

    /// Load schema from a YAML or JSON file (auto-detected by extension).
    #[staticmethod]
    fn from_file(path: &str) -> PyResult<Self> {
        let inner = SchemaDefinition::from_file(std::path::Path::new(path))
            .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
        Ok(Schema { inner })
    }

    /// Load schema from a YAML file.
    #[staticmethod]
    fn from_yaml_file(path: &str) -> PyResult<Self> {
        let inner = SchemaDefinition::from_yaml_file(path)
            .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
        Ok(Schema { inner })
    }

    /// Parse DDL statements into a schema.
    #[staticmethod]
    #[pyo3(signature = (ddl, dialect="generic"))]
    fn from_ddl(ddl: &str, dialect: &str) -> PyResult<Self> {
        let d = crate::convert::parse_dialect(dialect)?;
        let inner = altimate_core::schema::ddl::parse_ddl(ddl, d)
            .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
        Ok(Schema { inner })
    }

    /// Return all table names in the schema.
    fn table_names(&self) -> Vec<String> {
        let mut names: Vec<String> = self.inner.tables.keys().cloned().collect();
        names.sort();
        names
    }

    /// Return column names for a specific table, or None if table not found.
    fn column_names(&self, table: &str) -> Option<Vec<String>> {
        self.inner
            .tables
            .get(table)
            .map(|t| t.columns.iter().map(|c| c.name.clone()).collect())
    }

    /// Serialize the schema to a Python dict.
    fn to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
        crate::convert::to_py_result(py, &self.inner)
    }

    /// Serialize the schema to a JSON string.
    fn to_json(&self) -> PyResult<String> {
        serde_json::to_string_pretty(&self.inner)
            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
    }

    fn __repr__(&self) -> String {
        format!(
            "Schema(tables={}, dialect={:?})",
            self.inner.tables.len(),
            self.inner.dialect,
        )
    }

    fn __len__(&self) -> usize {
        self.inner.tables.len()
    }
}
