use nom::{
    IResult, Parser,
    branch::alt,
    character::complete::{anychar, char, one_of},
    combinator::{map, not, peek, recognize, value},
    multi::many1,
    sequence::preceded,
};

use crate::ast::Inline;

pub(super) fn text<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, Inline> {
    move |input: &'a str| {
        map(
            many1(alt((
                map(escaped_char, |c| c.to_string()),
                crate::parser::inline::html_entity::html_entity(),
                map(recognize(many1(preceded(peek(is_text()), anychar))), |c| {
                    c.to_string()
                }),
            ))),
            |vec| Inline::Text(vec.join("")),
        )
        .parse(input)
    }
}

fn is_text<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, ()> {
    move |input: &'a str| not(not_a_text()).parse(input)
}

fn not_a_text<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, Vec<()>> {
    move |input: &'a str| {
        alt((
            alt((
                value((), crate::parser::inline::wikilink::wikilink()),
                value((), crate::parser::inline::autolink::autolink),
                value((), crate::parser::inline::reference_link::reference_link()),
                value((), crate::parser::inline::hard_newline::hard_newline),
                value((), crate::parser::inline::html_entity::html_entity()),
                value((), crate::parser::inline::image::image()),
            )),
            alt((
                value((), crate::parser::inline::inline_link::inline_link()),
                value((), crate::parser::inline::code_span::code_span),
                value((), crate::parser::inline::emphasis::emphasis()),
                value(
                    (),
                    crate::parser::inline::footnote_reference::footnote_reference,
                ),
                value((), crate::parser::inline::strikethrough::strikethrough()),
            )),
            value(
                (),
                crate::parser::inline::environment_variable::environment_variable,
            ),
            value((), crate::parser::inline::tag::tag()),
        ))
        .map(|v| vec![v])
        .parse(input)
    }
}

fn escaped_char(input: &str) -> IResult<&str, char> {
    preceded(char('\\'), one_of("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")).parse(input)
}
