use super::{
    and_or_chain, anonymous_binop::parser as anonymous_binop,
    anonymous_function::parser as anonymous_function, assignment, block::parser as block,
    bytearray::parser as bytearray, if_else::parser as if_else, int::parser as int,
    list::parser as list, pair::parser as pair, record::parser as record,
    record_update::parser as record_update, string::parser as string, tuple::parser as tuple,
    var::parser as var, when::parser as when,
};
use crate::{
    expr::UntypedExpr,
    parser::{
        chain::{Chain, call::parser as call, field_access, tuple_index::parser as tuple_index},
        error::ParseError,
        token::Token,
    },
};
use chumsky::prelude::*;

pub fn parser<'a>(
    sequence: Recursive<'a, Token, UntypedExpr, ParseError>,
    expression: Recursive<'a, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + 'a {
    let chain = choice((
        tuple_index(),
        field_access::parser(),
        call(expression.clone()),
    ));

    chain_start(sequence, expression)
        .then(chain.repeated())
        .foldl(|expr, chain| match chain {
            Chain::Call(args, span) => expr.call(args, span),
            Chain::FieldAccess(label, span) => expr.field_access(label, span),
            Chain::TupleIndex(index, span) => expr.tuple_index(index, span),
        })
        .then(just(Token::Question).or_not())
        .map_with_span(|(value, token), location| match token {
            Some(_) => UntypedExpr::TraceIfFalse {
                value: Box::new(value),
                location,
            },
            None => value,
        })
}

pub fn chain_start<'a>(
    sequence: Recursive<'a, Token, UntypedExpr, ParseError>,
    expression: Recursive<'a, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + 'a {
    choice((
        string(),
        int(),
        pair(expression.clone()),
        record_update(expression.clone()),
        record(expression.clone()),
        and_or_chain(expression.clone()),
        var(),
        tuple(expression.clone()),
        bytearray(),
        list(expression.clone()),
        anonymous_function(sequence.clone()),
        anonymous_binop(),
        block(sequence.clone()),
        when(expression.clone()),
        assignment::let_(expression.clone()),
        assignment::expect(expression.clone()),
        if_else(sequence, expression.clone()),
    ))
}
