//! Output formatters for detection results.
//!
//! Supports four stdout formats: plain text (default), Markdown, JSON, and
//! SARIF.  SARIF output uses the `sarif_rust` crate for spec-compliant
//! generation and `sarif-to-md-core` for SARIF-to-Markdown conversion.

use sarif_rust::builder::{ResultBuilder, RunBuilder, SarifLogBuilder};
use sarif_rust::parser::SarifValidator;
use sarif_rust::types::Level;
use serde::Serialize;
use std::fmt::Write as _;

/// Supported output formats.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum OutputFormat {
    /// Human-readable plain text (default).
    #[default]
    Text,
    /// Markdown table.
    Markdown,
    /// Structured JSON.
    Json,
    /// OASIS SARIF v2.1.0 for static analysis tooling.
    Sarif,
}

/// Result of MIME detection for a single file.
#[derive(Debug, Clone, Serialize)]
pub struct DetectionResult {
    /// File path.
    pub path: String,
    /// Resolved MIME type (best guess).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mime_type: Option<String>,
    /// MIME type detected by file extension.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub by_extension: Option<String>,
    /// MIME type detected by binary magic bytes.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub by_magic: Option<String>,
    /// MIME type detected by text content sniffing.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub by_content: Option<String>,
    /// Human-readable module label.
    pub module: String,
    /// Known file extensions for the resolved type.
    pub extensions: Vec<String>,
    /// Whether the resolved type is in the mediatypes database.
    pub in_mediatypes: bool,
    /// Match status: "match", "mismatch", "extension-only", "content-only",
    /// "unknown".
    pub match_status: String,
}

/// Simple Markdown table for non-file commands (ext, mime).
#[must_use]
pub fn format_markdown_table(results: &[DetectionResult]) -> String {
    let mut out = String::from(
        "| File | MIME Type | Module | Extensions | Match |\n\
         |------|----------|--------|------------|-------|\n",
    );
    for r in results {
        let mime = r.mime_type.as_deref().unwrap_or("unknown");
        let exts = if r.extensions.is_empty() {
            "\u{2014}".to_owned()
        } else {
            r.extensions.join(", ")
        };
        let _ = writeln!(
            out,
            "| {path} | {mime} | {module} | {exts} | {status} |",
            path = r.path,
            module = r.module,
            status = r.match_status,
        );
    }
    out
}

/// Format multiple results as JSON.
///
/// # Errors
///
/// Returns an error if serialization fails (should not happen with valid data).
pub fn format_json(results: &[DetectionResult]) -> Result<String, serde_json::Error> {
    serde_json::to_string_pretty(results)
}

/// Determine the detection method string for a result.
const fn detection_method(r: &DetectionResult) -> &'static str {
    if r.by_magic.is_some() {
        "magic-bytes"
    } else if r.by_content.is_some() {
        "content-sniffing"
    } else if r.by_extension.is_some() {
        "file-extension"
    } else {
        "none"
    }
}

/// Format multiple results as SARIF v2.1.0 using `sarif_rust`.
///
/// # Errors
///
/// Returns an error if SARIF building or serialization fails.
pub fn format_sarif(results: &[DetectionResult]) -> Result<String, String> {
    let mut run_builder = RunBuilder::with_tool("mimetype-cli", Some(env!("CARGO_PKG_VERSION")));

    for r in results {
        let mime = r.mime_type.as_deref().unwrap_or("unknown");
        let method = detection_method(r);

        let level = if r.match_status == "mismatch" {
            Level::Warning
        } else {
            Level::Note
        };

        let result =
            ResultBuilder::with_text_message(format!("Detected MIME type: {mime} ({method})"))
                .with_rule_id("mime-detection")
                .with_level(level)
                .add_file_location(&r.path, 1, 1)
                .build();

        run_builder = run_builder.add_result(result);
    }

    let run = run_builder.build();

    let sarif_log = SarifLogBuilder::with_standard_schema()
        .add_run(run)
        .build()
        .map_err(|e| format!("SARIF build validation failed: {e}"))?;

    sarif_rust::to_string_pretty(&sarif_log).map_err(|e| format!("SARIF serialization failed: {e}"))
}

/// Convert SARIF JSON to GitHub Flavored Markdown using `sarif-to-md-core`.
///
/// # Errors
///
/// Returns an error if the SARIF JSON is invalid or Markdown generation fails.
pub fn format_sarif_markdown(sarif_json: &str) -> Result<String, String> {
    use sarif_to_md_core::markdown::sarif::generator::SarifMarkdownGenerator;
    use sarif_to_md_core::markdown::MarkdownFormat;
    use sarif_to_md_core::ReportProcessorBuilder;

    let generator = SarifMarkdownGenerator::new(MarkdownFormat::GitHubFlavored, true);

    let processor = ReportProcessorBuilder::new()
        .generator(generator)
        .content(sarif_json.to_owned())
        .build()
        .map_err(|e| format!("SARIF-to-Markdown build failed: {e}"))?;

    processor
        .generate()
        .map_err(|e| format!("SARIF-to-Markdown generation failed: {e}"))
}

/// Validate a SARIF JSON string by parsing and running strict validation.
///
/// # Errors
///
/// Returns an error if the JSON is not valid SARIF 2.1.0.
pub fn validate_sarif(json: &str) -> Result<(), String> {
    let sarif_log = sarif_rust::from_str(json).map_err(|e| format!("SARIF parse error: {e}"))?;

    let validator = SarifValidator::strict();
    validator
        .validate_sarif_log(&sarif_log)
        .map_err(|e| format!("SARIF validation error: {e}"))
}

/// Validate generated Markdown content.
///
/// Checks that the output is non-empty and contains expected structure.
///
/// # Errors
///
/// Returns an error if the Markdown is empty or malformed.
pub fn validate_markdown(md: &str) -> Result<(), String> {
    if md.trim().is_empty() {
        return Err("Markdown output is empty".to_owned());
    }
    // SARIF-generated Markdown should contain at least a heading or table.
    if !md.contains('#') && !md.contains('|') && !md.contains("---") {
        return Err(
            "Markdown output missing expected structure (no headings, tables, or rules)".to_owned(),
        );
    }
    Ok(())
}
