use std::fmt;
use gramatika::{DebugLisp, Parse, ParseStreamer, Span, Spanned, SpannedError, Substr, Token as _};
use crate::{token::comment_start, utils::join_substrs, ParseStream, Token, TokenKind};
#[derive(DebugLisp)]
pub enum Comment {
	Line(LineComment),
	Block(BlockComment),
}
#[derive(DebugLisp)]
pub struct LineComment {
	pub start: Token,
	pub text: Text,
}
#[derive(DebugLisp)]
pub struct BlockComment {
	pub start: Token,
	pub children: Vec<BlockCommentChild>,
	pub end: Token,
}
#[derive(DebugLisp)]
pub enum BlockCommentChild {
	Text(Text),
	Comment(BlockComment),
}
pub struct Text(pub Substr, pub Span);
impl Text {
	pub fn content(&self) -> &str {
		self.0.as_str()
	}
	pub fn append(&mut self, token: Token) {
		self.0 = join_substrs(&self.0, &token.lexeme());
		self.1 = self.1.through(token.span());
	}
	pub fn concat(mut self, token: Token) -> Self {
		self.0 = join_substrs(&self.0, &token.lexeme());
		self.1 = self.1.through(token.span());
		self
	}
}
impl From<Token> for Text {
	fn from(value: Token) -> Self {
		Self(value.lexeme(), value.span())
	}
}
impl Spanned for Text {
	fn span(&self) -> Span {
		self.1
	}
}
impl DebugLisp for Text {
	fn fmt(&self, f: &mut fmt::Formatter, indent: usize) -> fmt::Result {
		let span = self.span();
		if span.end.line > span.start.line && f.alternate() {
			writeln!(f, r#"(Text """"#)?;
			for (idx, line) in self.0.lines().enumerate() {
				writeln!(
					f,
					"{}{:>4} | {line}",
					gramatika::debug::INDENT.repeat(indent),
					span.start.line + idx + 1,
				)?;
			}
			write!(f, r#"{}""")"#, gramatika::debug::INDENT.repeat(indent))?;
		} else if f.alternate() {
			writeln!(f, r#"(Text"#)?;
			writeln!(
				f,
				r#"{}`{}` ({span:?})"#,
				gramatika::debug::INDENT.repeat(indent + 1),
				self.content(),
			)?;
			write!(f, "{})", gramatika::debug::INDENT.repeat(indent))?;
		} else {
			write!(f, r#"(Text `{}` ({span:?}))"#, self.content())?;
		}
		Ok(())
	}
}
impl fmt::Debug for Text {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		DebugLisp::fmt(self, f, 0)
	}
}
impl fmt::Display for Text {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		write!(f, "{}", self.content())
	}
}
impl Parse for Comment {
	type Stream = ParseStream;
	fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
		use TokenKind::*;
		match input.peek() {
			Some(token) => match token.as_matchable() {
				(CommentStart, "//", _) => Ok(Comment::Line(input.parse()?)),
				(CommentStart, "/*", _) => Ok(Comment::Block(input.parse()?)),
				(_, _, span) => Err(SpannedError {
					message: "Expected `//` or `/*`".into(),
					source: input.source(),
					span: Some(span),
				}),
			},
			None => Err(SpannedError {
				message: "Unexpected end of input".into(),
				source: input.source(),
				span: None,
			}),
		}
	}
}
impl Parse for LineComment {
	type Stream = ParseStream;
	fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
		let start = input.consume(comment_start!["//"])?;
		let start_span = start.span();
		let text = match text_until(input, |peeked| {
			peeked.span().start.line > start_span.start.line
		}) {
			Some(text) => text,
			None => {
				let content = Substr::new();
				let span = Span {
					start: start_span.end,
					end: start_span.end,
				};
				Text(content, span)
			}
		};
		Ok(Self { start, text })
	}
}
impl Parse for BlockComment {
	type Stream = ParseStream;
	fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
		let start = input.consume(comment_start!["/*"])?;
		let start_span = start.span();
		let mut children: Vec<BlockCommentChild> = vec![];
		loop {
			use TokenKind::*;
			match input.peek().map(|peeked| peeked.as_matchable()) {
				Some((CommentStart, "/*", _)) => {
					children.push(BlockCommentChild::Comment(input.parse()?));
				}
				Some((CommentEnd, "*/", _)) => {
					let end = input.next().unwrap();
					break Ok(Self {
						start,
						children,
						end,
					});
				}
				Some(_) => {
					let text = match text_until(input, |peeked| {
						matches!(
							peeked.as_matchable(),
							(CommentStart, "/*", _) | (CommentEnd, _, _)
						)
					}) {
						Some(text) => text,
						None => {
							let content = Substr::new();
							let span = Span {
								start: start_span.end,
								end: start_span.end,
							};
							Text(content, span)
						}
					};
					children.push(BlockCommentChild::Text(text));
				}
				None => {
					return Err(SpannedError {
						message: "Unexpected end of input".into(),
						source: input.source(),
						span: None,
					});
				}
			}
		}
	}
}
impl Spanned for Comment {
	fn span(&self) -> Span {
		match self {
			Comment::Line(inner) => inner.span(),
			Comment::Block(inner) => inner.span(),
		}
	}
}
impl Spanned for BlockComment {
	fn span(&self) -> Span {
		self.start.span().through(self.end.span())
	}
}
impl Spanned for LineComment {
	fn span(&self) -> Span {
		self.start.span().through(self.text.span())
	}
}
fn text_until<F>(input: &mut ParseStream, end_condition: F) -> Option<Text>
where F: Fn(&Token) -> bool {
	let mut result: Option<Text> = None;
	loop {
		match input.peek() {
			Some(token) if !end_condition(token) => {
				let kind = token.kind();
				let token = input.consume_as(kind, Token::plain).unwrap();
				result = match result.take() {
					Some(text) => Some(text.concat(token)),
					None => Some(Text::from(token)),
				}
			}
			_ => {
				break result;
			}
		}
	}
}