//! Minimal stderr logging utilities.

use std::{
    cell::RefCell,
    io::{self, Write},
};

/// Logging verbosity level.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Verbosity {
    /// Suppress warnings and informational messages.
    Quiet,
    /// Emit warnings but suppress informational messages.
    Normal,
    /// Emit warnings and informational messages.
    Verbose,
}

/// Lightweight logger used by renderers and generators.
#[derive(Debug)]
pub struct Logger<W: Write> {
    verbosity: Verbosity,
    writer: RefCell<W>,
}

impl Logger<io::Stderr> {
    /// Creates a logger with the given verbosity.
    pub fn new(verbosity: Verbosity) -> Self {
        Self {
            verbosity,
            writer: RefCell::new(io::stderr()),
        }
    }
}

impl<W: Write> Logger<W> {
    pub fn with_writer(verbosity: Verbosity, writer: W) -> Self {
        Self {
            verbosity,
            writer: RefCell::new(writer),
        }
    }

    /// Emits a warning unless verbosity is [`Verbosity::Quiet`].
    pub fn warn(&self, message: impl AsRef<str>) {
        if self.verbosity != Verbosity::Quiet {
            let _ = writeln!(self.writer.borrow_mut(), "Warning: {}", message.as_ref());
        }
    }

    /// Emits an informational message only when verbosity is [`Verbosity::Verbose`].
    pub fn info(&self, message: impl AsRef<str>) {
        if self.verbosity == Verbosity::Verbose {
            let _ = writeln!(self.writer.borrow_mut(), "Info: {}", message.as_ref());
        }
    }
}

#[cfg(test)]
mod tests {
    use std::io::{self, Write};

    use super::{Logger, Verbosity};

    fn logged_output(logger: &Logger<Vec<u8>>) -> String {
        String::from_utf8(logger.writer.borrow().clone()).unwrap()
    }

    #[test]
    fn quiet_suppresses_warnings() {
        let logger = Logger::with_writer(Verbosity::Quiet, Vec::new());
        logger.warn("skipped file");
        assert_eq!(logged_output(&logger), "");
    }

    #[test]
    fn quiet_suppresses_info_messages() {
        let logger = Logger::with_writer(Verbosity::Quiet, Vec::new());
        logger.info("rendering file");
        assert_eq!(logged_output(&logger), "");
    }

    #[test]
    fn normal_writes_warnings() {
        let logger = Logger::with_writer(Verbosity::Normal, Vec::new());
        logger.warn("skipped file");
        assert_eq!(logged_output(&logger), "Warning: skipped file\n");
    }

    #[test]
    fn normal_suppresses_info_messages() {
        let logger = Logger::with_writer(Verbosity::Normal, Vec::new());
        logger.info("rendering file");
        assert_eq!(logged_output(&logger), "");
    }

    #[test]
    fn verbose_writes_warnings() {
        let logger = Logger::with_writer(Verbosity::Verbose, Vec::new());
        logger.warn("skipped file");
        assert_eq!(logged_output(&logger), "Warning: skipped file\n");
    }

    #[test]
    fn verbose_writes_info_messages() {
        let logger = Logger::with_writer(Verbosity::Verbose, Vec::new());
        logger.info("rendering file");
        assert_eq!(logged_output(&logger), "Info: rendering file\n");
    }

    #[test]
    fn verbose_preserves_log_order() {
        let logger = Logger::with_writer(Verbosity::Verbose, Vec::new());

        logger.info("rendering a.rs");
        logger.warn("skipping duplicate file: a.rs");
        logger.info("rendering b.rs");

        assert_eq!(
            logged_output(&logger),
            "Info: rendering a.rs\n\
             Warning: skipping duplicate file: a.rs\n\
             Info: rendering b.rs\n"
        );
    }

    struct FailingWriter;

    impl Write for FailingWriter {
        fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
            Err(io::Error::other("intentional write failure"))
        }

        fn flush(&mut self) -> io::Result<()> {
            Ok(())
        }
    }

    #[test]
    fn logging_ignores_writer_errors() {
        let logger = Logger::with_writer(Verbosity::Verbose, FailingWriter);

        logger.warn("this should not panic");
        logger.info("this should not panic");
    }
}
