use crate::ast::{CodeBlock, CodeBlockKind};

// TODO: maybe combine the support lang tokens for all highlighters

#[cfg(feature = "highlighting-inkjet")]
pub(super) fn highlight(block: &CodeBlock) -> String {
    use std::cell::RefCell;

    use inkjet::{Highlighter, Language, formatter::Html};

    let language = match &block.kind {
        CodeBlockKind::Indented => Language::Plaintext,
        CodeBlockKind::Fenced { info } => info
            .as_deref()
            .and_then(Language::from_token)
            .unwrap_or(Language::Plaintext),
    };

    let mut buf = String::new();

    thread_local! {
        static HIGHLIGHTER: RefCell<Highlighter> = RefCell::new(Highlighter::new());
    }

    // TODO: return error probably
    HIGHLIGHTER.with(|this| {
        this.borrow_mut()
            .highlight_to_fmt(language, &Html, &block.literal, &mut buf)
            .expect("failed to write highlighted code");
    });

    buf
}

#[cfg(feature = "highlighting-syntect")]
pub fn highlight(block: &CodeBlock) -> String {
    use std::sync::LazyLock;

    use syntect::{
        html::{ClassStyle, ClassedHTMLGenerator},
        parsing::SyntaxSet,
        util::LinesWithEndings,
    };

    static SET: LazyLock<SyntaxSet> = LazyLock::new(SyntaxSet::load_defaults_newlines);

    let syntax_ref = match &block.kind {
        CodeBlockKind::Indented => SET.find_syntax_plain_text(),
        CodeBlockKind::Fenced { info } => info
            .as_deref()
            .and_then(|token| SET.find_syntax_by_token(token))
            .unwrap_or_else(|| SET.find_syntax_plain_text()),
    };

    let mut rs_html_generator =
        ClassedHTMLGenerator::new_with_class_style(syntax_ref, &SET, ClassStyle::Spaced);

    for line in LinesWithEndings::from(&block.literal) {
        rs_html_generator
            .parse_html_for_line_which_includes_newline(line)
            .unwrap();
    }

    rs_html_generator.finalize()
}

#[cfg(not(any(feature = "highlighting-inkjet", feature = "highlighting-syntect")))]
pub fn highlight(block: &CodeBlock) -> String {
    crate::util::escape(&block.literal)
}
