use std::convert::identity;

use chumsky::{
    combinator::Repeated,
    extra,
    input::MappedSpan,
    primitive::{any, just},
    span::SimpleSpan,
    text::Char,
    IterParser, Parser,
};
use either::Either::{Left, Right};
use itertools::Itertools;

use crate::{__internal__parser, ast::SourceFile, Span};

use self::{
    comments::{source_file_comment, source_file_comment_right_delimiter},
    expression::expression,
};

use super::{with_span, Error};

mod command;
mod comments;
mod expression;
mod template;
mod ty;
mod type_definition;

#[cfg(test)]
mod test;

__internal__parser! {pub ident, &'s str, {
    chumsky::text::ident().filter(|ident| !matches!(*ident, "if" | "else" | "each" | "select" | "in" | "template" | "type"))
}}

// chumsky types tend to get complicated and since this return type was auto generated it's fine
#[allow(clippy::type_complexity)]
fn source_file_whitespace_or_comments<'s, F: Fn(SimpleSpan) -> Span<'s> + 's>() -> Repeated<
    impl Parser<'s, MappedSpan<Span<'s>, &'s str, F>, (), extra::Err<Error<'s>>> + Clone,
    (),
    MappedSpan<Span<'s>, &'s str, F>,
    extra::Err<Error<'s>>,
> {
    source_file_comment()
        .or(any().filter(|ch: &char| ch.is_whitespace()).ignored())
        .repeated()
}

// same here
#[allow(clippy::type_complexity)]
fn command_whitespace_or_comments<'s, F: Fn(SimpleSpan) -> Span<'s> + 's>() -> Repeated<
    impl Parser<'s, MappedSpan<Span<'s>, &'s str, F>, (), extra::Err<Error<'s>>> + Clone,
    (),
    MappedSpan<Span<'s>, &'s str, F>,
    extra::Err<Error<'s>>,
> {
    source_file_comment_right_delimiter(just("}").ignored())
        .or(any()
            .filter(|ch: &char| ch.is_whitespace() && *ch != '}')
            .ignored())
        .repeated()
}

__internal__parser! {pub source_file, SourceFile<'s>, {
        with_span(template::template(expression())).map(Left).or(with_span(type_definition::type_definition()).map(Right))
            .padded_by(source_file_whitespace_or_comments())
            .repeated()
            .collect::<Vec<_>>()
            .map(|templates_and_type_definitions| {
                let (templates, type_defs): (Vec<_>, Vec<_>) = templates_and_type_definitions.into_iter().partition_map(identity);

                SourceFile {
                    templates,
                    type_defs
                }
            })
}}
