use egui::text::{CCursorRange, LayoutJob};

use crate::{
    markdown::Heading,
    text_styles::{H1, H1_MONO, H2, H2_MONO, H3, H3_MONO, H4, H4_MONO, H5, H5_MONO, H6, H6_MONO},
};

use super::{Item, Style, parse};

/// Highlight markdown, caching previous output to save CPU.
#[derive(Default)]
pub struct MemoizedHighlighter {
    style: egui::Style,
    code: String,
    output: LayoutJob,
}

impl MemoizedHighlighter {
    pub fn highlight(
        &mut self,
        egui_style: &egui::Style,
        code: &str,
        cursor: Option<CCursorRange>,
    ) -> LayoutJob {
        if (&self.style, self.code.as_str()) != (egui_style, code) {
            self.style = egui_style.clone();
            code.clone_into(&mut self.code);
            self.output = highlight_markdown(egui_style, code, cursor);
        }
        self.output.clone()
    }
}

pub fn highlight_markdown(
    egui_style: &egui::Style,
    text: &str,

    // TODO: hide special characters where cursor isn't
    _cursor: Option<CCursorRange>,
) -> LayoutJob {
    let mut job = LayoutJob::default();
    let code_style = Style {
        code: true,
        ..Default::default()
    };

    for item in parse(text) {
        match item {
            Item::Text { span, style } => {
                job.append(&span, 0.0, format_from_style(egui_style, &style));
            }
            Item::CodeBlock {
                all,
                language: _, // TODO
                code: _,     // TODO
            } => {
                job.append(&all, 100.0, format_from_style(egui_style, &code_style));
            }
        }
    }

    job
}

fn format_from_style(egui_style: &egui::Style, style: &Style) -> egui::text::TextFormat {
    use egui::{Align, Color32, Stroke, TextStyle};

    let color = if style.code {
        egui_style.visuals.strong_text_color() * Color32::GREEN
    } else if style.strong || style.heading.is_some() {
        egui_style.visuals.strong_text_color()
    } else if style.quoted {
        egui_style.visuals.weak_text_color()
    } else {
        egui_style.visuals.text_color()
    };

    let text_style = if let Some(heading) = style.heading {
        let text_style = match (heading, style.code) {
            (Heading::H1, false) => H1,
            (Heading::H2, false) => H2,
            (Heading::H3, false) => H3,
            (Heading::H4, false) => H4,
            (Heading::H5, false) => H5,
            (Heading::H6, false) => H6,

            (Heading::H1, true) => H1_MONO,
            (Heading::H2, true) => H2_MONO,
            (Heading::H3, true) => H3_MONO,
            (Heading::H4, true) => H4_MONO,
            (Heading::H5, true) => H5_MONO,
            (Heading::H6, true) => H6_MONO,
        };

        TextStyle::Name(text_style.into())
    } else if style.code {
        TextStyle::Monospace
    } else if style.small | style.raised {
        TextStyle::Small
    } else {
        TextStyle::Body
    };

    let background = if style.code {
        egui_style.visuals.code_bg_color
    } else {
        Color32::TRANSPARENT
    };

    let underline = if style.underline {
        Stroke::new(1.0, color)
    } else {
        Stroke::NONE
    };

    let strikethrough = if style.strikethrough {
        Stroke::new(1.0, color)
    } else {
        Stroke::NONE
    };

    let valign = if style.raised {
        Align::TOP
    } else {
        Align::BOTTOM
    };

    egui::text::TextFormat {
        font_id: text_style.resolve(egui_style),
        color,
        background,
        italics: style.italics,
        underline,
        strikethrough,
        valign,
        ..Default::default()
    }
}
