use std::fmt;

use pest::Span;
use std::fmt::Write;

use crate::ast::StringSpan;

#[derive(Debug, Clone)]
pub struct ValidationError {
    text: String,
    source_ref: Option<StringSpan>,
}

impl ValidationError {
    pub fn new(text: String, span: Option<StringSpan>) -> Self {
        Self {
            text,
            source_ref: span,
        }
    }

    pub fn as_error_message(&self, f: &mut String, input: &str) -> fmt::Result {
        if let Some(source_ref) = self.source_ref {
            let span = Span::new(input, source_ref.pos_start, source_ref.pos_end);
            let (line, col) = span.as_ref().unwrap().start_pos().line_col();
            write!(f, "Line {}, Column {}: Error: {}", line, col, self.text)
        } else {
            write!(f, "Error: {}", self.text)
        }
    }
}

impl fmt::Display for ValidationError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if let Some(source_ref) = self.source_ref {
            write!(f, "{}: Error: {}", source_ref, self.text)
        } else {
            write!(f, "Error: {}", self.text)
        }
    }
}

#[derive(Debug, Clone, Default)]
pub struct ValidationErrors {
    errors: Vec<ValidationError>,
}

impl ValidationErrors {
    pub fn push(&mut self, new: ValidationError) {
        self.errors.push(new);
    }

    pub fn extend(&mut self, new: Vec<ValidationError>) {
        self.errors.extend(new)
    }

    pub fn is_empty(&self) -> bool {
        self.errors.is_empty()
    }
    pub fn len(&self) -> usize {
        self.errors.len()
    }
    pub fn as_error_message(&self, input: &str) -> String {
        let mut buf = "\n".to_string();
        for err in &self.errors {
            err.as_error_message(&mut buf, input).unwrap();
            writeln!(buf).unwrap();
        }
        buf
    }

    pub fn has_error_contains(&self, text: &str) -> bool {
        self.errors.iter().any(|err| err.text.contains(text))
    }
}

impl fmt::Display for ValidationErrors {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for err in &self.errors {
            write!(f, "{err}")?;
        }
        Ok(())
    }
}
