use chumsky::{
    extra,
    input::MappedSpan,
    primitive::{choice, just},
    Parser,
};

use crate::{
    __internal__parser,
    ast::Expression,
    parse::{
        source_file::{command_whitespace_or_comments, ident},
        with_span, Error,
    },
    Span,
};

#[cfg(test)]
mod test;

__internal__parser! {pub control_flow(expression: impl Parser<'s, MappedSpan<Span<'s>, &'s str, F>, Expression<'s>, extra::Err<Error<'s>>> + Clone, expression_with_member_access_no_call: impl Parser<'s, MappedSpan<Span<'s>, &'s str, F>, Expression<'s>, extra::Err<Error<'s>>> + Clone), Expression<'s>, {
    choice((
        if_expression(expression.clone(), expression_with_member_access_no_call.clone()),
        each_expression(expression.clone(), expression_with_member_access_no_call)
    ))
}}

__internal__parser! {if_expression(expression: impl Parser<'s, MappedSpan<Span<'s>, &'s str, F>, Expression<'s>, extra::Err<Error<'s>>> + Clone, expression_with_member_access_no_call: impl Parser<'s, MappedSpan<Span<'s>, &'s str, F>, Expression<'s>, extra::Err<Error<'s>>> + Clone), Expression<'s>, {
    just("if")
        .ignore_then(
            with_span(
                expression_with_member_access_no_call
                    .map(Box::new)
            )
            .padded_by(command_whitespace_or_comments().at_least(1))
        )
        .then(with_span(expression.clone().map(Box::new)))
        .then(
            just("else")
                .padded_by(command_whitespace_or_comments().at_least(1))
                .ignore_then(with_span(expression.map(Box::new)))
                .or_not()
        )
        .map(|((condition, then_expression), else_expression)| Expression::If { condition, then_expression, else_expression })
}}

__internal__parser! {each_expression(expression: impl Parser<'s, MappedSpan<Span<'s>, &'s str, F>, Expression<'s>, extra::Err<Error<'s>>> + Clone, expression_with_member_access_no_call: impl Parser<'s, MappedSpan<Span<'s>, &'s str, F>, Expression<'s>, extra::Err<Error<'s>>> + Clone), Expression<'s>, {
    just("each")
        .ignore_then(
            with_span(ident())
                .padded_by(command_whitespace_or_comments().at_least(1))
        )
        .then_ignore(just("in"))
        .then(
            with_span(expression_with_member_access_no_call.map(Box::new))
            .padded_by(command_whitespace_or_comments().at_least(1))
        )
        .then(with_span(expression.map(Box::new)))
        .map(|((variable, array_expression), body_expression)| Expression::Each { variable, array_expression, body_expression })
}}
