//! # mimic-rs
//!
//! High-performance User-Agent to Sec-CH-UA Client Hints converter.
//!
//! This crate provides functionality to parse User-Agent strings and generate
//! corresponding Sec-CH-UA (Client Hints) headers as used by Chromium-based browsers.
//!
//! ## Features
//!
//! - Zero-copy parsing where possible
//! - Support for Chrome, Edge, Brave, Opera, and other Chromium-based browsers
//! - Accurate greased brand generation matching Chromium's algorithm
//! - Platform and mobile detection
//! - Optional serde support
//!
//! ## Example
//!
//! ```rust
//! use mimic_rs::ClientHints;
//!
//! let ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
//! let hints = ClientHints::from_ua(ua).unwrap();
//!
//! println!("sec-ch-ua: {}", hints.sec_ch_ua());
//! println!("sec-ch-ua-mobile: {}", hints.sec_ch_ua_mobile());
//! println!("sec-ch-ua-platform: {}", hints.sec_ch_ua_platform());
//! ```

#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms)]

mod brands;
mod parser;

pub use brands::Brand;
pub use parser::ParseError;

use std::fmt;

/// Represents the platform/operating system.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Platform {
    /// Windows operating system
    Windows,
    /// macOS operating system
    MacOS,
    /// Linux operating system
    Linux,
    /// Android operating system
    Android,
    /// Chrome OS
    ChromeOS,
    /// Unknown platform
    Unknown,
}

impl Platform {
    /// Returns the platform string as used in sec-ch-ua-platform header.
    #[inline]
    pub const fn as_str(&self) -> &'static str {
        match self {
            Platform::Windows => "Windows",
            Platform::MacOS => "macOS",
            Platform::Linux => "Linux",
            Platform::Android => "Android",
            Platform::ChromeOS => "Chrome OS",
            Platform::Unknown => "Unknown",
        }
    }
}

impl fmt::Display for Platform {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "\"{}\"", self.as_str())
    }
}

/// Client Hints generated from a User-Agent string.
///
/// This struct contains all the information needed to construct
/// Sec-CH-UA headers for HTTP requests.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ClientHints {
    brand: Brand,
    major_version: u32,
    full_version: String,
    platform: Platform,
    is_mobile: bool,
}

impl ClientHints {
    /// Parse a User-Agent string and generate corresponding Client Hints.
    ///
    /// # Arguments
    ///
    /// * `user_agent` - The User-Agent string to parse
    ///
    /// # Returns
    ///
    /// Returns `Ok(ClientHints)` if parsing succeeds, or `Err(ParseError)` if
    /// the User-Agent string cannot be parsed or is not from a Chromium-based browser.
    ///
    /// # Example
    ///
    /// ```rust
    /// use mimic_rs::ClientHints;
    ///
    /// let ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36";
    /// let hints = ClientHints::from_ua(ua).unwrap();
    /// assert_eq!(hints.major_version(), 120);
    /// ```
    pub fn from_ua(user_agent: &str) -> Result<Self, ParseError> {
        parser::parse(user_agent)
    }

    /// Create ClientHints with explicit values.
    ///
    /// Use this when you already know the browser details and don't need to parse a UA string.
    ///
    /// # Example
    ///
    /// ```rust
    /// use mimic_rs::{ClientHints, Brand, Platform};
    ///
    /// let hints = ClientHints::new(Brand::Chrome, 120, "120.0.0.0", Platform::Windows, false);
    /// ```
    pub fn new(
        brand: Brand,
        major_version: u32,
        full_version: impl Into<String>,
        platform: Platform,
        is_mobile: bool,
    ) -> Self {
        Self {
            brand,
            major_version,
            full_version: full_version.into(),
            platform,
            is_mobile,
        }
    }

    /// Returns the detected browser brand.
    #[inline]
    pub const fn brand(&self) -> Brand {
        self.brand
    }

    /// Returns the major version number.
    #[inline]
    pub const fn major_version(&self) -> u32 {
        self.major_version
    }

    /// Returns the full version string.
    #[inline]
    pub fn full_version(&self) -> &str {
        &self.full_version
    }

    /// Returns the detected platform.
    #[inline]
    pub const fn platform(&self) -> Platform {
        self.platform
    }

    /// Returns whether this is a mobile browser.
    #[inline]
    pub const fn is_mobile(&self) -> bool {
        self.is_mobile
    }

    /// Generate the `sec-ch-ua` header value.
    ///
    /// This includes the greased brand, Chromium brand, and the actual browser brand
    /// in a randomized order based on the version number.
    ///
    /// # Example
    ///
    /// ```rust
    /// use mimic_rs::ClientHints;
    ///
    /// let ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36";
    /// let hints = ClientHints::from_ua(ua).unwrap();
    /// let sec_ch_ua = hints.sec_ch_ua();
    /// // Output: "Not A(Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"
    /// ```
    pub fn sec_ch_ua(&self) -> String {
        brands::generate_sec_ch_ua(self.brand, self.major_version)
    }

    /// Generate the `sec-ch-ua-full-version-list` header value.
    ///
    /// Similar to `sec_ch_ua()` but uses the full version string instead of just the major version.
    pub fn sec_ch_ua_full_version_list(&self) -> String {
        brands::generate_sec_ch_ua_full_version(self.brand, self.major_version, &self.full_version)
    }

    /// Generate the `sec-ch-ua-mobile` header value.
    ///
    /// Returns `?1` for mobile browsers, `?0` for desktop browsers.
    #[inline]
    pub const fn sec_ch_ua_mobile(&self) -> &'static str {
        if self.is_mobile {
            "?1"
        } else {
            "?0"
        }
    }

    /// Generate the `sec-ch-ua-platform` header value.
    ///
    /// Returns the platform name in quotes (e.g., `"Windows"`).
    #[inline]
    pub fn sec_ch_ua_platform(&self) -> String {
        format!("\"{}\"", self.platform.as_str())
    }

    /// Generate all standard Client Hints headers.
    ///
    /// Returns a tuple of (sec-ch-ua, sec-ch-ua-mobile, sec-ch-ua-platform).
    pub fn all_headers(&self) -> (String, &'static str, String) {
        (
            self.sec_ch_ua(),
            self.sec_ch_ua_mobile(),
            self.sec_ch_ua_platform(),
        )
    }
}

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

    #[test]
    fn test_chrome_windows() {
        let ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
        let hints = ClientHints::from_ua(ua).unwrap();

        assert_eq!(hints.brand(), Brand::Chrome);
        assert_eq!(hints.major_version(), 120);
        assert_eq!(hints.platform(), Platform::Windows);
        assert!(!hints.is_mobile());
        assert_eq!(hints.sec_ch_ua_mobile(), "?0");
    }

    #[test]
    fn test_edge_windows() {
        let ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0";
        let hints = ClientHints::from_ua(ua).unwrap();

        assert_eq!(hints.brand(), Brand::Edge);
        assert_eq!(hints.major_version(), 120);
        assert_eq!(hints.platform(), Platform::Windows);
    }

    #[test]
    fn test_brave() {
        let ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Brave/120";
        let hints = ClientHints::from_ua(ua).unwrap();

        assert_eq!(hints.brand(), Brand::Brave);
    }

    #[test]
    fn test_chrome_android() {
        let ua = "Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36";
        let hints = ClientHints::from_ua(ua).unwrap();

        assert_eq!(hints.brand(), Brand::Chrome);
        assert_eq!(hints.platform(), Platform::Android);
        assert!(hints.is_mobile());
        assert_eq!(hints.sec_ch_ua_mobile(), "?1");
    }

    #[test]
    fn test_chrome_macos() {
        let ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
        let hints = ClientHints::from_ua(ua).unwrap();

        assert_eq!(hints.platform(), Platform::MacOS);
        assert!(!hints.is_mobile());
    }

    #[test]
    fn test_chrome_linux() {
        let ua = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
        let hints = ClientHints::from_ua(ua).unwrap();

        assert_eq!(hints.platform(), Platform::Linux);
    }

    #[test]
    fn test_sec_ch_ua_format() {
        let hints = ClientHints::new(Brand::Chrome, 120, "120.0.0.0", Platform::Windows, false);
        let sec_ch_ua = hints.sec_ch_ua();

        // Should contain Chromium and Google Chrome
        assert!(sec_ch_ua.contains("Chromium"));
        assert!(sec_ch_ua.contains("Google Chrome"));
        assert!(sec_ch_ua.contains("120"));
    }

    #[test]
    fn test_non_chromium_browser() {
        let ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0";
        let result = ClientHints::from_ua(ua);
        assert!(result.is_err());
    }

    #[test]
    fn test_platform_display() {
        assert_eq!(Platform::Windows.to_string(), "\"Windows\"");
        assert_eq!(Platform::MacOS.to_string(), "\"macOS\"");
        assert_eq!(Platform::Linux.to_string(), "\"Linux\"");
    }
}
