use ariadne::{Color, Label, Report, ReportKind, Source};
use lemma::error::ErrorDetails;
use lemma::Error;
use std::collections::HashMap;

fn format_details(
    error_type: &str,
    details: &ErrorDetails,
    label_message: &str,
    sources: &HashMap<lemma::SourceType, String>,
) -> String {
    let Some(ref src) = details.source else {
        return format!("{}: {}", error_type, details.message);
    };

    let source_type_str = src.source_type.to_string();

    let Some(full_content) = sources.get(&src.source_type) else {
        return format!(
            "{}: {} ({}:{})",
            error_type, details.message, source_type_str, src.span.line
        );
    };

    let mut output = Vec::new();

    let header = match details.spec_context.as_ref() {
        Some(spec) => format!(
            "{}: {} (in spec '{}', file {}:{})",
            error_type, details.message, spec.name, source_type_str, src.span.line
        ),
        None => format!(
            "{}: {} ({}:{})",
            error_type, details.message, source_type_str, src.span.line
        ),
    };

    let span = (source_type_str.as_str(), src.span.start..src.span.end);
    let mut report = Report::build(ReportKind::Error, span.clone())
        .with_message(header)
        .with_label(
            Label::new(span)
                .with_message(label_message)
                .with_color(Color::Red),
        );

    if let Some(suggestion) = &details.suggestion {
        report = report.with_help(suggestion);
    }

    let content: &str = full_content.as_str();
    if report
        .finish()
        .write(
            (source_type_str.as_str(), Source::from(content)),
            &mut output,
        )
        .is_err()
    {
        return format!(
            "{}: {} ({}:{})",
            error_type, details.message, source_type_str, src.span.line
        );
    }
    String::from_utf8_lossy(&output).to_string()
}

#[must_use]
pub fn format_error(error: &Error, sources: &HashMap<lemma::SourceType, String>) -> String {
    let fmt = |typ: &str, details: &ErrorDetails, label: &str| {
        format_details(typ, details, label, sources)
    };
    match error {
        Error::Parsing(details) => fmt("Parse error", details, ""),
        Error::Inversion(details) => fmt("Inversion error", details, ""),
        Error::Validation(details) => fmt("Validation error", details, ""),
        Error::Registry {
            details,
            identifier,
            kind,
        } => fmt(&format!("Registry error ({})", kind), details, identifier),
        Error::MissingRepository {
            details,
            repository,
        } => fmt("Missing repository", details, repository),
        Error::ResourceLimitExceeded {
            details,
            limit_name,
            limit_value,
            actual_value,
        } => fmt(
            &format!("Resource limit exceeded: {limit_name} (limit: {limit_value}, actual: {actual_value})"),
            details,
            "",
        ),
        Error::Request { details, .. } => fmt("Request error", details, ""),
    }
}
