use nom::{
    IResult, Parser,
    branch::alt,
    bytes::complete::tag,
    character::complete::{char, none_of},
    combinator::{opt, recognize, value, verify},
    multi::{many_m_n, many1},
    sequence::preceded,
};

use crate::{
    ast::Callout,
    parser::util::{line_terminated, not_eof_or_eol0, not_eof_or_eol1},
};

pub(super) fn callout<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, Callout> {
    move |input: &'a str| {
        let prefix = || preceded(many_m_n(0, 3, char(' ')), char('>'));

        let (input, (level, kind, title)) =
            preceded(prefix(), line_terminated(callout_header)).parse(input)?;

        let (input, lines) =
            many1(preceded(prefix(), line_terminated(not_eof_or_eol0))).parse(input)?;
        let inner = lines.join("\n");

        let (_, inner) = many1(crate::parser::blocks::block())
            .parse(&inner)
            .map_err(|err| err.map_input(|_| input))?;

        let callout = Callout {
            level,
            title,
            foldable: kind.is_some(),
            open: kind.unwrap_or(true),
            blocks: inner.into_iter().flatten().collect(),
        };

        Ok((input, callout))
    }
}

fn callout_header(input: &str) -> IResult<&str, (String, Option<bool>, Option<String>)> {
    let (input, _) = many_m_n(0, 3, char(' ')).parse(input)?;
    let (input, _) = tag("[!").parse(input)?;
    let (input, level) = recognize(many1(verify(none_of("]"), |c| *c != ']'))).parse(input)?;
    let (input, _) = tag("]").parse(input)?;
    let (input, kind) = opt(alt((value(true, tag("+")), value(false, tag("-"))))).parse(input)?;
    let (input, title) = opt(callout_title).parse(input)?;

    Ok((input, (level.to_owned(), kind, title)))
}

fn callout_title(input: &str) -> IResult<&str, String> {
    let (input, _) = many_m_n(0, 3, char(' ')).parse(input)?;
    let (input, label) = not_eof_or_eol1.parse(input)?;

    Ok((input, label.to_owned()))
}
