use chumsky::prelude::*;

use crate::{
    ast::{self, UntypedPattern},
    parser::{
        error::{self, ParseError},
        token::Token,
    },
};

pub fn parser(
    expression: Recursive<'_, Token, UntypedPattern, ParseError>,
) -> impl Parser<Token, UntypedPattern, Error = ParseError> + '_ {
    just(Token::LeftSquare)
        .ignore_then(expression.clone().separated_by(just(Token::Comma)))
        .then(choice((
            just(Token::Comma).ignore_then(
                just(Token::DotDot)
                    .ignore_then(expression.clone().or_not())
                    .or_not(),
            ),
            just(Token::Comma).ignored().or_not().map(|_| None),
        )))
        .then_ignore(just(Token::RightSquare))
        .validate(|(elements, tail), span: ast::Span, emit| {
            // A leading comma without a preceding element (e.g. `[, ..rest]`) is malformed.
            if elements.is_empty() && tail.is_some() {
                emit(ParseError::expected_input_found(
                    span,
                    None,
                    Some(error::Pattern::SpreadNoSubject),
                ));
            }

            let tail = match tail {
                // There is a tail and it has a Pattern::Var or Pattern::Discard
                Some(Some(pat @ (UntypedPattern::Var { .. } | UntypedPattern::Discard { .. }))) => {
                    Some(pat)
                }
                Some(Some(pat)) => {
                    emit(ParseError::expected_input_found(
                        pat.location(),
                        None,
                        Some(error::Pattern::Match),
                    ));

                    Some(pat)
                }
                // There is a tail but it has no content, implicit discard
                Some(None) => Some(UntypedPattern::Discard {
                    location: ast::Span {
                        start: span.end - 1,
                        end: span.end,
                    },
                    name: "_".to_string(),
                }),
                // No tail specified
                None => None,
            };

            UntypedPattern::List {
                location: span,
                elements,
                tail: tail.map(Box::new),
            }
        })
}
