use miette::NamedSource;
use std::fmt;
use syntect::easy::HighlightLines;
use syntect::highlighting::{Style, ThemeSet};
use syntect::parsing::SyntaxSet;
use syntect::util::{LinesWithEndings, as_24_bit_terminal_escaped};

/// Source code and name aggregate abstraction for error/message handling support.
#[derive(Clone, Copy, Debug)]
pub struct Source<'a> {
    code: &'a str,
    name: Option<&'a str>,
}

impl<'a> Source<'a> {
    /// Create a new source without a name.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Source;
    ///
    /// let s = Source::new("fn main() {}");
    ///
    /// assert!(s.name().is_none());
    /// ```
    #[must_use]
    pub fn new<S>(code: &'a S) -> Self
    where
        S: AsRef<str> + ?Sized,
    {
        Self {
            code: code.as_ref(),
            name: None,
        }
    }

    /// Create a new source with a name.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Source;
    ///
    /// let s = Source::new_with_name("fn main() {}", "main.rs");
    ///
    /// assert_eq!(s.name(), Some("main.rs"));
    /// ```
    #[must_use]
    pub fn new_with_name<S, N>(code: &'a S, name: &'a N) -> Self
    where
        S: AsRef<str> + ?Sized,
        N: AsRef<str> + ?Sized,
    {
        Self {
            code: code.as_ref(),
            name: Some(name.as_ref()),
        }
    }

    /// Set the name for this source.
    ///
    /// Returns a new `Source` with the updated name.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Source;
    ///
    /// let s = Source::new("fn main() {}");
    ///
    /// assert!(s.name().is_none());
    ///
    /// let s = s.with_name("main.rs");
    ///
    /// assert_eq!(s.name(), Some("main.rs"));
    /// ```
    #[must_use]
    pub fn with_name<N>(&self, name: &'a N) -> Self
    where
        N: AsRef<str> + ?Sized,
    {
        Self {
            code: self.code,
            name: Some(name.as_ref()),
        }
    }

    /// Get the source code.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Source;
    ///
    /// let s = Source::new("fn main() {}");
    ///
    /// assert_eq!(s.code(), "fn main() {}");
    /// ```
    #[must_use]
    pub fn code(&self) -> &'a str {
        self.code
    }

    /// Get the name.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Source;
    ///
    /// let s = Source::new_with_name("fn main() {}", "main.rs");
    ///
    /// assert_eq!(s.name(), Some("main.rs"));
    /// ```
    #[must_use]
    pub fn name(&self) -> Option<&str> {
        self.name
    }
}

impl<'a> From<&'a str> for Source<'a> {
    fn from(code: &'a str) -> Self {
        Self { code, name: None }
    }
}

impl<'a> From<&'a String> for Source<'a> {
    fn from(code: &'a String) -> Self {
        Self { code, name: None }
    }
}

impl From<Source<'_>> for NamedSource<String> {
    fn from(val: Source<'_>) -> Self {
        NamedSource::new(val.name.unwrap_or_default(), val.code.to_string()).with_language("Rust")
    }
}

impl fmt::Display for Source<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let ss = SyntaxSet::load_defaults_newlines();
        let ts = ThemeSet::load_defaults();

        let syntax = ss
            .find_syntax_by_name("Rust")
            .unwrap_or_else(|| ss.find_syntax_plain_text());

        let theme = ts
            .themes
            .get("base16-ocean.dark")
            .unwrap_or_else(|| ts.themes.values().next().unwrap());
        let mut h = HighlightLines::new(syntax, theme);

        for line in LinesWithEndings::from(self.code) {
            let ranges: Vec<(Style, &str)> = h.highlight_line(line, &ss).unwrap_or_default();
            let escaped = as_24_bit_terminal_escaped(&ranges[..], false);
            write!(f, "{escaped}")?;
        }

        write!(f, "\x1b[0m")?; // Reset terminal colors.

        Ok(())
    }
}
