use crate::matching::match_output;
use crate::parse::{ShoutFile, is_comment_line, trim_trailing_empty};
use crate::run::CommandResult;

fn escape_dollar(line: &str) -> String {
    if line.starts_with("$ ") {
        format!("\\{line}")
    } else {
        line.to_string()
    }
}

pub fn rewrite_file(
    file: &ShoutFile,
    results: &[CommandResult],
    original_content: &str,
) -> String {
    let lines: Vec<&str> = original_content.split('\n').collect();
    let mut output: Vec<String> = Vec::new();
    let mut cmd_idx = 0;
    let mut i = 0;

    while i < lines.len() {
        let line = lines[i];

        if is_comment_line(line) {
            output.push(line.to_string());
            i += 1;
        } else if line.starts_with("$ ") && !line.starts_with("\\$ ") {
            // Emit the command line as-is
            output.push(line.to_string());

            let cmd = file.commands.get(cmd_idx);
            let result = results.get(cmd_idx);

            if cmd.is_none() || result.is_none() {
                cmd_idx += 1;
                i += 1;
                continue;
            }
            let cmd = cmd.unwrap();
            let result = result.unwrap();

            // Skip past old expected output lines in the original
            let mut j = i + 1;
            while j < lines.len()
                && !is_comment_line(lines[j])
                && !(lines[j].starts_with("$ ") && !lines[j].starts_with("\\$ "))
            {
                j += 1;
            }

            // Collect old expected lines
            let old_expected_raw: Vec<String> = lines[i + 1..j].iter().map(|s| s.to_string()).collect();

            // Check if old expected output had an exit code marker
            let old_trimmed = trim_trailing_empty(&old_expected_raw);
            let old_exit_marker = if let Some(last) = old_trimmed.last() {
                if last.starts_with('[') && last.ends_with(']') {
                    let inner = &last[1..last.len() - 1];
                    if inner == "*" || inner.parse::<i32>().is_ok() {
                        Some(last.clone())
                    } else {
                        None
                    }
                } else {
                    None
                }
            } else {
                None
            };

            // Count trailing blank lines
            let mut trailing_blanks = 0;
            for k in (0..old_expected_raw.len()).rev() {
                if old_expected_raw[k].is_empty() {
                    trailing_blanks += 1;
                } else {
                    break;
                }
            }

            // If wildcards match, keep original expected output
            if match_output(&cmd.expected, &result.actual) {
                for ol in &old_expected_raw {
                    output.push(ol.clone());
                }
            } else {
                // Replace with actual output
                for al in &result.actual {
                    output.push(escape_dollar(al));
                }
                // Re-add exit code marker if it existed
                if let Some(marker) = old_exit_marker {
                    output.push(marker);
                }
                // Preserve trailing blank lines as separators
                for _ in 0..trailing_blanks {
                    output.push(String::new());
                }
            }

            i = j;
            cmd_idx += 1;
        } else if cmd_idx == 0 {
            // Lines before first command (directives, etc.)
            output.push(line.to_string());
            i += 1;
        } else {
            i += 1;
        }
    }

    output.join("\n")
}
