use std::iter;

use codespan_reporting::{
	diagnostic::{Diagnostic, Label, Severity},
	files::Files,
	term::{emit, Config},
};
use parser::source_map::FileSystem;

pub(crate) fn emit_ezno_diagnostic(
	diagnostic: checker::Diagnostic,
	fs: &impl FileSystem,
) -> Result<(), codespan_reporting::files::Error> {
	let diagnostic = match diagnostic {
		checker::Diagnostic::Global { reason, kind } => Diagnostic {
			severity: ezno_diagnostic_to_severity(&kind),
			code: None,
			message: reason,
			labels: Vec::new(),
			notes: Vec::default(),
		},
		checker::Diagnostic::Position { reason, position, kind } => Diagnostic {
			severity: ezno_diagnostic_to_severity(&kind),
			code: None,
			message: Default::default(),
			labels: vec![Label::primary(position.source, position).with_message(reason)],
			notes: Vec::default(),
		},
		checker::Diagnostic::PositionWithAdditionalLabels { reason, position, labels, kind } => {
			let (labels, notes) =
				labels.into_iter().partition::<Vec<_>, _>(|(_, value)| value.is_some());

			Diagnostic {
				severity: ezno_diagnostic_to_severity(&kind),
				code: None,
				message: Default::default(),
				labels: iter::once(Label::primary(position.source, position).with_message(reason))
					.chain(labels.into_iter().map(|(message, position)| {
						let position = position.unwrap();
						Label::secondary(position.source, position).with_message(message)
					}))
					.collect(),
				notes: notes.into_iter().map(|(message, _)| message).collect(),
			}
		}
	};

	emit_diagnostic(&diagnostic, &fs.into_code_span_store())
}

fn ezno_diagnostic_to_severity(kind: &checker::DiagnosticKind) -> Severity {
	match kind {
		checker::DiagnosticKind::Error => Severity::Error,
		checker::DiagnosticKind::Warning => Severity::Warning,
		checker::DiagnosticKind::Info => Severity::Note,
	}
}

#[cfg(target_family = "wasm")]
fn emit_diagnostic<'files, F: Files<'files>>(
	diagnostic: &Diagnostic<<F as Files<'files>>::FileId>,
	files: &'files F,
) -> Result<(), codespan_reporting::files::Error> {
	use crate::utilities::print_to_cli;
	use codespan_reporting::term::termcolor::Buffer;

	let config = Config::default();

	let mut buffer = Buffer::ansi();
	emit(&mut buffer, &config, files, diagnostic).unwrap();
	let output = String::from_utf8(buffer.into_inner()).expect("invalid string from diagnostic");
	print_to_cli(format_args!("{output}"));
	Ok(())
}

#[cfg(not(target_family = "wasm"))]
fn emit_diagnostic<'files, F: Files<'files>>(
	diagnostic: &Diagnostic<<F as Files<'files>>::FileId>,
	files: &'files F,
) -> Result<(), codespan_reporting::files::Error> {
	use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};

	let config = Config::default();

	let writer = StandardStream::stderr(ColorChoice::Auto);
	let mut lock = writer.lock();
	emit(&mut lock, &config, files, diagnostic)
}
