use crate::{
    ast::{self, Span},
    expr::UntypedExpr,
    parser::{annotation, error::ParseError, pattern, token::Token},
};
use chumsky::prelude::*;

pub fn let_(
    r: Recursive<'_, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
    just(Token::Let)
        .ignore_then(assignment_patterns())
        .then(choice((just(Token::Equal), just(Token::LArrow))))
        .then(r.clone())
        .validate(move |((patterns, kind), value), span, emit| {
            if matches!(value, UntypedExpr::Assignment { .. }) {
                emit(ParseError::invalid_assignment_right_hand_side(span))
            }

            let patterns = patterns
                .try_into()
                .expect("We use at_least(1) so this should never be empty");

            UntypedExpr::Assignment {
                location: span,
                value: Box::new(value),
                patterns,
                kind: ast::AssignmentKind::Let {
                    backpassing: kind == Token::LArrow,
                },
                comment: None,
            }
        })
}

fn assignment_patterns() -> impl Parser<Token, Vec<ast::AssignmentPattern>, Error = ParseError> {
    assignment_pattern()
        .separated_by(just(Token::Comma))
        .allow_trailing()
        .at_least(1)
}

pub fn assignment_pattern() -> impl Parser<Token, ast::AssignmentPattern, Error = ParseError> {
    pattern()
        .then(just(Token::Colon).ignore_then(annotation()).or_not())
        .map_with_span(|(pattern, annotation), span| ast::AssignmentPattern {
            pattern,
            annotation,
            location: span,
        })
}

pub fn expect(
    r: Recursive<'_, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
    select! {Token::ExpectComment(comment) => comment}
        .or_not()
        .then(
            just(Token::Expect)
                .map_with_span(|_, span| span)
                .then(
                    assignment_patterns()
                        .then(choice((just(Token::Equal), just(Token::LArrow))))
                        .or_not(),
                )
                .then(r.clone()),
        )
        .validate(
            move |(comment, ((expect_span, opt_pattern), value)), span, emit| {
                let location = span.map(|_, end| (expect_span.start, end));

                if matches!(value, UntypedExpr::Assignment { .. }) {
                    emit(ParseError::invalid_assignment_right_hand_side(location))
                }

                let (patterns, kind) = opt_pattern.unwrap_or_else(|| {
                    let filler_true = ast::AssignmentPattern::new(
                        ast::UntypedPattern::true_(location),
                        None,
                        Span::empty(),
                    );

                    (vec![filler_true], Token::Equal)
                });

                let patterns = patterns
                    .try_into()
                    .expect("We use at_least(1) so this should never be empty");

                UntypedExpr::Assignment {
                    location,
                    patterns,
                    value: Box::new(value),
                    kind: ast::AssignmentKind::Expect {
                        backpassing: kind == Token::LArrow,
                    },
                    comment,
                }
            },
        )
}

#[cfg(test)]
mod tests {
    use crate::assert_expr;

    #[test]
    fn let_bindings() {
        assert_expr!("let thing = [ 1, 2, a ]");
    }

    #[test]
    fn expect() {
        assert_expr!("expect Some(x) = something.field");
    }

    #[test]
    fn expect_bool_sugar() {
        assert_expr!("expect something.field == wow");
    }

    #[test]
    fn expect_trace_if_false() {
        assert_expr!("expect foo?");
    }

    #[test]
    fn expect_comment() {
        assert_expr!(
            r#"
            /// Some user-defined custom comment
            expect Some(x) = something.field
            "#
        );
    }

    #[test]
    fn expect_multiline_comment() {
        assert_expr!(
            r#"
            /// Some user-defined custom comment
            /// over multiple lines
            expect Some(x) = something.field
            "#
        );
    }

    #[test]
    fn expect_unfinished_let() {
        assert_expr!(
            "
            let a =
            // foo
            let b = 42
            "
        );
    }

    #[test]
    fn expect_let_in_let() {
        assert_expr!("let a = { let b = 42 }");
    }

    #[test]
    fn expect_let_in_let_return() {
        assert_expr!(
            "
            let a = {
              let b = 42
              b
            }
            "
        );
    }

    #[test]
    fn expect_let_in_let_parens() {
        assert_expr!("let a = ( let b = 42 )");
    }

    #[test]
    fn expect_expect_let() {
        assert_expr!("expect { let a = 42 } = foo");
    }
}
