//! The result of a single `verify` check.
//!
//! Every check returns a [`CheckResult`] with a [`Severity`]. The verifier
//! aggregates these: any `Error` severity makes `verify` exit non-zero
//! (design §5.2 step 4, §8.1). `Warn` is reported but does not fail the run.
//! `Skipped` is for the pure-library invocation test (design §5.1) — not a
//! failure, just an explicit "nothing to do here."

/// One check outcome. Ordered by escalating severity; `derive` on `PartialOrd`
/// would be fragile, so we implement the ordering explicitly.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
    /// Nothing to check (e.g. pure-library invocation test). Does not fail.
    Skipped,
    /// Check passed.
    Pass,
    /// Potential problem; surfaced but non-blocking.
    Warn,
    /// Critical failure; fails `verify` (and blocks `init` from writing).
    Error,
}

impl Severity {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Skipped => "skip",
            Self::Pass => "ok",
            Self::Warn => "warn",
            Self::Error => "fail",
        }
    }
}

#[derive(Debug, Clone)]
pub struct CheckResult {
    /// Machine-readable check id, e.g. `discovery.description_length`.
    /// Surfaced by the machine-readable report format consumed by tests;
    /// not yet read by the human renderer.
    #[allow(dead_code)]
    pub check_id: String,
    /// Human label, e.g. `SKILL.md description is under 1,536 characters`.
    pub check_name: String,
    pub severity: Severity,
    /// Short status line. For failures this is the *what's wrong*.
    pub message: String,
    /// The actionable fix, per design §8.1 "Errors teach, not just complain".
    /// Populated on `Warn`/`Error`; the one-liner suggestion.
    pub suggestion: Option<String>,
    /// File + line where the problem lives, when we can pin it down. Line is
    /// 1-based, `None` if file-level only.
    pub location: Option<(String, Option<usize>)>,
}

impl CheckResult {
    /// A clean pass with no suggestion needed.
    pub fn pass(check_id: &str, check_name: &str, message: impl Into<String>) -> Self {
        Self {
            check_id: check_id.to_string(),
            check_name: check_name.to_string(),
            severity: Severity::Pass,
            message: message.into(),
            suggestion: None,
            location: None,
        }
    }

    pub fn warn(
        check_id: &str,
        check_name: &str,
        message: impl Into<String>,
        suggestion: impl Into<String>,
    ) -> Self {
        Self {
            check_id: check_id.to_string(),
            check_name: check_name.to_string(),
            severity: Severity::Warn,
            message: message.into(),
            suggestion: Some(suggestion.into()),
            location: None,
        }
    }

    pub fn fail(
        check_id: &str,
        check_name: &str,
        message: impl Into<String>,
        suggestion: impl Into<String>,
    ) -> Self {
        Self {
            check_id: check_id.to_string(),
            check_name: check_name.to_string(),
            severity: Severity::Error,
            message: message.into(),
            suggestion: Some(suggestion.into()),
            location: None,
        }
    }

    pub fn skipped(check_id: &str, check_name: &str, message: impl Into<String>) -> Self {
        Self {
            check_id: check_id.to_string(),
            check_name: check_name.to_string(),
            severity: Severity::Skipped,
            message: message.into(),
            suggestion: None,
            location: None,
        }
    }

    /// True if this result fails `verify` (critical severity).
    pub fn is_critical_failure(&self) -> bool {
        matches!(self.severity, Severity::Error)
    }
}

/// Aggregate of every check in a `verify` run. Knows whether the run passed
/// and can render a human report (design §5.2 step 4).
#[derive(Debug, Default)]
pub struct VerifyReport {
    pub results: Vec<CheckResult>,
}

impl VerifyReport {
    pub fn push(&mut self, r: CheckResult) {
        self.results.push(r);
    }

    /// True iff at least one result is `Error`. This is what gates the exit
    /// code and the `init` pre-commit write.
    pub fn has_critical_failure(&self) -> bool {
        self.results.iter().any(CheckResult::is_critical_failure)
    }

    pub fn counts(&self) -> (usize, usize, usize, usize) {
        let mut pass = 0;
        let mut warn = 0;
        let mut fail = 0;
        let mut skip = 0;
        for r in &self.results {
            match r.severity {
                Severity::Pass => pass += 1,
                Severity::Warn => warn += 1,
                Severity::Error => fail += 1,
                Severity::Skipped => skip += 1,
            }
        }
        (pass, warn, fail, skip)
    }
}
