use std::{
    fmt::{Display, Formatter, Result as FmtResult},
    io,
};

use thiserror::Error;

use crate::FolgezettelId;

#[derive(Debug, Error)]
pub enum CliError {
    #[error("{0}")]
    Io(#[from] io::Error),
    #[error("failed to write: {0}")]
    Csv(#[from] csv::Error),
    #[error("failed to walk current directory: {0}")]
    Walk(#[from] walkdir::Error),
    #[error("path is not valid UTF-8: {0}")]
    NonUtf8Path(String),
    #[error("invalid note id: {0}")]
    InvalidId(#[from] ParseIdError),
}

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

#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum ParseIdError {
    #[error("id is empty")]
    Empty,
    #[error("expected a number segment in position: {position}")]
    ExpectedNumber { position: usize },
    #[error("expected a letter segment in position: {position}")]
    ExpectedLetter { position: usize },
    #[error("number segment at in position {position} exceeds 64 bits")]
    NumberOverflow { position: usize },
}

#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum ParseFileNameError {
    #[error("file name has no extension")]
    MissingFileExtension,
    #[error("file name has no stem")]
    MissingStem,
    #[error("file name must contain '<id>-<title>'")]
    MissingTitleSeparator,
    #[error("file name title is empty")]
    EmptyTitle,
    #[error("invalid id: {0}")]
    InvalidId(#[from] ParseIdError),
}

#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum Diagnostic {
    #[error("non-zettel file '{file}': {error}")]
    NonZettelFile {
        file: String,
        #[source]
        error: ParseFileNameError,
    },
    #[error("invalid file name '{file}': {error}")]
    InvalidFileName {
        file: String,
        #[source]
        error: ParseFileNameError,
    },
    #[error("duplicate id '{id}' on {}", .files.join(", "))]
    DuplicateId {
        id: FolgezettelId,
        files: Vec<String>,
    },
    #[error("missing parent '{parent}' for {file}")]
    MissingParent {
        id: FolgezettelId,
        parent: FolgezettelId,
        file: String,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticSeverity {
    Error,
    Info,
    Warning,
}

impl Display for DiagnosticSeverity {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
        formatter.write_str(match self {
            DiagnosticSeverity::Info => "info",
            DiagnosticSeverity::Warning => "warning",
            DiagnosticSeverity::Error => "error",
        })
    }
}

impl Diagnostic {
    pub fn severity(&self) -> DiagnosticSeverity {
        match self {
            Diagnostic::InvalidFileName {
                error: ParseFileNameError::MissingTitleSeparator | ParseFileNameError::EmptyTitle,
                ..
            }
            | Diagnostic::MissingParent { .. } => DiagnosticSeverity::Warning,
            Diagnostic::InvalidFileName { .. } | Diagnostic::DuplicateId { .. } => {
                DiagnosticSeverity::Error
            }
            Diagnostic::NonZettelFile { .. } => DiagnosticSeverity::Info,
        }
    }
}
