mod control_flow;
mod literal;
#[cfg(test)]
mod test;

use crate::{
    __internal__parser,
    ast::{Expression, MemberAccess},
    parse::{
        map_err_missing_delimiter,
        source_file::expression::{control_flow::control_flow, literal::literal},
        with_span, DelimiterType,
    },
    ParseError as Error, Span,
};

use chumsky::{
    input::MapExtra, pratt, prelude::choice, primitive::just, recursive::recursive,
    span::Span as _, text::int, Parser,
};

use super::{command_whitespace_or_comments, ident};

__internal__parser! {pub expression, Expression<'s>, {
    recursive::<'s, 's, _, Expression<'s>, _, _, _>(|expression| {
        let call = |mut left: (Expression<'s>, Span<'s>), (), right: (Expression<'s>, Span<'s>), _extra: &mut MapExtra<'s, '_, _, _>| {
                let total_span = left.1.union(right.1);
                match left.0 {
                    Expression::Call { func: _, ref mut arguments } => {
                        arguments.push(right);
                        (left.0, total_span)
                    },
                    _  => (Expression::Call{func: (Box::new(left.0), left.1), arguments: vec![right]}, total_span),
                }
            };
        let member_access_pratt =
            pratt::postfix(2, with_span(member_access()), |expression: (Expression<'s>, Span<'s>), access: (MemberAccess<'s>, Span<'s>), _extra| {
                let total_span = expression.1.union(access.1);
                (Expression::MemberAccess{expression: (Box::new(expression.0), expression.1), access},
                total_span)
            });
        recursive(|atom| with_span(choice((
            control_flow(expression.clone(), atom.pratt((member_access_pratt.clone(),)).map(|(expr, _span): (Expression<'s>, Span<'s>)| expr)), // can't have function calls since otherwise `if true 1` would be interpreted as one
            map_err_missing_delimiter(just("("), expression.clone().padded_by(command_whitespace_or_comments()), just(")"), DelimiterType::BracketedExpression),
            literal(expression).map(Expression::Literal),
            ident().map(Expression::VariableOrCall),
        ))))
        .pratt((
            pratt::infix(pratt::right(0), just("$").padded_by(command_whitespace_or_comments()).ignored(), call),
            pratt::infix(pratt::left(1), command_whitespace_or_comments(), call),
            member_access_pratt,
        )).map(|(expr, _span)| expr)
    })
}}

__internal__parser! {member_access, MemberAccess<'s>, {
    just(".").then(command_whitespace_or_comments()).ignore_then(
        int(10).try_map(|num_str: &'s str, span| match num_str.parse() {
            Ok(num) => Ok(MemberAccess::Numeric(num, span)),
            Err(err) => Err(Error::InvalidNumericMemberAccess(err, span))
        }).or(
            with_span(ident()).map(|(name, span)| MemberAccess::Named(name, span))
        )
    )
}}
