use std::fmt::Display;

use nu_protocol::Span;
use owo_colors::OwoColorize;

use crate::parser::Engine;

#[derive(Debug)]
pub enum Error {
    Base {
        span: Option<(Option<Span>, Span)>,
        msg: String,
    },
    Nested {
        span: Option<(Option<Span>, Span)>,
        msg: String,
        children: Vec<Error>,
    },
    Chained(Vec<Error>),
}

impl Error {
    pub const fn mod_error(msg: String, span: Option<(Option<Span>, Span)>) -> Self {
        Self::Base { span, msg }
    }

    pub const fn nest_errors(
        msg: String,
        span: Option<(Option<Span>, Span)>,
        children: Vec<Self>,
    ) -> Self {
        Self::Nested {
            span,
            msg,
            children,
        }
    }

    pub fn chain(mut self, mut error: Self) -> Self {
        match (&mut self, &mut error) {
            (Self::Chained(errors), Self::Chained(extras)) => {
                errors.append(extras);
                self
            }
            (Error::Chained(errors), _) => {
                errors.push(error);
                self
            }
            (_, Error::Chained(errors)) => {
                errors.push(self);
                error
            }
            (_, _) => Self::Chained(vec![self, error]),
        }
    }

    pub fn to_spanned(&self, engine: &Engine) -> String {
        match self {
            Self::Base { span, msg } => base_message(msg, span, engine),
            Error::Nested {
                span,
                children,
                msg,
            } => {
                let message = base_message(msg, span, engine);

                let mut child_iter = children.iter().map(|e| e.to_spanned(engine));
                let message = child_iter
                    .by_ref()
                    .take(children.len() - 1)
                    .map(|src| nested_message(src, "├─", "│ "))
                    .fold(message, |acc, e| acc + "\n" + &e);

                let src = child_iter
                    .next()
                    .expect("Nested errors will always have one child");
                message + "\n" + &nested_message(src, "└─", "  ")
            }
            Error::Chained(errors) => errors
                .iter()
                .map(|e| e.to_spanned(engine))
                .fold(String::new(), |acc, e| acc + "\n" + &e),
        }
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let msg = match self {
            Error::Base { msg, .. } => msg.to_owned(),
            Error::Nested { msg, children, .. } => {
                children.iter().fold(msg.to_owned(), |acc, elem| {
                    acc + "\n"
                        + elem
                            .to_string()
                            .lines()
                            .map(|l| "    ".to_owned() + l)
                            .fold(String::new(), |total, elem| total + "\n" + &elem)
                            .as_str()
                })
            }
            Error::Chained(errors) => errors
                .iter()
                .map(|e| e.to_string())
                .fold(String::new(), |acc, elem| acc + "\n\n" + &elem),
        };

        write!(f, "{msg}")
    }
}

fn base_message(msg: &str, span: &Option<(Option<Span>, Span)>, engine: &Engine) -> String {
    let message = msg.to_owned();
    if let Some(span) = span {
        let (module_message, message) = message
            .split_once("\n")
            .expect("Cannot fail in splitting message");

        let source = span
            .0
            .map(|outer_span| engine.get_span_contents(outer_span));
        let inner_source = engine.get_span_contents(span.1);

        let inner_quoted_source = quote_source(inner_source, false);
        let outer_quoted_source = source.map(|src| quote_source(src, true));

        if let Some(outer_source) = outer_quoted_source {
            format!(
                "{module_message}\n{}\n\n in \n{outer_source}\n\n{message}",
                inner_quoted_source.red()
            )
        } else {
            format!(
                "{module_message}\n{}\n\n{message}",
                inner_quoted_source.red()
            )
        }
    } else {
        message
    }
}

fn quote_source(source: impl AsRef<str>, color_bar: bool) -> String {
    source
        .as_ref()
        .lines()
        .map(|line| {
            if color_bar {
                "  ▎".green().to_string() + line
            } else {
                "  ▎".to_owned() + line
            }
        })
        .fold("".to_owned(), |acc, line| acc + "\n" + &line)
}

fn nested_message(src: String, indicator: &str, pad: &str) -> String {
    let mut lines = src.lines();

    let first_line =
        indicator.blue().to_string() + lines.next().expect("There must be something here");

    lines
        .map(|l| pad.blue().to_string() + l)
        .fold(first_line, |acc, l| acc + "\n" + &l)
}

#[macro_export]
macro_rules! function {
    () => {{
        const fn f() {}
        fn type_name_of<T>(_: T) -> &'static str {
            std::any::type_name::<T>()
        }
        let name = type_name_of(f);
        name.strip_suffix("::f").unwrap()
    }};
}

#[macro_export]
macro_rules! mod_err {
    (($outer_span:expr, $inner_span:expr), $msg:expr) => {{
        use $crate::function;
        let msg = format!(
            "{} (in [{}:{}]) ::\n{}",
            function!(),
            file!(),
            line!(),
            $msg.to_string()
        );

        Error::mod_error(msg, Some((Some($outer_span), $inner_span)))
    }};
    ($inner_span:expr, $msg:expr) => {{
        use $crate::function;
        let msg = format!(
            "{} (in [{}:{}]) ::\n{}",
            function!(),
            file!(),
            line!(),
            $msg.to_string()
        );

        Error::mod_error(msg, Some((None, $inner_span)))
    }};
    ($msg:expr) => {{
        use $crate::function;
        let msg = format!(
            "{} (in [{}:{}]) ::\n{}",
            function!(),
            file!(),
            line!(),
            $msg.to_string()
        );

        Error::mod_error(msg, None)
    }};
}

#[macro_export]
macro_rules! nest_errors {
    (($outer_span:expr, $inner_span:expr), $parent:expr, $($children:ident),+) => {{
        use $crate::function;
        let children = vec![$(Error::mod_error($children.to_string(), None)),+];
        let msg = format!("{} (in [{}:{}]) ::\n{}", function!(), file!(), line!(), $parent);

        Error::nest_errors(msg, Some((Some($outer_span), $inner_span)), children)
    }};
    ($inner_span:expr, $parent:expr, $($children:ident),+) => {{
        use $crate::function;
        let children = vec![$(Error::mod_error($children.to_string(), None)),+];
        let msg = format!("{} (in [{}:{}]) ::\n{}", function!(), file!(), line!(), $parent);

        Error::nest_errors(msg, Some((None, $inner_span)), children)
    }};
    ($parent:expr, $($children:ident),+) => {{
        use $crate::function;
        let children = (vec![$(Error::mod_error($children.to_string(), None)),+]);
        let msg = format!("{} (in [{}:{}]) ::\n{}", function!(), file!(), line!(), $parent);

        Error::nest_errors(msg, None, children)
    }};
}
