use markdown_it::parser::inline::{InlineRule, InlineState};
use markdown_it::{Node, NodeValue, Renderer};
use maud::Render;

use crate::components::mangle::{MangledBetaEmail, MangledGoogleEmail};

#[derive(Debug)]
pub struct InlineMdx(String);

impl NodeValue for InlineMdx {
	fn render(&self, node: &Node, fmt: &mut dyn Renderer) {
		if mdx_style(&self.0, node, fmt) {
			return;
		}

		if mdx_include(&self.0, node, fmt) {
			return;
		}

		fmt.open("code", &[]);
		fmt.text(&self.0);
		fmt.close("code");
	}
}

impl InlineRule for InlineMdx {
	const MARKER: char = '{';

	fn run(state: &mut InlineState<'_, '_>) -> Option<(Node, usize)> {
		let input = &state.src[state.pos..state.pos_max];
		if !input.starts_with('{') {
			return None;
		}

		let mut balance = 1;
		let mut end = 1;
		for i in input[1..].bytes() {
			match i {
				b'}' => balance -= 1,
				b'{' => balance += 1,
				_ => {}
			}

			if balance == 0 {
				break;
			}

			end += 1;
		}

		if balance != 0 {
			return None;
		}

		let content = &input[1..end];
		Some((Node::new(InlineMdx(content.to_owned())), content.len() + 2))
	}
}

fn mdx_style(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
	// Parse inside of mdx: `color(value, "text")`
	let mdx = mdx
		.trim()
		.trim_start_matches('{')
		.trim_end_matches('}')
		.trim();

	// Find the function name (everything before the opening parenthesis)
	let paren_pos = match mdx.find('(') {
		Some(x) => x,
		None => return false,
	};

	if mdx[..paren_pos].trim() != "color" {
		return false;
	};

	// Find matching closing parenthesis
	let skip = paren_pos + 1;
	let mut balance = 1;
	let mut end = skip;
	for i in mdx[skip..].bytes() {
		match i {
			b')' => balance -= 1,
			b'(' => balance += 1,
			_ => {}
		}

		if balance == 0 {
			break;
		}

		end += 1;
	}

	if balance != 0 {
		return false;
	}

	let args = mdx[skip..end].trim();

	// Parse arguments: should be "value, text" or "value, \"text\""
	let comma_pos = match args.find(',') {
		Some(x) => x,
		None => return false,
	};

	let value = args[..comma_pos].trim();
	let text = args[comma_pos + 1..].trim();

	// Strip quotes from text if present
	let text = if (text.starts_with('"') && text.ends_with('"'))
		|| (text.starts_with('\'') && text.ends_with('\''))
	{
		&text[1..text.len() - 1]
	} else {
		text
	};

	let mut style_str = String::new();

	if value.starts_with("#") {
		style_str.push_str("color:");
		style_str.push_str(value);
		style_str.push(';');
	} else if value.starts_with("--") {
		style_str.push_str("color:var(");
		style_str.push_str(value);
		style_str.push_str(");");
	} else {
		style_str.push_str("color:");
		style_str.push_str(value);
		style_str.push(';');
	}

	fmt.open("span", &[("style", style_str)]);
	fmt.text(text);
	fmt.close("span");

	return true;
}

fn mdx_include(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
	// Parse inside of mdx: `include(<args>)`
	let args = {
		let mdx = mdx
			.trim()
			.trim_start_matches('{')
			.trim_end_matches('}')
			.trim();

		if !mdx.starts_with("include(") {
			return false;
		}

		let skip = 8;
		let mut balance = 1;
		let mut end = skip;
		for i in mdx[skip..].bytes() {
			match i {
				b')' => balance -= 1,
				b'(' => balance += 1,
				_ => {}
			}

			if balance == 0 {
				break;
			}

			end += 1;
		}

		if balance != 0 {
			return false;
		}

		let args = mdx[skip..end].trim();
		let trail = mdx[end + 1..].trim();
		if !trail.is_empty() {
			return false;
		}

		args
	};

	let str = match args {
		"email_beta" => MangledBetaEmail {}.render().0,
		"email_goog" => MangledGoogleEmail {}.render().0,
		_ => return false,
	};

	fmt.text_raw(&str);

	return true;
}
