//! Command-line interface for mkdlint

#[cfg(feature = "cli")]
use clap::Parser;

#[cfg(feature = "cli")]
use mkdlint::{LintOptions, apply_fixes, formatters, lint_sync};

#[cfg(feature = "cli")]
#[derive(clap::ValueEnum, Clone, Debug, Default)]
enum OutputFormat {
    #[default]
    Text,
    Json,
    Sarif,
}

#[cfg(feature = "cli")]
#[derive(Parser, Debug)]
#[command(name = "mkdlint")]
#[command(about = "A linter for Markdown files", long_about = None)]
#[command(version)]
struct Args {
    #[command(subcommand)]
    command: Option<Command>,

    /// Files or directories to lint
    #[arg(global = true)]
    files: Vec<String>,

    /// Path to configuration file
    #[arg(short, long, global = true)]
    config: Option<String>,

    /// Output format
    #[arg(short = 'o', long, default_value = "text", global = true)]
    output_format: OutputFormat,

    /// Glob patterns for files to ignore (repeatable)
    #[arg(long, action = clap::ArgAction::Append, global = true)]
    ignore: Vec<String>,

    /// Disable colored output
    #[arg(long, global = true)]
    no_color: bool,

    /// Disable inline configuration comments
    #[arg(long, global = true)]
    no_inline_config: bool,

    /// Automatically fix violations where possible
    #[arg(short, long, global = true)]
    fix: bool,

    /// List all available rules
    #[arg(long, global = true)]
    list_rules: bool,

    /// Read input from stdin (use '-' as filename)
    #[arg(long, global = true)]
    stdin: bool,

    /// Enable specific rules (can be repeated, e.g., --enable MD001 --enable MD003)
    #[arg(long, action = clap::ArgAction::Append, global = true)]
    enable: Vec<String>,

    /// Disable specific rules (can be repeated, e.g., --disable MD013 --disable MD033)
    #[arg(long, action = clap::ArgAction::Append, global = true)]
    disable: Vec<String>,

    /// Verbose output with detailed information
    #[arg(short, long, global = true)]
    verbose: bool,

    /// Quiet mode - only show file names with errors
    #[arg(short, long, global = true)]
    quiet: bool,

    /// Watch mode - re-lint files on changes
    #[arg(short, long, global = true)]
    watch: bool,

    /// Watch specific paths (default: all input files/directories)
    #[arg(long, action = clap::ArgAction::Append, global = true)]
    watch_paths: Vec<String>,
}

#[cfg(feature = "cli")]
#[derive(Parser, Debug)]
enum Command {
    /// Initialize a new configuration file
    Init {
        /// Output file path (default: .markdownlint.json)
        #[arg(long, default_value = ".markdownlint.json")]
        output: String,

        /// Output format (json, yaml, or toml)
        #[arg(long, default_value = "json")]
        format: String,

        /// Interactive mode with guided questions
        #[arg(long, short)]
        interactive: bool,
    },
}

/// Expand directories to .md/.markdown files recursively
#[cfg(feature = "cli")]
fn expand_paths(paths: &[String]) -> Vec<String> {
    use walkdir::WalkDir;

    let mut expanded = Vec::new();
    for path in paths {
        let p = std::path::Path::new(path);
        if p.is_dir() {
            for entry in WalkDir::new(p).into_iter().filter_map(|e| e.ok()) {
                let ep = entry.path();
                if ep.is_file()
                    && let Some(ext) = ep.extension().and_then(|e| e.to_str())
                    && (ext == "md" || ext == "markdown")
                {
                    expanded.push(ep.to_string_lossy().to_string());
                }
            }
        } else {
            expanded.push(path.clone());
        }
    }
    expanded.sort();
    expanded
}

/// Filter files by ignore glob patterns
#[cfg(feature = "cli")]
fn filter_ignored(
    files: Vec<String>,
    ignore_patterns: &[String],
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
    if ignore_patterns.is_empty() {
        return Ok(files);
    }

    use globset::{Glob, GlobSetBuilder};

    let mut builder = GlobSetBuilder::new();
    for pattern in ignore_patterns {
        builder.add(Glob::new(pattern)?);
    }
    let ignore_set = builder.build()?;

    Ok(files
        .into_iter()
        .filter(|f| !ignore_set.is_match(f))
        .collect())
}

/// Interactive configuration wizard
#[cfg(feature = "cli")]
fn init_config_interactive(
    output_path: &str,
    format: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    use colored::Colorize;
    use dialoguer::{Confirm, Input, MultiSelect, Select};

    println!("{}", "mkdlint Configuration Wizard".cyan().bold());
    println!();
    println!("This wizard will help you create a custom configuration file.");
    println!();

    // Question 1: Format preference (already set via --format, but allow override)
    let formats = vec!["JSON", "YAML", "TOML"];
    let default_format_idx = match format {
        "yaml" | "yml" => 1,
        "toml" => 2,
        _ => 0,
    };
    let format_idx = Select::new()
        .with_prompt("What format would you like for your config file?")
        .items(&formats)
        .default(default_format_idx)
        .interact()?;
    let selected_format = match format_idx {
        1 => "yaml",
        2 => "toml",
        _ => "json",
    };

    // Question 2: Line length
    let line_length: usize = Input::new()
        .with_prompt("Maximum line length (0 to disable)")
        .default(120)
        .interact()?;

    // Question 3: Heading style
    let heading_styles = vec![
        "ATX (# Heading)",
        "Setext (Underlined)",
        "Consistent (auto-detect)",
    ];
    let heading_style_idx = Select::new()
        .with_prompt("Preferred heading style?")
        .items(&heading_styles)
        .default(0)
        .interact()?;
    let heading_style = match heading_style_idx {
        1 => "setext",
        2 => "consistent",
        _ => "atx",
    };

    // Question 4: List marker style
    let list_markers = vec![
        "Dash (-)",
        "Asterisk (*)",
        "Plus (+)",
        "Consistent (auto-detect)",
    ];
    let list_marker_idx = Select::new()
        .with_prompt("Preferred unordered list marker?")
        .items(&list_markers)
        .default(0)
        .interact()?;
    let list_marker = match list_marker_idx {
        1 => "asterisk",
        2 => "plus",
        3 => "consistent",
        _ => "dash",
    };

    // Question 5: Emphasis style
    let emphasis_styles = vec!["Asterisk (*text*)", "Underscore (_text_)", "Consistent"];
    let emphasis_style_idx = Select::new()
        .with_prompt("Preferred emphasis style?")
        .items(&emphasis_styles)
        .default(0)
        .interact()?;
    let emphasis_style = match emphasis_style_idx {
        1 => "underscore",
        2 => "consistent",
        _ => "asterisk",
    };

    // Question 6: Strong emphasis style
    let strong_styles = vec!["Asterisk (**text**)", "Underscore (__text__)", "Consistent"];
    let strong_style_idx = Select::new()
        .with_prompt("Preferred strong emphasis style?")
        .items(&strong_styles)
        .default(0)
        .interact()?;
    let strong_style = match strong_style_idx {
        1 => "underscore",
        2 => "consistent",
        _ => "asterisk",
    };

    // Question 7: Inline HTML
    let allow_html = Confirm::new()
        .with_prompt("Allow inline HTML in markdown?")
        .default(false)
        .interact()?;

    let allowed_elements = if allow_html {
        let elements = vec!["br", "img", "details", "summary", "div", "span"];
        let selected = MultiSelect::new()
            .with_prompt(
                "Which HTML elements should be allowed? (use Space to select, Enter to confirm)",
            )
            .items(&elements)
            .defaults(&[true, true, true, true, false, false])
            .interact()?;

        selected
            .iter()
            .map(|&idx| elements[idx])
            .collect::<Vec<_>>()
    } else {
        vec![]
    };

    // Question 8: Code block style
    let code_styles = vec!["Fenced (```)", "Indented (4 spaces)", "Consistent"];
    let code_style_idx = Select::new()
        .with_prompt("Preferred code block style?")
        .items(&code_styles)
        .default(0)
        .interact()?;
    let code_style = match code_style_idx {
        1 => "indented",
        2 => "consistent",
        _ => "fenced",
    };

    // Question 9: Rules to disable
    let common_rules = vec![
        "MD013 (Line length)",
        "MD033 (Inline HTML)",
        "MD034 (Bare URLs)",
        "MD041 (First line H1)",
    ];
    let disabled_rules_selection = MultiSelect::new()
        .with_prompt("Which rules would you like to disable? (optional)")
        .items(&common_rules)
        .interact()?;

    let disabled_rules: Vec<&str> = disabled_rules_selection
        .iter()
        .map(|&idx| match idx {
            0 => "MD013",
            1 => "MD033",
            2 => "MD034",
            3 => "MD041",
            _ => "",
        })
        .filter(|s| !s.is_empty())
        .collect();

    println!();
    println!("{}", "Generating configuration...".green());

    // Build config options
    let options = ConfigOptions {
        line_length,
        heading_style,
        list_marker,
        emphasis_style,
        strong_style,
        allow_html,
        allowed_elements,
        code_style,
        disabled_rules,
    };

    // Generate configuration based on answers
    let content = generate_config(selected_format, &options);

    // Update output path extension if format changed
    let output_path = if selected_format != format {
        match selected_format {
            "yaml" => output_path
                .replace(".json", ".yaml")
                .replace(".toml", ".yaml"),
            "toml" => output_path
                .replace(".json", ".toml")
                .replace(".yaml", ".toml"),
            _ => output_path
                .replace(".yaml", ".json")
                .replace(".toml", ".json"),
        }
    } else {
        output_path.to_string()
    };

    // Write to file
    std::fs::write(&output_path, content)?;

    println!();
    println!(
        "{} Created configuration file: {}",
        "✓".green().bold(),
        output_path.cyan()
    );
    println!();
    println!("Next steps:");
    println!(
        "  1. Review and edit {} to fine-tune rules",
        output_path.cyan()
    );
    println!(
        "  2. Run: {} {} {}",
        "mkdlint".cyan(),
        "--config".yellow(),
        output_path.yellow()
    );
    println!(
        "  3. Auto-fix issues: {} {} {} {}",
        "mkdlint".cyan(),
        "--fix".yellow(),
        "--config".yellow(),
        output_path.yellow()
    );

    Ok(())
}

/// Config options collected from wizard
#[cfg(feature = "cli")]
struct ConfigOptions<'a> {
    line_length: usize,
    heading_style: &'a str,
    list_marker: &'a str,
    emphasis_style: &'a str,
    strong_style: &'a str,
    allow_html: bool,
    allowed_elements: Vec<&'a str>,
    code_style: &'a str,
    disabled_rules: Vec<&'a str>,
}

/// Generate configuration content based on wizard answers
#[cfg(feature = "cli")]
fn generate_config(format: &str, options: &ConfigOptions) -> String {
    match format {
        "json" => generate_json_config(options),
        "yaml" => generate_yaml_config(options),
        "toml" => generate_toml_config(options),
        _ => String::new(),
    }
}

#[cfg(feature = "cli")]
fn generate_json_config(options: &ConfigOptions) -> String {
    let mut config = String::from("{\n  \"default\": true");

    // Disabled rules
    for rule in &options.disabled_rules {
        config.push_str(&format!(",\n  \"{}\": false", rule));
    }

    // Line length
    if options.line_length > 0 {
        config.push_str(&format!(
            ",\n  \"MD013\": {{\n    \"line_length\": {},\n    \"code_blocks\": false,\n    \"tables\": false\n  }}",
            options.line_length
        ));
    }

    // Heading style (MD003)
    if options.heading_style != "consistent" {
        config.push_str(&format!(
            ",\n  \"MD003\": {{\n    \"style\": \"{}\"\n  }}",
            options.heading_style
        ));
    }

    // List marker style (MD004)
    if options.list_marker != "consistent" {
        config.push_str(&format!(
            ",\n  \"MD004\": {{\n    \"style\": \"{}\"\n  }}",
            options.list_marker
        ));
    }

    // Inline HTML (MD033)
    if options.allow_html && !options.allowed_elements.is_empty() {
        let elements_json = options
            .allowed_elements
            .iter()
            .map(|e| format!("\"{}\"", e))
            .collect::<Vec<_>>()
            .join(", ");
        config.push_str(&format!(
            ",\n  \"MD033\": {{\n    \"allowed_elements\": [{}]\n  }}",
            elements_json
        ));
    }

    // Code block style (MD046)
    if options.code_style != "consistent" {
        config.push_str(&format!(
            ",\n  \"MD046\": {{\n    \"style\": \"{}\"\n  }}",
            options.code_style
        ));
    }

    // Emphasis style (MD049)
    if options.emphasis_style != "consistent" {
        config.push_str(&format!(
            ",\n  \"MD049\": {{\n    \"style\": \"{}\"\n  }}",
            options.emphasis_style
        ));
    }

    // Strong emphasis style (MD050)
    if options.strong_style != "consistent" {
        config.push_str(&format!(
            ",\n  \"MD050\": {{\n    \"style\": \"{}\"\n  }}",
            options.strong_style
        ));
    }

    config.push_str("\n}\n");
    config
}

#[cfg(feature = "cli")]
fn generate_yaml_config(options: &ConfigOptions) -> String {
    let mut config = String::from("# mkdlint configuration\ndefault: true\n");

    // Disabled rules
    for rule in &options.disabled_rules {
        config.push_str(&format!("\n{}: false", rule));
    }

    // Line length
    if options.line_length > 0 {
        config.push_str(&format!(
            "\n\n# Line length\nMD013:\n  line_length: {}\n  code_blocks: false\n  tables: false",
            options.line_length
        ));
    }

    // Heading style (MD003)
    if options.heading_style != "consistent" {
        config.push_str(&format!(
            "\n\n# Heading style\nMD003:\n  style: {}",
            options.heading_style
        ));
    }

    // List marker style (MD004)
    if options.list_marker != "consistent" {
        config.push_str(&format!(
            "\n\n# List marker\nMD004:\n  style: {}",
            options.list_marker
        ));
    }

    // Inline HTML (MD033)
    if options.allow_html && !options.allowed_elements.is_empty() {
        config.push_str("\n\n# Inline HTML\nMD033:\n  allowed_elements:");
        for element in &options.allowed_elements {
            config.push_str(&format!("\n    - {}", element));
        }
    }

    // Code block style (MD046)
    if options.code_style != "consistent" {
        config.push_str(&format!(
            "\n\n# Code block style\nMD046:\n  style: {}",
            options.code_style
        ));
    }

    // Emphasis style (MD049)
    if options.emphasis_style != "consistent" {
        config.push_str(&format!(
            "\n\n# Emphasis style\nMD049:\n  style: {}",
            options.emphasis_style
        ));
    }

    // Strong emphasis style (MD050)
    if options.strong_style != "consistent" {
        config.push_str(&format!(
            "\n\n# Strong emphasis style\nMD050:\n  style: {}",
            options.strong_style
        ));
    }

    config.push('\n');
    config
}

#[cfg(feature = "cli")]
fn generate_toml_config(options: &ConfigOptions) -> String {
    let mut config = String::from("# mkdlint configuration\ndefault = true\n");

    // Disabled rules
    for rule in &options.disabled_rules {
        config.push_str(&format!("\n{} = false", rule));
    }

    // Line length
    if options.line_length > 0 {
        config.push_str(&format!(
            "\n\n# Line length\n[MD013]\nline_length = {}\ncode_blocks = false\ntables = false",
            options.line_length
        ));
    }

    // Heading style (MD003)
    if options.heading_style != "consistent" {
        config.push_str(&format!(
            "\n\n# Heading style\n[MD003]\nstyle = \"{}\"",
            options.heading_style
        ));
    }

    // List marker style (MD004)
    if options.list_marker != "consistent" {
        config.push_str(&format!(
            "\n\n# List marker\n[MD004]\nstyle = \"{}\"",
            options.list_marker
        ));
    }

    // Inline HTML (MD033)
    if options.allow_html && !options.allowed_elements.is_empty() {
        let elements_toml = options
            .allowed_elements
            .iter()
            .map(|e| format!("\"{}\"", e))
            .collect::<Vec<_>>()
            .join(", ");
        config.push_str(&format!(
            "\n\n# Inline HTML\n[MD033]\nallowed_elements = [{}]",
            elements_toml
        ));
    }

    // Code block style (MD046)
    if options.code_style != "consistent" {
        config.push_str(&format!(
            "\n\n# Code block style\n[MD046]\nstyle = \"{}\"",
            options.code_style
        ));
    }

    // Emphasis style (MD049)
    if options.emphasis_style != "consistent" {
        config.push_str(&format!(
            "\n\n# Emphasis style\n[MD049]\nstyle = \"{}\"",
            options.emphasis_style
        ));
    }

    // Strong emphasis style (MD050)
    if options.strong_style != "consistent" {
        config.push_str(&format!(
            "\n\n# Strong emphasis style\n[MD050]\nstyle = \"{}\"",
            options.strong_style
        ));
    }

    config.push('\n');
    config
}

/// Initialize a new configuration file
#[cfg(feature = "cli")]
fn init_config(
    output_path: &str,
    format: &str,
    interactive: bool,
) -> Result<(), Box<dyn std::error::Error>> {
    use colored::Colorize;
    use std::path::Path;

    // Check if file already exists
    if Path::new(output_path).exists() {
        eprintln!(
            "{} Configuration file '{}' already exists.",
            "Error:".red().bold(),
            output_path
        );
        eprintln!("Remove it first or choose a different output path with --output");
        std::process::exit(1);
    }

    // Interactive mode: ask questions and generate customized config
    if interactive {
        return init_config_interactive(output_path, format);
    }

    // Create a useful default configuration with examples
    let content = match format {
        "json" => r#"{
  "default": true,
  "MD013": {
    "line_length": 120,
    "code_blocks": false,
    "tables": false
  },
  "MD033": {
    "allowed_elements": ["br", "img", "details", "summary"]
  },
  "MD040": {
    "default_language": "text"
  }
}"#
        .to_string(),
        "yaml" | "yml" => r#"# Markdownlint configuration
default: true

# Line length
MD013:
  line_length: 120
  code_blocks: false
  tables: false

# Inline HTML
MD033:
  allowed_elements:
    - br
    - img
    - details
    - summary

# Fenced code language
MD040:
  default_language: text
"#
        .to_string(),
        "toml" => r#"# Markdownlint configuration
default = true

[MD013]
line_length = 120
code_blocks = false
tables = false

[MD033]
allowed_elements = ["br", "img", "details", "summary"]

[MD040]
default_language = "text"
"#
        .to_string(),
        _ => {
            eprintln!(
                "{} Unsupported format '{}'. Use json, yaml, or toml.",
                "Error:".red().bold(),
                format
            );
            std::process::exit(1);
        }
    };

    // Write to file
    std::fs::write(output_path, content)?;

    println!(
        "{} Created configuration file: {}",
        "✓".green().bold(),
        output_path.cyan()
    );
    println!();
    println!("Next steps:");
    println!("  1. Edit {} to customize rules", output_path.cyan());
    println!(
        "  2. Run: {} {} {}",
        "mkdlint".cyan(),
        "--config".yellow(),
        output_path.cyan()
    );

    Ok(())
}

/// List all available linting rules
#[cfg(feature = "cli")]
fn list_rules() {
    use colored::Colorize;
    use mkdlint::rules::get_rules;

    println!("{}", "Available Linting Rules".bold().underline());
    println!();

    let rules = get_rules();
    let mut rules_info: Vec<_> = rules
        .iter()
        .map(|r| {
            let names = r.names();
            let description = r.description();
            let tags = r.tags();
            let fixable = if tags.contains(&"fixable") {
                "✓"
            } else {
                " "
            };
            let alias = if names.len() > 1 { names[1] } else { "" };
            (
                names[0].to_string(),
                alias.to_string(),
                description.to_string(),
                fixable.to_string(),
            )
        })
        .collect();

    // Sort by rule number (MD001, MD002, etc.)
    rules_info.sort_by(|(a, _, _, _), (b, _, _, _)| a.cmp(b));

    println!(
        "{:8} {:30} {:8} {}",
        "Rule".bold(),
        "Alias".bold(),
        "Fixable".bold(),
        "Description".bold()
    );
    println!("{}", "─".repeat(80));

    for (rule_id, alias, description, fixable) in &rules_info {
        let fixable_mark = if fixable == "✓" {
            fixable.green()
        } else {
            fixable.normal()
        };
        println!(
            "{:8} {:30} {:^8} {}",
            rule_id.cyan(),
            alias.yellow(),
            fixable_mark,
            description
        );
    }

    println!();
    println!(
        "Total: {} rules ({} fixable)",
        rules.len(),
        rules_info.iter().filter(|(_, _, _, f)| f == "✓").count()
    );
}

/// Run watch mode with file change detection
#[cfg(feature = "cli")]
fn run_watch_mode(args: &Args) -> Result<(), Box<dyn std::error::Error>> {
    use colored::Colorize;
    use notify_debouncer_full::{new_debouncer, notify::*};
    use std::path::Path;
    use std::sync::mpsc::channel;
    use std::time::Duration;

    println!("{}", "Starting watch mode...".cyan().bold());
    println!();

    // Determine paths to watch
    let watch_paths = if args.watch_paths.is_empty() {
        &args.files
    } else {
        &args.watch_paths
    };

    // Initial lint
    println!("{} Initial lint...", "▸".cyan());
    if let Err(e) = lint_files_once(args) {
        eprintln!("{} {}", "Error:".red().bold(), e);
    }
    println!();

    // Set up file watcher with debouncing (300ms)
    let (tx, rx) = channel();
    let mut debouncer = new_debouncer(Duration::from_millis(300), None, tx)?;

    // Watch all specified paths
    for path in watch_paths {
        let path_obj = Path::new(path);
        if path_obj.exists() {
            debouncer
                .watcher()
                .watch(path_obj, RecursiveMode::Recursive)?;
            println!("{} Watching: {}", "✓".green(), path.cyan());
        } else {
            eprintln!(
                "{} Path does not exist: {}",
                "Warning:".yellow().bold(),
                path
            );
        }
    }

    println!();
    println!("{} Press {} to exit", "▸".cyan(), "Ctrl+C".yellow().bold());
    println!();

    // Main watch loop
    loop {
        match rx.recv() {
            Ok(result) => match result {
                Ok(events) => {
                    // Filter for markdown file changes
                    let has_markdown_changes = events.iter().any(|event| {
                        event.paths.iter().any(|path| {
                            path.extension()
                                .and_then(|ext| ext.to_str())
                                .map(|ext| ext == "md" || ext == "markdown")
                                .unwrap_or(false)
                        })
                    });

                    if has_markdown_changes {
                        println!("{} File changed, re-linting...", "▸".cyan());
                        if let Err(e) = lint_files_once(args) {
                            eprintln!("{} {}", "Error:".red().bold(), e);
                        }
                        println!();
                    }
                }
                Err(errors) => {
                    for error in errors {
                        eprintln!("{} Watch error: {:?}", "Error:".red().bold(), error);
                    }
                }
            },
            Err(e) => {
                eprintln!("{} Channel error: {}", "Error:".red().bold(), e);
                break;
            }
        }
    }

    Ok(())
}

/// Lint files once (used by watch mode and normal mode)
#[cfg(feature = "cli")]
fn lint_files_once(args: &Args) -> Result<(), Box<dyn std::error::Error>> {
    use colored::Colorize;

    // Expand directories and filter ignored files
    let files = expand_paths(&args.files);
    let files = filter_ignored(files, &args.ignore)?;

    if files.is_empty() {
        if !args.quiet {
            println!("No files to lint.");
        }
        return Ok(());
    }

    // Build configuration
    let mut config = if let Some(ref config_path) = args.config {
        mkdlint::Config::from_file(config_path)?
    } else {
        mkdlint::Config::default()
    };

    // Apply --enable and --disable flags
    use mkdlint::RuleConfig;
    for rule in &args.enable {
        config
            .rules
            .insert(rule.to_uppercase(), RuleConfig::Enabled(true));
    }
    for rule in &args.disable {
        config
            .rules
            .insert(rule.to_uppercase(), RuleConfig::Enabled(false));
    }

    let options = LintOptions {
        files: files.clone(),
        strings: std::collections::HashMap::new(),
        config: Some(config),
        no_inline_config: args.no_inline_config,
        ..Default::default()
    };

    let results = lint_sync(&options)?;

    // Handle auto-fix
    if args.fix {
        let mut fixed_count = 0;
        for file_path in &files {
            let errors = match results.get(file_path) {
                Some(errors) if !errors.is_empty() => errors,
                _ => continue,
            };

            let has_fixes = errors.iter().any(|e| e.fix_info.is_some());
            if !has_fixes {
                continue;
            }

            let content = std::fs::read_to_string(file_path)?;
            let fixed = apply_fixes(&content, errors);
            if fixed != content {
                std::fs::write(file_path, &fixed)?;
                fixed_count += 1;
                if args.verbose || !args.quiet {
                    println!("{} {}", "Fixed:".green().bold(), file_path);
                }
            }
        }

        if !args.quiet {
            if fixed_count > 0 {
                println!(
                    "{} {} file(s) fixed.",
                    "✓".green().bold(),
                    fixed_count.to_string().green()
                );
            } else {
                println!("{}", "No fixable issues found.".dimmed());
            }
        }
    } else if results.is_empty() {
        if !args.quiet {
            println!("{} No errors found!", "✓".green().bold());
        }
    } else {
        // Display errors
        if args.quiet {
            for (file, errors) in &results.results {
                if !errors.is_empty() {
                    println!("{}", file);
                }
            }
        } else {
            let output = match args.output_format {
                OutputFormat::Text => {
                    let mut sources = std::collections::HashMap::new();
                    for file in &files {
                        if let Ok(content) = std::fs::read_to_string(file) {
                            sources.insert(file.clone(), content);
                        }
                    }
                    formatters::format_text_with_context(&results, &sources)
                }
                OutputFormat::Json => formatters::format_json(&results),
                OutputFormat::Sarif => formatters::format_sarif(&results),
            };
            print!("{}", output);
        }

        // In watch mode, don't return error - just continue watching
        if args.watch {
            return Ok(());
        }
    }

    Ok(())
}

#[cfg(feature = "cli")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Args::parse();

    if args.no_color {
        colored::control::set_override(false);
    }

    // Handle init subcommand
    if let Some(Command::Init {
        output,
        format,
        interactive,
    }) = args.command
    {
        return init_config(&output, &format, interactive);
    }

    // Handle --list-rules flag
    if args.list_rules {
        list_rules();
        return Ok(());
    }

    // Validate files are provided
    if args.files.is_empty() && !args.stdin {
        eprintln!("error: FILES argument required (or use --stdin)");
        std::process::exit(1);
    }

    // Watch mode requires files, not stdin
    if args.watch && args.stdin {
        eprintln!("error: --watch cannot be used with --stdin");
        std::process::exit(1);
    }

    // If watch mode, delegate to watch function
    if args.watch {
        return run_watch_mode(&args);
    }

    // Handle stdin input
    let (files, stdin_content) = if args.stdin {
        (
            vec!["-".to_string()],
            Some(std::io::read_to_string(std::io::stdin())?),
        )
    } else {
        // Expand directories and filter ignored files
        let files = expand_paths(&args.files);
        let files = filter_ignored(files, &args.ignore)?;

        if files.is_empty() {
            if !args.quiet {
                println!("No files to lint.");
            }
            return Ok(());
        }
        (files, None)
    };

    // Build configuration with enable/disable rules
    let mut config = if let Some(ref config_path) = args.config {
        mkdlint::Config::from_file(config_path)?
    } else {
        mkdlint::Config::default()
    };

    // Apply --enable and --disable flags
    use mkdlint::RuleConfig;
    for rule in &args.enable {
        config
            .rules
            .insert(rule.to_uppercase(), RuleConfig::Enabled(true));
    }
    for rule in &args.disable {
        config
            .rules
            .insert(rule.to_uppercase(), RuleConfig::Enabled(false));
    }

    let mut strings = std::collections::HashMap::new();
    if let Some(content) = stdin_content {
        strings.insert("-".to_string(), content);
    }

    let options = LintOptions {
        files: if args.stdin { vec![] } else { files.clone() },
        strings,
        config: Some(config),
        no_inline_config: args.no_inline_config,
        ..Default::default()
    };

    let results = lint_sync(&options)?;

    if args.fix {
        let mut fixed_count = 0;
        let file_list = if args.stdin {
            vec!["-".to_string()]
        } else {
            files.clone()
        };

        for file_path in &file_list {
            let errors = match results.get(file_path) {
                Some(errors) if !errors.is_empty() => errors,
                _ => continue,
            };

            let has_fixes = errors.iter().any(|e| e.fix_info.is_some());
            if !has_fixes {
                continue;
            }

            let content = if file_path == "-" {
                options.strings.get("-").unwrap().clone()
            } else {
                std::fs::read_to_string(file_path)?
            };

            let fixed = apply_fixes(&content, errors);
            if fixed != content {
                if file_path == "-" {
                    // Output to stdout
                    print!("{}", fixed);
                } else {
                    std::fs::write(file_path, &fixed)?;
                    fixed_count += 1;
                    if args.verbose || !args.quiet {
                        println!("Fixed: {}", file_path);
                    }
                }
            }
        }

        if !args.quiet && !args.stdin {
            if fixed_count > 0 {
                println!("{} file(s) fixed.", fixed_count);
            } else {
                println!("No fixable issues found.");
            }
        }
    } else if results.is_empty() {
        if !args.quiet {
            println!("No errors found!");
        }
    } else {
        // Handle different output modes
        if args.quiet {
            // Quiet mode: just list files with errors
            for (file, errors) in &results.results {
                if !errors.is_empty() {
                    println!("{}", file);
                }
            }
        } else {
            let output = match args.output_format {
                OutputFormat::Text => {
                    // Read source files for context display
                    let mut sources = std::collections::HashMap::new();
                    if args.stdin {
                        if let Some(content) = options.strings.get("-") {
                            sources.insert("-".to_string(), content.clone());
                        }
                    } else {
                        for file_path in &files {
                            if let Ok(content) = std::fs::read_to_string(file_path) {
                                sources.insert(file_path.clone(), content);
                            }
                        }
                    }

                    let formatted = formatters::format_text_with_context(&results, &sources);

                    // Add summary if verbose
                    if args.verbose {
                        let total_errors: usize = results.results.values().map(|e| e.len()).sum();
                        let total_files = results.results.len();
                        format!(
                            "{}\n\nSummary: {} error(s) in {} file(s)",
                            formatted, total_errors, total_files
                        )
                    } else {
                        formatted
                    }
                }
                OutputFormat::Json => formatters::format_json(&results),
                OutputFormat::Sarif => formatters::format_sarif(&results),
            };
            println!("{}", output);
        }
        std::process::exit(1);
    }

    Ok(())
}

#[cfg(not(feature = "cli"))]
fn main() {
    eprintln!("CLI feature not enabled. Rebuild with --features cli");
    std::process::exit(1);
}
