use rspack_error::miette;

use crate::fs::{BatchFSError, FSError};

#[derive(Debug)]
pub struct InvalidDetail {
  pub reason: String,
  pub packs: Vec<String>,
}

#[derive(Debug)]
pub enum ValidateResult {
  NotExists,
  Valid,
  Invalid(InvalidDetail),
}

impl ValidateResult {
  pub fn invalid(reason: &str) -> Self {
    Self::Invalid(InvalidDetail {
      reason: reason.to_string(),
      packs: vec![],
    })
  }
  pub fn invalid_with_packs(reason: &str, packs: Vec<String>) -> Self {
    Self::Invalid(InvalidDetail {
      reason: reason.to_string(),
      packs,
    })
  }
  pub fn is_valid(&self) -> bool {
    matches!(self, ValidateResult::Valid)
  }
}

#[derive(Debug)]
pub enum ErrorReason {
  Reason(String),
  Detail(InvalidDetail),
  Error(Box<dyn std::error::Error + Send + Sync>),
}

impl std::fmt::Display for ErrorReason {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    match self {
      ErrorReason::Detail(detail) => {
        write!(f, "{}", detail.reason)?;
        for line in detail.packs.iter().take(5) {
          write!(f, "\n{}", line)?;
        }
        if detail.packs.len() > 5 {
          write!(f, "\n...")?;
        }
      }
      ErrorReason::Error(e) => {
        if let Some(e) = e.downcast_ref::<Error>() {
          write!(f, "{}", e.inner)?;
        } else {
          write!(f, "{}", e)?;
        }
      }
      ErrorReason::Reason(e) => {
        write!(f, "{}", e)?;
      }
    };
    Ok(())
  }
}

#[derive(Debug)]
pub enum ErrorType {
  Validate,
  Save,
  Load,
}

impl std::fmt::Display for ErrorType {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    match self {
      ErrorType::Validate => write!(f, "validate"),
      ErrorType::Save => write!(f, "save"),
      ErrorType::Load => write!(f, "load"),
    }
  }
}

#[derive(Debug)]
pub struct Error {
  r#type: Option<ErrorType>,
  scope: Option<&'static str>,
  inner: ErrorReason,
}

impl From<FSError> for Error {
  fn from(e: FSError) -> Self {
    Self {
      r#type: None,
      scope: None,
      inner: ErrorReason::Error(Box::new(e)),
    }
  }
}

impl From<BatchFSError> for Error {
  fn from(e: BatchFSError) -> Self {
    Self {
      r#type: None,
      scope: None,
      inner: ErrorReason::Error(Box::new(e)),
    }
  }
}

impl Error {
  pub fn from_detail(
    r#type: Option<ErrorType>,
    scope: Option<&'static str>,
    detail: InvalidDetail,
  ) -> Self {
    Self {
      r#type,
      scope,
      inner: ErrorReason::Detail(detail),
    }
  }
  pub fn from_error(
    r#type: Option<ErrorType>,
    scope: Option<&'static str>,
    error: Box<dyn std::error::Error + Send + Sync>,
  ) -> Self {
    Self {
      r#type,
      scope,
      inner: ErrorReason::Error(error),
    }
  }
  pub fn from_reason(
    r#type: Option<ErrorType>,
    scope: Option<&'static str>,
    reason: String,
  ) -> Self {
    Self {
      r#type,
      scope,
      inner: ErrorReason::Reason(reason),
    }
  }
}

impl std::fmt::Display for Error {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    if let Some(t) = &self.r#type {
      write!(f, "{} ", t)?;
      if let Some(scope) = self.scope {
        write!(f, "scope `{}` ", scope)?;
      }
      write!(f, "failed due to")?;
      write!(f, " {}", self.inner)?;
    } else {
      write!(f, "{}", self.inner)?;
    }

    Ok(())
  }
}

impl std::error::Error for Error {
  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
    match &self.inner {
      ErrorReason::Error(error) => error.source(),
      _ => None,
    }
  }
}

impl miette::Diagnostic for Error {
  fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
    Some(Box::new(format!(
      "Error::{}",
      self
        .r#type
        .as_ref()
        .map_or("".to_string(), |t| t.to_string())
    )))
  }
  fn severity(&self) -> Option<miette::Severity> {
    Some(miette::Severity::Warning)
  }
}

pub type Result<T> = std::result::Result<T, Error>;
