use chumsky::prelude::*;

use super::anonymous_function::parser as anonymous_function;
use super::assignment;
use super::block::parser as block;
use super::bytearray::parser as bytearray;
use super::if_else::parser as if_else;
use super::int::parser as int;
use super::list::parser as list;
use super::record::parser as record;
use super::record_update::parser as record_update;
use super::string::parser as string;
use super::tuple::parser as tuple;
use super::var::parser as var;
use super::when::parser as when;
use super::{and_or_chain, anonymous_binop::parser as anonymous_binop};

use crate::{
    expr::UntypedExpr,
    parser::{
        chain::{call::parser as call, field_access, tuple_index::parser as tuple_index, Chain},
        error::ParseError,
        token::Token,
    },
};

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(),
        record_update(expression.clone()),
        record(expression.clone()),
        field_access::constructor(),
        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()),
    ))
}
