mod autolink;
mod code_span;
mod emphasis;
mod environment_variable;
mod footnote_reference;
mod hard_newline;
mod html_entity;
mod image;
mod inline_link;
mod reference_link;
mod strikethrough;
mod tag;
mod text;
mod wikilink;

#[cfg(test)]
mod tests;

use nom::{
    IResult, Parser,
    branch::alt,
    combinator::map,
    multi::{many0, many1},
};

use crate::ast::Inline;

/// Merges consecutive Text elements into a single Text element
fn merge_consecutive_text_elements(inlines: Vec<Inline>) -> Vec<Inline> {
    let mut result = Vec::new();
    let mut current_text = String::new();
    let mut has_text = false;

    for inline in inlines {
        match inline {
            Inline::Text(text) => {
                current_text.push_str(&text);
                has_text = true;
            }
            other => {
                // If we have accumulated text, add it to result
                if has_text {
                    result.push(Inline::Text(current_text.clone()));
                    current_text.clear();
                    has_text = false;
                }
                // Add the non-text element
                result.push(other);
            }
        }
    }

    // Don't forget the last accumulated text
    if has_text {
        result.push(Inline::Text(current_text));
    }

    result
}

pub(super) fn inline_many0<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, Vec<Inline>> {
    move |input: &'a str| {
        let (input, list_of_lists) = many0(inline()).parse(input)?;
        let r: Vec<_> = list_of_lists.into_iter().flatten().collect();
        let merged = merge_consecutive_text_elements(r);
        Ok((input, merged))
    }
}

pub(super) fn inline_many1<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, Vec<Inline>> {
    move |input: &'a str| {
        let (input, list_of_lists) = many1(inline()).parse(input)?;
        let r: Vec<_> = list_of_lists.into_iter().flatten().collect();
        let merged = merge_consecutive_text_elements(r);
        Ok((input, merged))
    }
}

pub(super) fn inline<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, Vec<Inline>> {
    move |input: &'a str| {
        alt((
            map(wikilink::wikilink(), Inline::Wikilink),
            map(autolink::autolink, Inline::Autolink),
            map(inline_link::inline_link(), Inline::Link),
            footnote_reference::footnote_reference,
            reference_link::reference_link(),
            hard_newline::hard_newline,
            image::image(),
            map(code_span::code_span, Inline::Code),
            environment_variable::environment_variable,
            emphasis::emphasis(),
            strikethrough::strikethrough(),
            map(tag::tag(), Inline::Tag),
            text::text(),
        ))
        .map(|v| vec![v])
        .parse(input)
    }
}
