pub mod source_file;

use std::{
    collections::HashMap,
    fmt::{Debug, Display},
    num::{ParseFloatError, ParseIntError},
    ops::Range,
};

use ariadne::{Config, IndexType, Label, Report, ReportBuilder, ReportKind};
use chumsky::{
    extra,
    input::{Input, MappedSpan},
    primitive::{any, choice, just, map_ctx},
    text::{newline, TextExpected},
    util::Maybe,
    DefaultExpected, Parser,
};

use crate::{
    ast::{CurlyKind, EscapeSequence},
    AriadneCache, Span,
};

#[derive(PartialEq, Clone)]
/// Represents a failure to parse a source file
pub enum Error<'s> {
    /// In case there were multiple errors
    Multiple(Vec<Error<'s>>),
    /// An end of input was expected but the source file did not end
    ExpectedEndOfInput(Span<'s>),
    /// The source file ended unexpectedly
    UnexpectedEndOfInput(Span<'s>),
    /// One of the following characters were expected
    ExpectedOneOfMultipleChars {
        /// Where they were expected
        at: Span<'s>,
        /// Which character we got instead (if any in case of end of file)
        got: Option<char>,
        /// Which characters were expected
        expected: Vec<char>,
    },
    /// A specific character was expected
    ExpectedChar {
        /// Where it was expected
        at: Span<'s>,
        /// Which character we got instead (if any in case of end of file)
        got: Option<char>,
        /// Which character was expected
        expected: char,
    },
    /// Encountered an unexpected character
    UnexpectedChar(Span<'s>, Option<char>),
    /// Expected a digit
    ExpectedDigit {
        /// Where it was expected
        at: Span<'s>,
        /// Which character we got instead (if any in case of end of file)
        got: Option<char>,
        /// Which range of digits was expected
        expected: Range<u32>,
    },
    /// Expected a specific identifier (a name)
    ExpectedIdentifier {
        /// Where it was expected
        at: Span<'s>,
        /// Which character we got instead (if any in case of end of file)
        got: Option<char>,
        /// Which identifier we expected
        expected: &'s str,
    },
    /// Expected part of an identifier
    ExpectedIdentifierPart {
        /// Where it was expected
        at: Span<'s>,
        /// Which character we got instead (if any in case of end of file)
        got: Option<char>,
    },
    /// Expected inline whitespace (such as spaces and tabs but not line feeds)
    ExpectedInlineWhitespace {
        /// Where it was expected
        at: Span<'s>,
        /// Which character we got instead (if any in case of end of file)
        got: Option<char>,
    },
    /// Expected whitespace characters (such as spaces, tabs and line feeds)
    ExpectedWhitespace {
        /// Where it was expected
        at: Span<'s>,
        /// Which character we got instead (if any in case of end of file)
        got: Option<char>,
    },
    /// Expected a newline (such as LF or CRLF)
    ExpectedNewline {
        /// Where it was expected
        at: Span<'s>,
        /// Which character we got instead (if any in case of end of file)
        got: Option<char>,
    },
    /// A delimiter (such as a brace or bracket) did not get closed
    UnclosedDelimiter {
        /// Where the opening brace or backet is
        start: Span<'s>,
        /// Which kind of delimiter wasn't closed
        kind: DelimiterType,
    },
    /// Encountered an unknown escape sequence (character sequence starting with a backslash to use
    /// a certain sequence with a special meaning literally; such as \n)
    UnknownEscapeSequence(Span<'s>),
    /// Template bodies (templates and template literals) need to start with a {\n but we did not
    /// get that newline
    MissingTemplateBodyStartNewline {
        /// Where the template starts
        template_start: Span<'s>,
        /// Where we expected the newline
        expected_newline_span: Span<'s>,
    },
    /// Encountered an invalid int literal (such as 10); this could be caused by writing a number
    /// that is too large to fit in the signed 64 bit integer for example
    InvalidIntLiteral(ParseIntError, Span<'s>),
    /// Encountered an invalid float literal (such as 1.45); this could be caused by writing a
    /// number that does not fit in a 64 bit float for example
    InvalidFloatLiteral(ParseFloatError, Span<'s>),
    // false positive, since we mean the template language not rust
    #[allow(clippy::doc_markdown)]
    /// Encountered an invalid numeric member access (such as some_tuple.0); this could be caused
    /// by writing a number that is too large to fit in a 32 bit unsigned integer
    InvalidNumericMemberAccess(ParseIntError, Span<'s>),
    /// Encountered a temlate curly escape sequence (such as \{{) that had a wrong amount of curly
    /// braces. This can happen if you try to escape too few curly braces (in which case you can
    /// just them without the backslash).
    WrongCurlyCountTemplateCurlyEscapeSequence {
        /// Whether the escape sequence was of open or closed curly braces
        curly_kind: CurlyKind,

        /// Where the curly braces are that started the template or template literal
        template_curly_span: Span<'s>,
        /// The count of curly braces that started the template or template literal
        template_curly_count: usize,

        /// Where the escape sequence is
        escape_sequence_span: Span<'s>,
        /// How many curly braces the escape sequence used
        escape_sequence_curly_count: usize,
    },
}

impl<'s> Error<'s> {
    // hardly avoidable
    #[allow(clippy::too_many_lines)]
    fn write_to_buf(&self, cache: &mut AriadneCache<'s>, buf: &mut Vec<u8>, with_color: bool) {
        fn builder(at: Span<'_>, with_color: bool) -> ReportBuilder<'_, Span<'_>> {
            Report::build(ReportKind::Error, at).with_config(
                Config::new()
                    .with_color(with_color)
                    .with_index_type(IndexType::Byte),
            )
        }

        fn write<'s>(
            builder: ReportBuilder<'s, Span<'s>>,
            cache: &mut AriadneCache<'s>,
            buf: &mut Vec<u8>,
        ) {
            builder.finish().write(cache, buf).unwrap();
        }

        match self {
            Self::Multiple(inner) => {
                for err in inner {
                    err.write_to_buf(cache, buf, with_color);
                }
            }

            Self::ExpectedEndOfInput(at) => write(
                builder(*at, with_color).with_message("Expected end of input").with_label(Label::new(*at).with_message("Expected the end of input here")),
                cache,
                buf
            ),

            Self::UnexpectedEndOfInput(at) => write(
                builder(*at, with_color).with_message("Unexpected end of input"),
                cache,
                buf
            ),

            Self::ExpectedChar { at, expected, got: _ } => write(
                builder(*at, with_color).with_message("Expected character").with_label(Label::new(*at).with_message(format!("Expected {expected:?}"))),
                cache,
                buf
            ),

            Self::ExpectedOneOfMultipleChars { at, expected, got: _ } =>  write(
                builder(*at, with_color).with_message("Expected a different character").with_label(Label::new(*at).with_message(format!("Expected one of the following characters: {expected:?}"))),
                cache,
                buf
            ),

            Self::UnexpectedChar(at, _got) => write(
                builder(*at, with_color).with_message("Unexpected character").with_label(Label::new(*at).with_message("Unexpected character")),
                cache,
                buf
            ),

            Self::ExpectedDigit { at, expected, got: _ } => write(
                builder(*at, with_color).with_message("Expected a digit").with_label(Label::new(*at).with_message(format!("Expected a digit in the range of {expected:?}"))),
                cache,
                buf
            ),

            Self::ExpectedIdentifier { at, expected, got: _ } => write(
                builder(*at, with_color).with_message("Expected identifier").with_label(Label::new(*at).with_message(format!("Expected identifier {expected:?}"))),
                cache,
                buf
            ),

            Self::ExpectedIdentifierPart { at, got: _ } => write(
                builder(*at, with_color).with_message("Expected identifier").with_label(Label::new(*at).with_message("Expected an indentifier")),
                cache,
                buf
            ),

            Self::ExpectedInlineWhitespace { at, got: _ } => write(
                builder(*at, with_color).with_message("Expected inline whitespace").with_label(Label::new(*at).with_message("Expected inline whitespace here")),
                cache,
                buf
            ),

            Self::ExpectedWhitespace { at, got: _ } => write(
                builder(*at, with_color).with_message("Expected whitespace").with_label(Label::new(*at).with_message("Expected whitespace here")),
                cache,
                buf
            ),

            Self::ExpectedNewline { at, got: _ } => write(
                builder(*at, with_color).with_message("Expected a newline").with_label(Label::new(*at).with_message("Expected a newline here")),
                cache,
                buf
            ),

            Self::InvalidFloatLiteral(err, at) => write(
                builder(*at, with_color).with_message(format!("Invalid float literal: {err}")).with_label(Label::new(*at).with_message("This float literal is invalid")),
                cache,
                buf,
            ),

            Self::InvalidIntLiteral(err, at) => write(
                builder(*at, with_color).with_message(format!("Invalid int literal: {err}")).with_label(Label::new(*at).with_message("This int literal is invalid")),
                cache,
                buf,
            ),

            Self::InvalidNumericMemberAccess(err, at) => write(
                builder(*at, with_color).with_message(format!("Invalid tuple access: {err}")).with_label(Label::new(*at).with_message("This tuple access is invalid")),
                cache,
                buf,
            ),

            Self::MissingTemplateBodyStartNewline {
                template_start,
                expected_newline_span,
            } => write(
                builder(*expected_newline_span, with_color)
                    .with_message("Missing Template body newline")
                    .with_note("Template bodies need to start on a newline")
                    .with_label(
                        Label::new(*expected_newline_span)
                            .with_message("Expected a newline here")
                            .with_priority(1),
                    )
                    .with_label(
                        Label::new(*template_start)
                            .with_message("Template body starts here")
                            .with_priority(0),
                    ),
                cache,
                buf,
            ),

            Self::UnclosedDelimiter { start, kind } => write(
                builder(*start, with_color)
                    .with_message("Unclosed delimiter")
                    .with_label(
                        Label::new(*start).with_message(format!("This {kind} was never closed")),
                    ),
                cache,
                buf,
            ),

            Self::UnknownEscapeSequence(at) => write(
                builder(*at, with_color).with_message("Unknown escape sequence").with_label(Label::new(*at).with_message("This escape sequence is unknown")),
                cache,
                buf,
            ),

            Self::WrongCurlyCountTemplateCurlyEscapeSequence {
                curly_kind,
                template_curly_span,
                template_curly_count,
                escape_sequence_span,
                escape_sequence_curly_count,
            } => write(
                builder(*escape_sequence_span, with_color)
                    .with_message("Too few curly braces in escape sequence")
                    .with_label(Label::new(*template_curly_span).with_message(format!(
                        "Template body starts here with {template_curly_count} curly braces"
                    )))
                    .with_label(Label::new(*escape_sequence_span).with_message(format!("The template body uses {template_curly_count} curly braces but this {curly_kind} curly brace escape sequence escapes just {escape_sequence_curly_count}")))
                    .with_note(format!("Up to {template_curly_count} consecutive curly braces can be used without escaping them")),
                    cache,
                    buf
            ),
        }
    }
}

impl Debug for Error<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("\n")?;
        <Self as Display>::fmt(self, f)
    }
}

impl Display for Error<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut buf = Vec::new();
        let mut cache = AriadneCache(HashMap::new());

        self.write_to_buf(&mut cache, &mut buf, !f.alternate());

        f.write_str(std::str::from_utf8(&buf).unwrap())
    }
}

impl std::error::Error for Error<'_> {}

impl<'s, F: Fn(chumsky::span::SimpleSpan) -> Span<'s> + 's>
    chumsky::error::Error<'s, MappedSpan<Span<'s>, &'s str, F>> for Error<'s>
{
    fn merge(self, other: Self) -> Self {
        match (self, other) {
            (Error::Multiple(mut previous), Error::Multiple(next)) => {
                previous.extend(next);
                Error::Multiple(previous)
            }
            (Error::Multiple(mut previous), other) => {
                previous.push(other);
                Error::Multiple(previous)
            }
            (first, second) => Error::Multiple(vec![first, second]),
        }
    }
}

impl<'s, F: Fn(chumsky::span::SimpleSpan) -> Span<'s> + 's>
    chumsky::error::LabelError<'s, MappedSpan<Span<'s>, &'s str, F>, DefaultExpected<'s, char>>
    for Error<'s>
{
    fn expected_found<E: IntoIterator<Item = DefaultExpected<'s, char>>>(
        expected: E,
        found: Option<
            chumsky::util::MaybeRef<'s, <MappedSpan<Span<'s>, &'s str, F> as Input<'s>>::Token>,
        >,
        span: <MappedSpan<Span<'s>, &'s str, F> as Input<'s>>::Span,
    ) -> Self {
        Self::Multiple(
            expected
                .into_iter()
                .map(|expected| match expected {
                    DefaultExpected::Any => Self::UnexpectedEndOfInput(span),
                    DefaultExpected::EndOfInput => Self::ExpectedEndOfInput(span),
                    DefaultExpected::SomethingElse => {
                        Self::UnexpectedChar(span, found.map(Maybe::into_inner))
                    }
                    DefaultExpected::Token(t) => Self::ExpectedChar {
                        at: span,
                        expected: t.into_inner(),
                        got: found.map(Maybe::into_inner),
                    },
                    _ => Self::UnexpectedChar(span, found.map(Maybe::into_inner)),
                })
                .collect(),
        )
    }
}

impl<'s, F: Fn(chumsky::span::SimpleSpan) -> Span<'s> + 's>
    chumsky::error::LabelError<
        's,
        MappedSpan<Span<'s>, &'s str, F>,
        TextExpected<'s, MappedSpan<Span<'s>, &'s str, F>>,
    > for Error<'s>
{
    fn expected_found<
        E: IntoIterator<Item = TextExpected<'s, MappedSpan<Span<'s>, &'s str, F>>>,
    >(
        expected: E,
        found: Option<
            chumsky::util::MaybeRef<'s, <MappedSpan<Span<'s>, &'s str, F> as Input<'s>>::Token>,
        >,
        span: <MappedSpan<Span<'s>, &'s str, F> as Input<'s>>::Span,
    ) -> Self {
        Self::Multiple(
            expected
                .into_iter()
                .map(|expected| match expected {
                    TextExpected::Digit(range) => Error::ExpectedDigit {
                        at: span,
                        expected: range,
                        got: found.map(Maybe::into_inner),
                    },
                    TextExpected::Identifier(ident) => Error::ExpectedIdentifier {
                        at: span,
                        expected: ident,
                        got: found.map(Maybe::into_inner),
                    },
                    TextExpected::IdentifierPart => Error::ExpectedIdentifierPart {
                        at: span,
                        got: found.map(Maybe::into_inner),
                    },
                    TextExpected::InlineWhitespace => Error::ExpectedInlineWhitespace {
                        at: span,
                        got: found.map(Maybe::into_inner),
                    },
                    TextExpected::Newline => Error::ExpectedNewline {
                        at: span,
                        got: found.map(Maybe::into_inner),
                    },
                    TextExpected::Whitespace => Error::ExpectedWhitespace {
                        at: span,
                        got: found.map(Maybe::into_inner),
                    },
                    _ => Error::UnexpectedChar(span, found.map(Maybe::into_inner)),
                })
                .collect(),
        )
    }
}

impl<'s, F: Fn(chumsky::span::SimpleSpan) -> Span<'s> + 's>
    chumsky::error::LabelError<'s, MappedSpan<Span<'s>, &'s str, F>, Maybe<char, &'s char>>
    for Error<'s>
{
    fn expected_found<E: IntoIterator<Item = Maybe<char, &'s char>>>(
        expected: E,
        found: Option<
            chumsky::util::MaybeRef<'s, <MappedSpan<Span<'s>, &'s str, F> as Input<'s>>::Token>,
        >,
        span: <MappedSpan<Span<'s>, &'s str, F> as Input<'s>>::Span,
    ) -> Self {
        Self::ExpectedOneOfMultipleChars {
            at: span,
            got: found.map(Maybe::into_inner),
            expected: expected.into_iter().map(Maybe::into_inner).collect(),
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
/// The kind of delimiter
pub enum DelimiterType {
    /// a multi line comment; /* and */
    MultiLineComment,
    /// a parameter list; ( and )
    ParameterList,
    /// an array type; [ and ]
    ArrayType,
    /// an array literal; [ and ]
    ArrayLiteral,
    /// a tuple type; ( and )
    TupleType,
    /// a tuple literal; ( and )
    TupleLiteral,
    /// a struct type; { and }
    StructType,
    /// a struct literal; { and }
    BracketedType,
    /// a string literal; " and "
    StructLiteral,
    /// a bracketed type; ( and )
    StringLiteral,
    /// a bracketed expression; ( and )
    BracketedExpression,
    /// a template body; {\n and \n}
    TemplateBody { curly_count: usize },
    /// select arms; { and }
    SelectArms,
    /// a command; {* and }*
    Command { curly_count: usize },
}

impl Display for DelimiterType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::MultiLineComment => write!(f, "multiline comment"),
            Self::ParameterList => write!(f, "parameter list"),
            Self::ArrayType => write!(f, "array type"),
            Self::ArrayLiteral => write!(f, "array literal"),
            Self::TupleType => write!(f, "tuple type"),
            Self::TupleLiteral => write!(f, "tuple literal"),
            Self::StructType => write!(f, "struct type"),
            Self::BracketedType => write!(f, "bracketed type"),
            Self::StructLiteral => write!(f, "struct literal"),
            Self::StringLiteral => write!(f, "string literal"),
            Self::BracketedExpression => write!(f, "bracketed expression"),
            Self::TemplateBody { curly_count } if *curly_count == 1 => write!(f, "template body"),
            Self::TemplateBody { curly_count } => {
                write!(f, "template body ({curly_count} curly braces)")
            }
            Self::SelectArms => write!(f, "select arms"),
            Self::Command { curly_count } if *curly_count == 1 => write!(f, "command"),
            Self::Command { curly_count } => write!(f, "command ({curly_count} curly braces)"),
        }
    }
}

fn with_span<'a, I: Input<'a>, T, E: extra::ParserExtra<'a, I>>(
    original: impl Parser<'a, I, T, E> + Clone,
) -> impl Parser<'a, I, (T, I::Span), E> + Clone {
    original.map_with(|output, extra| (output, extra.span()))
}

fn map_err_missing_delimiter<
    's,
    I: Input<'s, Span = Span<'s>>,
    S,
    T,
    E,
    Extra: extra::ParserExtra<'s, I, Error = Error<'s>>,
>(
    start: impl Parser<'s, I, S, Extra> + Clone,
    output: impl Parser<'s, I, T, Extra> + Clone,
    end: impl Parser<'s, I, E, Extra> + Clone,
    delimiter_type: DelimiterType,
) -> impl Parser<'s, I, T, Extra> + Clone {
    start
        .to_span()
        .then(output)
        .then(end.or_not())
        .try_map(move |((start, output), end), _| match end {
            Some(_) => Ok(output),
            None => Err(Error::UnclosedDelimiter {
                start,
                kind: delimiter_type,
            }),
        })
}

fn ignore_ctx<'a, I: Input<'a>, T, E: extra::ParserExtra<'a, I>>(
    original: impl Parser<'a, I, T, extra::Full<E::Error, E::State, ()>> + Clone,
) -> impl Parser<'a, I, T, E> + Clone {
    map_ctx(|_| (), original)
}

#[macro_export]
#[allow(non_snake_case)]
/// Internal macro. Semver exempt. Do not use.
macro_rules! __internal__parse_with_path {
    ($p:expr, $path: expr, $source:expr) => {{
        use ::chumsky::{input::Input, Parser};

        $p.parse(
            $source.map_span(|span: ::chumsky::span::SimpleSpan| $crate::Span {
                start: span.start,
                end: span.end,
                path_and_source: ($path, $source),
            }),
        )
    }};
}

#[macro_export]
#[allow(non_snake_case)]
/// Internal macro. Semver exempt. Do not use.
macro_rules! __internal__parse_include_str {
    ($p:expr, $path:literal) => {{
        use ::chumsky::input::Input;

        $p.parse(
            include_str!($path).map_span(|span: ::chumsky::span::SimpleSpan| $crate::Span {
                start: span.start,
                end: span.end,
                path_and_source: ($path, include_str!($path)),
            }),
        )
    }};
}

#[macro_export]
#[allow(non_snake_case)]
/// Internal macro. Semver exempt. Do not use.
macro_rules! __internal__span_macro_with_path {
    ($span:path, $path:expr, $source:expr) => {
        macro_rules! s {
            ($expr:expr) => {{
                $span {
                    start: $expr.start,
                    end: $expr.end,
                    path_and_source: ($path, $source),
                }
            }};
        }
    };
}

#[macro_export]
#[allow(non_snake_case)]
/// Internal macro. Semver exempt. Do not use.
macro_rules! __internal__span_macro_include_str {
    ($span:path, $path:literal) => {
        macro_rules! s {
            ($expr:expr) => {{
                $span {
                    start: $expr.start,
                    end: $expr.end,
                    path_and_source: ($path, include_str!($path)),
                }
            }};
        }
    };
}

#[macro_export]
#[allow(non_snake_case)]
/// Internal macro. Semver exempt. Do not use.
macro_rules! __internal__parser {
    ($vis:vis $name:ident, $output:ty, $content:block) => {
        $vis fn $name<'s, F: Fn(::chumsky::span::SimpleSpan) -> $crate::Span<'s> + 's>() -> impl ::chumsky::Parser<'s, ::chumsky::input::MappedSpan<$crate::Span<'s>, &'s str, F>, $output, ::chumsky::extra::Err<$crate::parse::Error<'s>>> + Clone {
            $content/*.map_with(|m, ctx| {dbg!(ctx.span(), stringify!($name), &m); m})*/
        }
    };
    ($vis:vis $name:ident($($arg:ident: $arg_ty:ty),+), $output:ty, $content:block) => {
        $vis fn $name<'s, F: Fn(::chumsky::span::SimpleSpan) -> $crate::Span<'s> + 's>($($arg: $arg_ty),+) -> impl ::chumsky::Parser<'s, ::chumsky::input::MappedSpan<$crate::Span<'s>, &'s str, F>, $output, ::chumsky::extra::Err<$crate::parse::Error<'s>>> + Clone {
            $content/*.map_with(|m, ctx| {dbg!(ctx.span(), stringify!($name), &m); m})*/
        }
    };
    ($vis:vis $name:ident, $output:ty, $extra:ty, $content:block) => {
        $vis fn $name<'s, F: Fn(::chumsky::span::SimpleSpan) -> $crate::Span<'s> + 's>() -> impl ::chumsky::Parser<'s, ::chumsky::input::MappedSpan<$crate::Span<'s>, &'s str, F>, $output, $extra> + Clone {
            $content/*.map_with(|m, ctx| {dbg!(ctx.span(), stringify!($name), &m); m})*/
        }
    };
    ($vis:vis $name:ident($($arg:ident: $arg_ty:ty),+), $output:ty, $extra:ty, $content:block) => {
        $vis fn $name<'s, F: Fn(::chumsky::span::SimpleSpan) -> $crate::Span<'s> + 's>($($arg: $arg_ty),+) -> impl ::chumsky::Parser<'s, ::chumsky::input::MappedSpan<$crate::Span<'s>, &'s str, F>, $output, $extra> + Clone {
            $content/*.map_with(|m, ctx| {dbg!(ctx.span(), stringify!($name), &m); m})*/
        }
    };
}

__internal__parser! {capturing_newline, &'s str, {
   newline().to_slice()
}}

__internal__parser! {escape_sequence, EscapeSequence, {
    choice((
        just("\\n").to(EscapeSequence::Newline),
        just("\\t").to(EscapeSequence::Tab),
        just("\\r").to(EscapeSequence::CarriageReturn),
        just("\\\\").to(EscapeSequence::Backslash),
        just("\\").then(any()).try_map(|_, span| Err(Error::UnknownEscapeSequence(span)))
    ))
}}
