//! Global repr formatting options and associated Python types.

use std::sync::OnceLock;
use std::sync::RwLock;

use pyo3::prelude::*;
use pyo3::types::PyModule;

/// Row selection strategy used by qroissant repr formatting.
#[derive(PartialEq, Eq, Default, Clone, Copy, Debug)]
#[pyclass(
    eq,
    eq_int,
    frozen,
    rename_all = "SCREAMING_SNAKE_CASE",
    module = "qroissant"
)]
pub enum RowDisplay {
    /// Show the first `max_rows` rows followed by an ellipsis when truncated.
    #[default]
    Head,
    /// Show the first half and last half of rows with an ellipsis in the middle.
    HeadTail,
}

#[pymethods]
impl RowDisplay {
    fn __repr__(&self) -> &'static str {
        match self {
            Self::Head => "RowDisplay.HEAD",
            Self::HeadTail => "RowDisplay.HEAD_TAIL",
        }
    }
}

/// Formatting options for user-facing qroissant string representations.
///
/// Notes
/// -----
/// These options control how qroissant values render through `str(...)` and
/// `repr(...)`. Apply them process-wide through `set_formatting_options(...)`.
#[pyclass(get_all, eq, frozen, skip_from_py_object, module = "qroissant")]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct FormattingOptions {
    pub max_rows: usize,
    pub max_columns: usize,
    pub row_display: RowDisplay,
}

impl Default for FormattingOptions {
    fn default() -> Self {
        Self {
            max_rows: 8,
            max_columns: 6,
            row_display: RowDisplay::Head,
        }
    }
}

#[pymethods]
impl FormattingOptions {
    #[staticmethod]
    /// Create a builder initialized with qroissant's default formatting policy.
    fn builder() -> FormattingOptionsBuilder {
        FormattingOptionsBuilder::default()
    }

    fn __repr__(&self) -> String {
        format!(
            "FormattingOptions(max_rows={}, max_columns={}, row_display={})",
            self.max_rows,
            self.max_columns,
            self.row_display.__repr__(),
        )
    }
}

/// Builder for [`FormattingOptions`].
#[pyclass(skip_from_py_object, module = "qroissant")]
#[derive(Default, Clone, Debug)]
pub struct FormattingOptionsBuilder {
    inner: FormattingOptions,
}

#[pymethods]
impl FormattingOptionsBuilder {
    #[pyo3(signature = (value, /))]
    fn with_max_rows(&self, value: usize) -> Self {
        let mut b = self.clone();
        b.inner.max_rows = value;
        b
    }

    #[pyo3(signature = (value, /))]
    fn with_max_columns(&self, value: usize) -> Self {
        let mut b = self.clone();
        b.inner.max_columns = value;
        b
    }

    #[pyo3(signature = (value, /))]
    fn with_row_display(&self, value: RowDisplay) -> Self {
        let mut b = self.clone();
        b.inner.row_display = value;
        b
    }

    /// Finalize the builder into an immutable `FormattingOptions` instance.
    fn build(&self) -> FormattingOptions {
        self.inner.clone()
    }

    fn __repr__(&self) -> String {
        format!("FormattingOptionsBuilder({})", self.inner.__repr__())
    }
}

// ---------------------------------------------------------------------------
// Global state
// ---------------------------------------------------------------------------

fn options_lock() -> &'static RwLock<FormattingOptions> {
    static OPTIONS: OnceLock<RwLock<FormattingOptions>> = OnceLock::new();
    OPTIONS.get_or_init(|| RwLock::new(FormattingOptions::default()))
}

pub fn active_options() -> FormattingOptions {
    match options_lock().read() {
        Ok(guard) => guard.clone(),
        Err(poisoned) => poisoned.into_inner().clone(),
    }
}

fn store_options(options: FormattingOptions) {
    match options_lock().write() {
        Ok(mut guard) => *guard = options,
        Err(poisoned) => *poisoned.into_inner() = options,
    }
}

// ---------------------------------------------------------------------------
// Python-visible functions
// ---------------------------------------------------------------------------

#[pyfunction]
/// Return the active qroissant repr formatting options.
pub fn get_formatting_options() -> FormattingOptions {
    active_options()
}

#[pyfunction]
#[pyo3(signature = (options, /))]
/// Update the active qroissant repr formatting options.
pub fn set_formatting_options(options: PyRef<'_, FormattingOptions>) {
    store_options(options.clone());
}

#[pyfunction]
/// Restore qroissant's default repr formatting options.
pub fn reset_formatting_options() {
    store_options(FormattingOptions::default());
}

pub fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
    module.add_class::<RowDisplay>()?;
    module.add_class::<FormattingOptions>()?;
    module.add_class::<FormattingOptionsBuilder>()?;
    module.add_function(wrap_pyfunction!(get_formatting_options, module)?)?;
    module.add_function(wrap_pyfunction!(set_formatting_options, module)?)?;
    module.add_function(wrap_pyfunction!(reset_formatting_options, module)?)?;
    Ok(())
}
