use crate::{
    combine::{
        expected::Expected,
        parser::{Node, Parser},
        result::{PResultExt, ParseResult, State},
    },
    lexer::{
        Lexer,
        span::Spanned,
        token::{Keyword, Token},
    },
};

#[derive(Debug, Clone)]
pub struct Sequence<L, R, K: Keyword> {
    lhs: L,
    rhs: R,
    expected: Expected<K>,
}

impl<L: Parser<K>, R: Parser<K>, K: Keyword> Sequence<L, R, K> {
    pub fn new(lhs: L, rhs: R) -> Self {
        let expected = lhs.expected().clone().then(rhs.expected().clone());
        Self { lhs, rhs, expected }
    }
}

#[macro_export]
macro_rules! seq {

    ($a:expr; |$($params:pat_param),+ $(,)?| $body:expr) => {
        $crate::map!($a.clone(), |$crate::seq_pat!($($params),+)| $body)
    };
    ($a:expr, $b:expr; |$($params:pat_param),+ $(,)?| $body:expr) => {
        $crate::map!($crate::combine::impls::sequence::Sequence::new($a.clone(), $b.clone()), |$crate::seq_pat!($($params),+)| $body)
    };
    ($a:expr, $b:expr, $($rest:expr),+; |$($params:pat_param),+ $(,)?| $body:expr) => {
        $crate::map!(seq!($crate::combine::impls::sequence::Sequence::new($a.clone(), $b.clone()), $($rest),+), |$crate::seq_pat!($($params),+)| $body)
    };

    ($a:expr) => {
        $a.clone()
    };
    ($a:expr, $b:expr) => {
        $crate::combine::impls::sequence::Sequence::new($a.clone(), $b.clone())
    };
    ($a:expr, $b:expr, $($rest:expr),+) => {
        seq!($crate::combine::impls::sequence::Sequence::new($a.clone(), $b.clone()), $($rest),+)
    };
}

#[macro_export]
macro_rules! seq_pat {
    ($first:pat_param $(,)?) => {
        $crate::seq_pat!(@params $first)
    };

    ($first:pat_param, $second:pat_param $(,)?) => {
        $crate::seq_pat!(@params ($first, $second))
    };

    ($first:pat_param, $second:pat_param, $($params:pat_param),+ $(,)?) => {
        $crate::seq_pat!(@params ($first, $second), $($params),*)
    };

    (@params $first:pat_param, $second:pat_param, $($params:pat_param),+) => {
        $crate::seq_pat!(@params ($crate::lexer::span::Spanned { value: $first, .. }, $second), $($params),*)
    };

    (@params $first:pat_param, $second:pat_param) => {
        ($crate::lexer::span::Spanned { value: $first, .. }, $second)
    };

    (@params $first:pat_param) => {
        $first
    };
}

// 1: ($first) -> $first
// 2: ($first, $second) -> ( $first, $second )
// 3: ($first, $second, $third) -> (Spanned { value: ($first, $second)  }, $third)
// 4:                           -> (Spanned { value: ( Spanned { value: ($first, $second)}, $third )}, $forth)
// ($first @ Spanned { value: ( ... )}, $second @ Spanned { .. } )

impl<L: Node, R: Node> Node for (L, R) {}

impl<K: Keyword, L: Parser<K>, R: Parser<K>> Parser<K> for Sequence<L, R, K> {
    type Item = (Spanned<L::Item>, Spanned<R::Item>);

    fn parse<'lex, M: Fn(&mut Lexer<'lex, K>) -> bool>(
        &self,
        lexer: &mut Lexer<'lex, K>,
        should_stop: M,
    ) -> ParseResult<Self::Item> {
        // In the case of sequences, we want to guarantee that the lhs gets the rhs as
        // a condition to abort so it can eat until the next in the sequence
        // to guarntee this we also need to nest at the LHS because then we
        // pass the last one of the sequence as a condition to abort to the
        // sequence (..last - 1)

        /*
        */

        let lhs = self.lhs.parse(lexer, |lexer: &mut Lexer<_>| {
            self.rhs.can_parse(&lexer.peek()) || should_stop(lexer)
        });

        match lhs {
            Ok(state) => {
                let rhs = if should_stop(lexer)
                    && !self.rhs.can_parse(&lexer.peek())
                    && !R::is_optional()
                {
                    let diags = rail_diag::report!()
                        .message(format!(
                            "missing {}, found {}",
                            self.rhs.expected().last().raw_format(true),
                            lexer.peek().kind().tag()
                        ))
                        .annotate(lexer.last_char_span().shift(1), |builder| {
                            builder
                                .level(rail_diag::NoteLevel::Help)
                                .message(format!(
                                    "help: add {} here",
                                    self.rhs.expected().last().raw_format(false)
                                ));
                        })
                        .build();
                    Ok(State::Synced(diags))
                } else {
                    self.rhs.parse(lexer, should_stop)
                };

                Ok(state).combined(rhs, |lhs, rhs| lhs.wrapping(rhs))
            }
            Err(mut diags) => {

                let mut next = lexer.peek();
                if self.rhs.can_parse(&next) {
                    let expected = self.lhs.expected().last().raw_format(true);
                    let span = lexer.char_before_span(next.span());
                    diags = rail_diag::report!()
                        .message(format!("missing {}, found {}", expected, next.kind().tag()))
                        .annotate(span, |builder| {
                            builder
                                .level(rail_diag::NoteLevel::Help)
                                .message(format!("help: add {} here", expected));
                        })
                        .build();
                }

                loop {
                    if self.rhs.can_parse(&next) {
                        break;
                    }

                    if should_stop(lexer) {
                        return ParseResult::desynced(diags);
                    }
                    next = lexer.bump();
                }

                ParseResult::<L::Item>::synced(diags)
                    .combined(self.rhs.parse(lexer, should_stop), |lhs, rhs| {
                        lhs.wrapping(rhs)
                    })
            }
        }
    }

    fn can_parse(&self, peek: &Token<K>) -> bool {
        if L::is_optional() {
            self.lhs.can_parse(peek) || self.rhs.can_parse(peek)
        } else {
            self.lhs.can_parse(peek)
        }
    }

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

    fn is_trivially_skippable() -> bool {
        false
    }
}
