/// Errors originating from WAL operations.
///
/// Covers I/O failures, data corruption detected during recovery,
/// and lifecycle violations (e.g., appending after shutdown).
#[derive(Debug, thiserror::Error)]
pub enum WalError {
    /// Underlying filesystem I/O failure.
    #[error("WAL I/O error: {0}")]
    Io(#[from] std::io::Error),
    /// Data corruption detected (BLAKE3 mismatch, invalid magic, etc.).
    #[error("WAL corruption: {message}")]
    Corruption { message: String },
    /// Current segment is full; internal signal to trigger rotation.
    #[error("WAL segment full")]
    SegmentFull,
    /// Attempted append after WAL has been shut down.
    #[error("WAL closed")]
    Closed,
    /// Channel send to writer thread failed (writer thread panicked or exited).
    #[error("WAL channel send failed")]
    SendFailed,
    /// Writer thread join failed during shutdown.
    #[error("WAL shutdown failed")]
    ShutdownFailed,
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;

    #[test]
    fn display_io() {
        let e = WalError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            "file not found",
        ));
        assert!(e.to_string().contains("I/O error"));
        assert!(e.to_string().contains("file not found"));
    }

    #[test]
    fn display_corruption() {
        let e = WalError::Corruption {
            message: "bad checksum".into(),
        };
        assert_eq!(e.to_string(), "WAL corruption: bad checksum");
    }

    #[test]
    fn display_segment_full() {
        assert_eq!(WalError::SegmentFull.to_string(), "WAL segment full");
    }

    #[test]
    fn display_closed() {
        assert_eq!(WalError::Closed.to_string(), "WAL closed");
    }

    #[test]
    fn display_send_failed() {
        assert_eq!(WalError::SendFailed.to_string(), "WAL channel send failed");
    }

    #[test]
    fn display_shutdown_failed() {
        assert_eq!(WalError::ShutdownFailed.to_string(), "WAL shutdown failed");
    }

    #[test]
    fn from_io_error() {
        let io_err = std::io::Error::other("disk full");
        let wal_err: WalError = io_err.into();
        assert!(matches!(wal_err, WalError::Io(_)));
    }

    #[test]
    fn source_io() {
        use std::error::Error;
        let e = WalError::Io(std::io::Error::other("test"));
        assert!(e.source().is_some());
    }

    #[test]
    fn source_corruption_is_none() {
        use std::error::Error;
        let e = WalError::Corruption {
            message: "test".into(),
        };
        assert!(e.source().is_none());
    }

    #[test]
    fn source_closed_is_none() {
        use std::error::Error;
        assert!(WalError::Closed.source().is_none());
    }
}
