use crate::{
    combine::{
        expected::Expected,
        result::{PResultExt, ParseResult, State},
    },
    lexer::{
        Lexer,
        span::Spanned,
        token::{Keyword, Token, TokenKind},
    },
};
use rail_diag::{Diagnostics, report};

#[macro_export]
macro_rules! Node {

    (
        $(#[derive_args(name = $rule_name:literal)])?
        $pub:vis
        struct $name:ident
        <
            $( $generics:ident ),* $(,)?
            >
        $($rest:tt)*
    ) => {
        impl
        <
            $( $generics ),*
            >
        $crate::combine::parser::Node for $name <
            $( $generics ),*
            > {
            fn name() -> Option<&'static str> {
                Some(const_str::concat!("<", $crate::Node!(@name $name $($rule_name)?), ">"))
            }
        }
    };

    (
        $(#[derive_args(name = $rule_name:literal)])?
        $pub:vis
        enum $name:ident
        <
            $( $generics:ident ),* $(,)?
            >
        $($rest:tt)*
    ) => {
        impl
        <
            $( $generics ),*
            >
        $crate::combine::parser::Node for $name
        <
            $( $generics ),*
            >
        {
            fn name() -> Option<&'static str> {
                Some(const_str::concat!("<", $crate::Node!(@name $name $($rule_name)?), ">"))
            }
        }
    };

    (
        $(#[derive_args(name = $rule_name:literal)])?
        $pub:vis
        struct $name:ident $($rest:tt)*
    ) => {
        impl $crate::combine::parser::Node for $name {
            fn name() -> Option<&'static str> {
                Some(const_str::concat!("<", $crate::Node!(@name $name $($rule_name)?), ">"))
            }
        }
    };

    (
        $(#[derive_args(name = $rule_name:literal)])?
        $pub:vis
        enum $name:ident $($rest:tt)*
    ) => {
        impl $crate::combine::parser::Node for $name {
            fn name() -> Option<&'static str> {
                Some(const_str::concat!("<", $crate::Node!(@name $name $($rule_name)?), ">"))
            }
        }
    };

    (@name $name:ident) => { const_str::convert_ascii_case!(snake, stringify!($name)) };
    (@name $name:ident $rule_name:literal) => { $rule_name };
}

pub trait Node {
    fn name() -> Option<&'static str> {
        None
    }
    fn name_from_value(&self) -> Option<&'static str> {
        Self::name()
    }
}

/// Object-safe core trait — used for `dyn` storage inside `Recursive`.
/// You should never need to implement this manually — implement [`Parser`] instead,
/// and the blanket impl takes care of this automatically.
pub trait DynParser<K: Keyword> {
    type Item: Node;

    fn parse_dyn<'lex>(
        &self,
        lexer: &mut Lexer<'lex, K>,
        should_stop: &dyn Fn(&mut Lexer<'lex, K>) -> bool,
    ) -> ParseResult<Self::Item>;

    fn can_parse(&self, peek: &Token<K>) -> bool;

    fn expected(&self) -> &Expected<K>;
}

/// Blanket impl — every `Parser` automatically becomes a `DynParser`.
impl<K: Keyword, P: Parser<K>> DynParser<K> for P {
    type Item = P::Item;

    fn parse_dyn<'lex>(
        &self,
        lexer: &mut Lexer<'lex, K>,
        should_stop: &dyn Fn(&mut Lexer<'lex, K>) -> bool,
    ) -> ParseResult<Self::Item> {
        self.parse(lexer, should_stop)
    }

    fn can_parse(&self, peek: &Token<K>) -> bool {
        Parser::can_parse(self, peek)
    }

    fn expected(&self) -> &Expected<K> {
        Parser::expected(self)
    }
}

/// The full parser trait. Implement this for your combinators.
pub trait Parser<K: Keyword>: Clone {
    type Item: Node;

    fn try_parse<'lex>(
        &self,
        lexer: &mut Lexer<'lex, K>,
    ) -> Result<Spanned<Self::Item>, Diagnostics> {
        let res = self.parse(lexer, |lex| matches!(lex.peek().kind(), TokenKind::Eof));

        let peek = lexer.peek();
        let res = if !peek.is_eof() {
            let start = peek.span();
            let mut span = start;
            let end = loop {
                let next = lexer.bump();
                if next.is_eof() {
                    break span;
                }
                span = next.span();
            };
            let span = start.union(&end);
            let diag = report!()
                .message("leftover tokens found")
                .annotate(span, |_| {})
                .build();
            res.with_diagnostics(diag)
        } else {
            res
        };
        match res {
            Ok(state) => match state {
                State::Item(spanned) => Ok(spanned),
                State::Synced(diagnostics) => Err(diagnostics),
            },
            Err(diagnostics) => Err(diagnostics),
        }
    }

    fn parse<'lex, M: Fn(&mut Lexer<'lex, K>) -> bool>(
        &self,
        lexer: &mut Lexer<'lex, K>,
        should_stop: M,
    ) -> ParseResult<Self::Item>;

    fn can_parse(&self, peek: &Token<K>) -> bool;

    fn expected(&self) -> &Expected<K>;

    fn is_optional() -> bool {
        false
    }

    fn is_trivially_skippable() -> bool;
}
