//! Pagination, sorting, and field selection types.

use serde::{Deserialize, Serialize};

/// Page information for paginated results.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct Page {
    /// Current page number (1-indexed).
    pub number: u64,
    /// Number of items per page.
    pub size: u64,
    /// Total number of pages, if known.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub total_pages: Option<u64>,
    /// Total number of items, if known.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub total_items: Option<u64>,
}

impl Page {
    /// Creates a new page with the given number and size.
    #[must_use]
    pub const fn new(number: u64, size: u64) -> Self {
        Self {
            number,
            size,
            total_pages: None,
            total_items: None,
        }
    }

    /// Sets the total number of pages.
    #[must_use]
    pub const fn with_total_pages(mut self, total_pages: u64) -> Self {
        self.total_pages = Some(total_pages);
        self
    }

    /// Sets the total number of items.
    #[must_use]
    pub const fn with_total_items(mut self, total_items: u64) -> Self {
        self.total_items = Some(total_items);
        self
    }

    /// Calculates the offset for database queries.
    #[must_use]
    pub const fn offset(&self) -> u64 {
        self.number.saturating_sub(1).saturating_mul(self.size)
    }

    /// Returns true if there is a next page.
    #[must_use]
    pub const fn has_next(&self) -> Option<bool> {
        match self.total_pages {
            Some(total) => Some(self.number < total),
            None => None,
        }
    }

    /// Returns true if there is a previous page.
    #[must_use]
    pub const fn has_previous(&self) -> bool {
        self.number > 1
    }
}

impl Default for Page {
    fn default() -> Self {
        Self::new(1, 20)
    }
}

/// Sort specification for query results.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Sort {
    /// Field name to sort by.
    pub field: String,
    /// Sort direction.
    pub direction: SortDirection,
}

impl Sort {
    /// Creates a new sort specification.
    #[must_use]
    pub fn new(field: impl Into<String>, direction: SortDirection) -> Self {
        Self {
            field: field.into(),
            direction,
        }
    }

    /// Creates an ascending sort.
    #[must_use]
    pub fn asc(field: impl Into<String>) -> Self {
        Self::new(field, SortDirection::Asc)
    }

    /// Creates a descending sort.
    #[must_use]
    pub fn desc(field: impl Into<String>) -> Self {
        Self::new(field, SortDirection::Desc)
    }

    /// Parses a sort string (e.g., "name" or "-name" for descending).
    #[must_use]
    pub fn parse(s: &str) -> Option<Self> {
        let trimmed = s.trim();
        if trimmed.is_empty() {
            return None;
        }

        if let Some(field) = trimmed.strip_prefix('-') {
            if field.is_empty() {
                return None;
            }
            Some(Self::desc(field))
        } else if let Some(field) = trimmed.strip_prefix('+') {
            if field.is_empty() {
                return None;
            }
            Some(Self::asc(field))
        } else {
            Some(Self::asc(trimmed))
        }
    }
}

/// Sort direction.
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum SortDirection {
    /// Ascending order.
    #[default]
    Asc,
    /// Descending order.
    Desc,
}

impl SortDirection {
    /// Returns true if ascending.
    #[must_use]
    pub const fn is_asc(&self) -> bool {
        matches!(self, Self::Asc)
    }

    /// Returns true if descending.
    #[must_use]
    pub const fn is_desc(&self) -> bool {
        matches!(self, Self::Desc)
    }
}

/// Field selection for sparse fieldsets.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct Fields {
    /// Fields to include (whitelist).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub include: Option<Vec<String>>,
    /// Fields to exclude (blacklist).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub exclude: Option<Vec<String>>,
}

impl Fields {
    /// Creates empty field selection.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Creates a field selection with included fields.
    #[must_use]
    pub fn include(fields: Vec<String>) -> Self {
        Self {
            include: Some(fields),
            exclude: None,
        }
    }

    /// Creates a field selection with excluded fields.
    #[must_use]
    pub fn exclude(fields: Vec<String>) -> Self {
        Self {
            include: None,
            exclude: Some(fields),
        }
    }

    /// Checks if a field should be included.
    #[must_use]
    pub fn should_include(&self, field: &str) -> bool {
        if let Some(ref include) = self.include {
            return include.iter().any(|f| f == field);
        }
        if let Some(ref exclude) = self.exclude {
            return !exclude.iter().any(|f| f == field);
        }
        true
    }

    /// Parses a comma-separated list of fields.
    /// Fields prefixed with '-' are excluded, others are included.
    #[must_use]
    pub fn parse(s: &str) -> Self {
        let mut include = Vec::new();
        let mut exclude = Vec::new();

        for field in s.split(',') {
            let field = field.trim();
            if field.is_empty() {
                continue;
            }
            if let Some(excluded) = field.strip_prefix('-') {
                if !excluded.is_empty() {
                    exclude.push(excluded.to_string());
                }
            } else {
                include.push(field.to_string());
            }
        }

        match (include.is_empty(), exclude.is_empty()) {
            (false, true) => Self::include(include),
            (true, false) => Self::exclude(exclude),
            (false, false) => Self {
                include: Some(include),
                exclude: Some(exclude),
            },
            (true, true) => Self::new(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_page_new() {
        let page = Page::new(2, 25);
        assert_eq!(page.number, 2);
        assert_eq!(page.size, 25);
        assert!(page.total_pages.is_none());
        assert!(page.total_items.is_none());
    }

    #[test]
    fn test_page_with_totals() {
        let page = Page::new(1, 10).with_total_pages(5).with_total_items(50);
        assert_eq!(page.total_pages, Some(5));
        assert_eq!(page.total_items, Some(50));
    }

    #[test]
    fn test_page_offset() {
        assert_eq!(Page::new(1, 10).offset(), 0);
        assert_eq!(Page::new(2, 10).offset(), 10);
        assert_eq!(Page::new(3, 25).offset(), 50);
    }

    #[test]
    fn test_page_navigation() {
        let page = Page::new(2, 10).with_total_pages(5);
        assert_eq!(page.has_next(), Some(true));
        assert!(page.has_previous());

        let first_page = Page::new(1, 10).with_total_pages(5);
        assert!(first_page.has_next() == Some(true));
        assert!(!first_page.has_previous());

        let last_page = Page::new(5, 10).with_total_pages(5);
        assert!(last_page.has_next() == Some(false));
        assert!(last_page.has_previous());
    }

    #[test]
    fn test_sort_new() {
        let sort = Sort::new("name", SortDirection::Asc);
        assert_eq!(sort.field, "name");
        assert!(sort.direction.is_asc());
    }

    #[test]
    fn test_sort_asc_desc() {
        let asc = Sort::asc("created_at");
        assert!(asc.direction.is_asc());

        let desc = Sort::desc("updated_at");
        assert!(desc.direction.is_desc());
    }

    #[test]
    fn test_sort_parse() {
        let asc = Sort::parse("name").expect("should parse");
        assert_eq!(asc.field, "name");
        assert!(asc.direction.is_asc());

        let desc = Sort::parse("-created_at").expect("should parse");
        assert_eq!(desc.field, "created_at");
        assert!(desc.direction.is_desc());

        let explicit_asc = Sort::parse("+updated_at").expect("should parse");
        assert_eq!(explicit_asc.field, "updated_at");
        assert!(explicit_asc.direction.is_asc());

        assert!(Sort::parse("").is_none());
        assert!(Sort::parse("-").is_none());
        assert!(Sort::parse("+").is_none());
    }

    #[test]
    fn test_fields_include() {
        let fields = Fields::include(vec!["id".to_string(), "name".to_string()]);
        assert!(fields.should_include("id"));
        assert!(fields.should_include("name"));
        assert!(!fields.should_include("email"));
    }

    #[test]
    fn test_fields_exclude() {
        let fields = Fields::exclude(vec!["password".to_string()]);
        assert!(fields.should_include("id"));
        assert!(fields.should_include("name"));
        assert!(!fields.should_include("password"));
    }

    #[test]
    fn test_fields_parse() {
        let include_only = Fields::parse("id,name,email");
        assert_eq!(
            include_only.include,
            Some(vec![
                "id".to_string(),
                "name".to_string(),
                "email".to_string()
            ])
        );
        assert!(include_only.exclude.is_none());

        let exclude_only = Fields::parse("-password,-secret");
        assert!(exclude_only.include.is_none());
        assert_eq!(
            exclude_only.exclude,
            Some(vec!["password".to_string(), "secret".to_string()])
        );

        let mixed = Fields::parse("id,name,-password");
        assert_eq!(
            mixed.include,
            Some(vec!["id".to_string(), "name".to_string()])
        );
        assert_eq!(mixed.exclude, Some(vec!["password".to_string()]));

        let empty = Fields::parse("");
        assert!(empty.include.is_none());
        assert!(empty.exclude.is_none());
    }

    #[test]
    fn test_sort_direction_default() {
        let dir = SortDirection::default();
        assert!(dir.is_asc());
    }

    #[test]
    fn test_serialization() {
        let page = Page::new(1, 10).with_total_items(100);
        let json = serde_json::to_string(&page).expect("should serialize");
        assert!(json.contains("\"number\":1"));
        assert!(json.contains("\"total_items\":100"));

        let sort = Sort::desc("name");
        let json = serde_json::to_string(&sort).expect("should serialize");
        assert!(json.contains("\"direction\":\"desc\""));
    }
}
