use chumsky::{
    container::Container,
    extra,
    input::MappedSpan,
    primitive::{any, choice, empty, just, map_ctx, one_of},
    span::SimpleSpan,
    text::{inline_whitespace, newline},
    ConfigIterParser, IterParser, Parser,
};
use either::Either;

use crate::{
    __internal__parser,
    ast::{
        CurlyKind, Expression, IndentationMarker, Template, TemplateBody, TemplateContent,
        TemplateContentEscapeSequence, TemplateLine, TemplateLineNewline, Type,
    },
    parse::{
        capturing_newline, escape_sequence, ignore_ctx, map_err_missing_delimiter, with_span,
        DelimiterType, Error,
    },
    Span,
};

use super::{
    command::command_or_comment, command_whitespace_or_comments, ident,
    source_file_whitespace_or_comments, ty::ty,
};

#[cfg(test)]
mod test;

// chumsky parser types tend to get a little big, but this is still fine
#[allow(clippy::type_complexity)]
pub fn struct_parameters_common<'s, F: Fn(SimpleSpan) -> Span<'s> + 's>(
    ty: impl Parser<'s, MappedSpan<Span<'s>, &'s str, F>, Type<'s>, extra::Err<Error<'s>>> + Clone,
) -> impl Parser<
    's,
    MappedSpan<Span<'s>, &'s str, F>,
    Vec<((&'s str, Span<'s>), (Option<Type<'s>>, Span<'s>))>,
    extra::Err<Error<'s>>,
> + Clone {
    with_span(ident())
        .then(
            just(":")
                .padded_by(source_file_whitespace_or_comments())
                .ignore_then(with_span(ty.map(Some)))
                .or(with_span(empty().to(None))),
        )
        .separated_by(just(',').padded_by(source_file_whitespace_or_comments()))
        .allow_trailing()
        .collect::<Vec<((&'s str, Span<'s>), (Option<Type<'s>>, Span<'s>))>>()
        .padded_by(source_file_whitespace_or_comments())
}

__internal__parser! {parameters, Vec<((&'s str, Span<'s>), (Option<Type<'s>>, Span<'s>))>, {
    map_err_missing_delimiter(
        just('(').to_span(),
        struct_parameters_common(ty()),
        just(')'),
        DelimiterType::ParameterList
        )
}}

__internal__parser! {pub body(expression: impl Parser<'s, MappedSpan<Span<'s>, &'s str, F>, Expression<'s>, extra::Err<Error<'s>>> + Clone), TemplateBody<'s>, {
    with_span(just("{").repeated().at_least(1).count())
        .then_ignore(inline_whitespace())
        .then_with_ctx(newline().or_not().try_map_with(|newline_or_not, extra| {
            let (_, template_start) = extra.ctx();
            newline_or_not.ok_or(Error::MissingTemplateBodyStartNewline { template_start: *template_start, expected_newline_span: extra.span() })
        }))
        // at the time of writing there is no `then_ignore_with_ctx`
        .map(|(values, ())| values)
        .then(indentation_marker())
        .then_with_ctx(
            map_ctx::<
                _,
                Vec<(TemplateLine<'_>, Span<'s>)>,
                MappedSpan<Span<'s>, &'s str, F>,
                extra::Full<Error, (), ((usize, Span<'s>), _)>,
                extra::Full<Error, (), (usize, Span<'s>)>, _>
                    (|(start_and_span, _)| *start_and_span,
                        with_span(template_line(expression)).repeated().collect::<Vec<(TemplateLine, Span<'s>)>>()
                    )
        )
        .then_ignore(inline_whitespace())
        .then_with_ctx(choice((just("}").repeated().at_least(1).configure(|cfg, (((curly_count, _), _), _)| cfg.exactly(*curly_count)).to(Some(())), empty().to(None))))
        .try_map(|
            ((((curly_count, start), indentation_marker), lines), end_or_not),
            _| match end_or_not {
                Some(()) => Ok(TemplateBody{indentation_marker, lines}),
                None => Err(Error::UnclosedDelimiter { start, kind: DelimiterType::TemplateBody { curly_count } })
            })
}}

__internal__parser! {indentation_marker, IndentationMarker<'s>, {
    choice((
            with_span(indentation()).then(just('>').to_span()).then_ignore(newline()).map(|(indentation_and_span, _marker_span)| IndentationMarker::Present{ indentation_and_span }),
            with_span(indentation()).then(just("\\>").to_span()).then(with_span(capturing_newline())).map(|((indentation_and_span, marker_span), newline)| IndentationMarker::Escaped{ indentation_and_span, marker_span, newline }),
            empty().to(IndentationMarker::None),
           ))
}}

__internal__parser! {indentation, &'s str, {
    any().and_is(one_of(" \t")).repeated().to_slice()
}}

__internal__parser! {template_line(expression: impl Parser<'s, MappedSpan<Span<'s>, &'s str, F>, Expression<'s>, extra::Err<Error<'s>>> + Clone), TemplateLine<'s>, extra::Full<Error<'s>, (), (usize, Span<'s>)>, {
    #[derive(Default)]
    struct TemplateContentCommentlessContainer<'s> {
        had_comments: bool,
        template_content: Vec<(TemplateContent<'s>, Span<'s>)>,
    }

    impl<'s> Container<Option<(TemplateContent<'s>, Span<'s>)>> for TemplateContentCommentlessContainer<'s> {
        fn push(&mut self, item: Option<(TemplateContent<'s>, Span<'s>)>) {
            self.had_comments |= item.is_none();
            if item.is_none() {
                self.had_comments = true;
            }
            self.template_content.extend(item);
        }

        fn with_capacity(n: usize) -> Self {
            Self { template_content: Vec::with_capacity(n), had_comments: false}
        }
    }
    ignore_ctx(with_span(indentation()))
        .then(with_span(template_content_or_comment(expression)).map(|(content, span)| content.map(|content| (content, span))).repeated().collect::<TemplateContentCommentlessContainer>())
        .then(ignore_ctx(with_span(template_line_newline())))
        .map(|((indentation, TemplateContentCommentlessContainer{template_content, had_comments}), newline)| TemplateLine{indentation, content: template_content, newline, had_comments})
}}

__internal__parser! {template_line_newline, TemplateLineNewline<'s>, extra::Err<Error<'s>>, {
    choice((
            capturing_newline().map(TemplateLineNewline::Normal),
            just("\\").then(newline()).to(TemplateLineNewline::Escaped),
    ))
}}

__internal__parser! {template_content_or_comment(expression: impl Parser<'s, MappedSpan<Span<'s>, &'s str, F>, Expression<'s>, extra::Err<Error<'s>>> + Clone), Option<TemplateContent<'s>>, extra::Full<Error<'s>, (), (usize, Span<'s>)>, {
    choice((
        template_content_escape_sequence().map(TemplateContent::EscapeSequence).map(Some),
        command_or_comment(expression).map(|command| command.map(Box::new).map(TemplateContent::Command)),
        map_ctx(|(brace_count, _): &(usize, Span<'s>)| *brace_count, any::<'_, _, extra::Full<_, _, _>>().and_is(choice((
            newline(),
            just("\\").ignored(),
            just("{").repeated().configure(|cfg, brace_count| cfg.at_least(*brace_count)),
            just("}").repeated().configure(|cfg, brace_count| cfg.at_least(*brace_count)),
        )).not())).repeated().at_least(1).to_slice().map(TemplateContent::Static).map(Some),
    ))
}}

__internal__parser! {template_content_escape_sequence, TemplateContentEscapeSequence<'s>, extra::Full<Error<'s>, (), (usize, Span<'s>)>, {
    choice((
        ignore_ctx(escape_sequence()).map(TemplateContentEscapeSequence::Normal),
        just("\\").ignore_then(just("{").repeated().at_least(1).configure(|cfg, (count, _)| cfg.at_most(*count)).to_slice().map(Either::<&str, &str>::Left).or(just("}").repeated().at_least(1).configure(|cfg, (count, _)| cfg.at_most(*count)).to_slice().map(Either::Right)))
            .try_map_with(|escape_sequence_curly_slice, ctx| {
                // differentiate between escaped opening and escaped closing template braces
                let (template_curly_count, template_curly_span) = ctx.ctx();
                let (template_curly_count, template_curly_span) = (*template_curly_count, *template_curly_span);
                let (escape_sequence_curly_slice, curly_kind) = match escape_sequence_curly_slice {
                    Either::Left(curly_slice) => (curly_slice, CurlyKind::Opening),
                    Either::Right(curly_slice) => (curly_slice, CurlyKind::Closing),
                };
                // figure out if the correct amount of braces were escaped
                if escape_sequence_curly_slice.len() < template_curly_count {
                    Err(Error::WrongCurlyCountTemplateCurlyEscapeSequence { curly_kind, template_curly_span, template_curly_count, escape_sequence_span: ctx.span(), escape_sequence_curly_count: escape_sequence_curly_slice.len()})
                } else {
                    Ok(TemplateContentEscapeSequence::Curly(curly_kind, escape_sequence_curly_slice.len(), escape_sequence_curly_slice, template_curly_span))
                }
        })
    ))
}}

__internal__parser! {pub template(expression: impl Parser<'s, MappedSpan<Span<'s>, &'s str, F>, Expression<'s>, extra::Err<Error<'s>>> + Clone), Template<'s>, {
    with_span(ident().or_not()).then_ignore(command_whitespace_or_comments()).then_ignore(just("template"))
        .then(with_span(ident()).padded_by(source_file_whitespace_or_comments()))
        .then(parameters())
        .then_ignore(source_file_whitespace_or_comments())
        .then(body(expression))
        .map(|(((output_type, name), parameters), body)| Template { output_type, name, parameters, body})
}}
