use crate::{ErrorType, Kind, Span};
use alloc::{string::String, vec::Vec};
use ariadne::{Config, Label, Report, ReportKind};
use core::{fmt, iter};
use std::io;

impl From<Kind> for ReportKind<'_> {
    fn from(kind: Kind) -> Self {
        match kind {
            Kind::Error => ReportKind::Error,
            Kind::Warn => ReportKind::Warning,
        }
    }
}

pub(crate) struct SpanWrapper<T>(T);

impl<T: Span> ariadne::Span for SpanWrapper<T> {
    type SourceId = T::Uri;

    fn source(&self) -> &Self::SourceId {
        self.0.uri()
    }
    fn start(&self) -> usize {
        Span::start(&self.0)
    }
    fn end(&self) -> usize {
        Span::end(&self.0)
    }
}

type SourceEntry<T> = (
    <<T as ErrorType>::Span as Span>::Uri,
    ariadne::Source<<<T as ErrorType>::Span as Span>::Source>,
);

struct Cache<T: ErrorType + ?Sized> {
    sources: Vec<SourceEntry<T>>,
}

impl<T: ErrorType + ?Sized> FromIterator<T::Span> for Cache<T> {
    fn from_iter<I: IntoIterator<Item = T::Span>>(iter: I) -> Self {
        let sources = iter
            .into_iter()
            .map(
                |span| -> (
                    <T::Span as Span>::Uri,
                    ariadne::Source<<T::Span as Span>::Source>,
                ) {
                    (
                        span.uri().clone(),
                        ariadne::Source::from(span.source_text().clone()),
                    )
                },
            )
            .collect();
        Self { sources }
    }
}

impl<T: ErrorType + ?Sized> ariadne::Cache<<T::Span as Span>::Uri> for Cache<T> {
    type Storage = <T::Span as Span>::Source;

    fn fetch(
        &mut self,
        id: &<T::Span as Span>::Uri,
    ) -> Result<&ariadne::Source<Self::Storage>, impl fmt::Debug> {
        self.sources
            .iter()
            .find(|(uri, _)| uri == id)
            .map(|(_, source)| source)
            .ok_or("Source not found")
    }

    fn display<'a>(&self, id: &'a <T::Span as Span>::Uri) -> Option<impl fmt::Display + 'a> {
        self.sources
            .iter()
            .find(|(uri, _)| uri == id)
            .map(|(uri, _)| uri)
            .cloned()
    }
}

pub(crate) fn to_ariadne_report<T: ErrorType + ?Sized>(
    error: &T,
    buf: &mut impl io::Write,
    config: Config,
) -> Result<(), io::Error> {
    let primary_span = error.primary_span().unwrap_or_default();
    let primary_message = error.primary_message();
    let cache: Cache<T> = Cache::from_iter(iter::once(primary_span.clone()));
    let mut builder = Report::build(error.kind().into(), SpanWrapper(primary_span.clone()))
        .with_code(error.code())
        .with_message(primary_message)
        .with_label(Label::new(SpanWrapper(primary_span)).with_message(error.primary_label()))
        .with_config(config);
    for (_, _, message) in error.additional() {
        builder = builder.with_note(message);
    }
    builder.finish().write(cache, buf)
}
pub(crate) fn fmt_as_ariadne_report<T: ErrorType + ?Sized>(
    error: &T,
    config: Config,
) -> Result<String, io::Error> {
    let mut result = Vec::new();
    to_ariadne_report(error, &mut result, config)?;
    String::from_utf8(result).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
