ruchy 4.1.1

A systems scripting language that transpiles to idiomatic Rust with extreme quality engineering
Documentation
//! Markdown output format for documentation

use crate::reporting::ascii::{detect_trend, sparkline};
use crate::reporting::TranspileReport;

use super::ReportFormatter;

/// Markdown formatter (`CommonMark`)
#[derive(Default)]
pub struct MarkdownFormatter;

impl ReportFormatter for MarkdownFormatter {
    fn format(&self, report: &TranspileReport) -> String {
        let mut sections = vec![
            "# Ruchy Transpilation Report".to_string(),
            String::new(),
            format!("**Generated**: {}", chrono_lite_now()),
            String::new(),
            "## Executive Summary".to_string(),
            String::new(),
            format!(
                "| Metric | Value |
|--------|-------|
| Total Files | {} |
| Passed | {} |
| Failed | {} |
| Success Rate | {:.1}% |
| Andon Status | {} |
| Grade | {} |",
                report.total,
                report.passed,
                report.failed,
                report.success_rate(),
                report.andon(),
                report.grade()
            ),
        ];

        // Trend section
        if !report.trend.is_empty() {
            let line = sparkline(&report.trend);
            let direction = detect_trend(&report.trend);
            sections.push(String::new());
            sections.push("## Trend Analysis".to_string());
            sections.push(String::new());
            sections.push(format!(
                "**7-Day Trend**: `{}` ({})",
                line,
                direction.label()
            ));
        }

        // Error taxonomy
        if !report.errors.is_empty() {
            sections.push(String::new());
            sections.push("## Error Taxonomy".to_string());
            sections.push(String::new());
            sections.push("| Error Code | Count | % of Failures | Sample |".to_string());
            sections.push("|------------|-------|---------------|--------|".to_string());

            for entry in &report.errors {
                let percentage = if report.failed > 0 {
                    (entry.count as f64 / report.failed as f64) * 100.0
                } else {
                    0.0
                };

                let sample = entry
                    .samples
                    .first()
                    .map_or_else(|| "-".to_string(), |s| truncate_md(s, 40));

                sections.push(format!(
                    "| {} | {} | {:.1}% | {} |",
                    entry.code, entry.count, percentage, sample
                ));
            }
        }

        // Footer
        sections.push(String::new());
        sections.push("---".to_string());
        sections.push("*Report generated by Ruchy Transpile Oracle [VIS-001]*".to_string());

        sections.join("\n")
    }
}

/// Get current timestamp (lightweight, no chrono dependency)
fn chrono_lite_now() -> String {
    // Use system time for basic timestamp
    use std::time::{SystemTime, UNIX_EPOCH};

    let duration = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default();
    let secs = duration.as_secs();

    // Convert to basic ISO-ish format
    let days = secs / 86400;
    let years_since_1970 = days / 365;
    let year = 1970 + years_since_1970;

    format!("{year}-XX-XX (Unix: {secs})")
}

/// Truncate for markdown tables (escape pipes)
fn truncate_md(s: &str, max_len: usize) -> String {
    let escaped = s.replace('|', "\\|");
    if escaped.len() <= max_len {
        escaped
    } else {
        format!("{}...", &escaped[..max_len.saturating_sub(3)])
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::reporting::ErrorEntry;

    #[test]
    fn test_markdown_formatter_header() {
        let report = TranspileReport::new(100, 85, 15);
        let fmt = MarkdownFormatter;
        let output = fmt.format(&report);

        assert!(output.contains("# Ruchy Transpilation Report"));
    }

    #[test]
    fn test_markdown_formatter_summary_table() {
        let report = TranspileReport::new(100, 85, 15);
        let fmt = MarkdownFormatter;
        let output = fmt.format(&report);

        assert!(output.contains("| Total Files | 100 |"));
        assert!(output.contains("| Passed | 85 |"));
        assert!(output.contains("| Failed | 15 |"));
        assert!(output.contains("| Success Rate | 85.0% |"));
    }

    #[test]
    fn test_markdown_formatter_with_errors() {
        let mut report = TranspileReport::new(100, 85, 15);
        report.add_error(ErrorEntry::new("E0308", 8).with_sample("mismatched types"));

        let fmt = MarkdownFormatter;
        let output = fmt.format(&report);

        assert!(output.contains("## Error Taxonomy"));
        assert!(output.contains("| E0308 | 8 |"));
        assert!(output.contains("mismatched types"));
    }

    #[test]
    fn test_markdown_formatter_with_trend() {
        let report = TranspileReport::new(100, 85, 15).with_trend(vec![70.0, 75.0, 80.0, 85.0]);

        let fmt = MarkdownFormatter;
        let output = fmt.format(&report);

        assert!(output.contains("## Trend Analysis"));
        assert!(output.contains("7-Day Trend"));
        assert!(output.contains("improving"));
    }

    #[test]
    fn test_markdown_formatter_footer() {
        let report = TranspileReport::new(100, 85, 15);
        let fmt = MarkdownFormatter;
        let output = fmt.format(&report);

        assert!(output.contains("VIS-001"));
        assert!(output.contains("---"));
    }

    #[test]
    fn test_truncate_md_short() {
        assert_eq!(truncate_md("hello", 10), "hello");
    }

    #[test]
    fn test_truncate_md_long() {
        assert_eq!(truncate_md("hello world this is long", 10), "hello w...");
    }

    #[test]
    fn test_truncate_md_pipes() {
        assert_eq!(truncate_md("a|b|c", 10), "a\\|b\\|c");
    }
}