use regex::Regex;

/// Check if a single line matches a pattern that may contain inline `...` wildcards.
pub fn match_line(pattern: &str, actual: &str) -> bool {
    if !pattern.contains("...") {
        return pattern == actual;
    }

    let parts: Vec<&str> = pattern.split("...").collect();
    let escaped: Vec<String> = parts.iter().map(|p| regex::escape(p)).collect();
    let re_str = format!("^{}$", escaped.join(".*"));
    match Regex::new(&re_str) {
        Ok(re) => re.is_match(actual),
        Err(_) => false,
    }
}

/// Match expected output against actual output, supporting multi-line `...` wildcards.
pub fn match_output(expected: &[String], actual: &[String]) -> bool {
    do_match(expected, 0, actual, 0)
}

fn do_match(expected: &[String], ei: usize, actual: &[String], ai: usize) -> bool {
    // Both exhausted — match
    if ei == expected.len() && ai == actual.len() {
        return true;
    }

    // Expected exhausted but actual remains — no match
    if ei == expected.len() {
        return false;
    }

    let exp = &expected[ei];

    // Multi-line wildcard
    if exp == "..." {
        // Try matching zero or more actual lines
        for skip in ai..=actual.len() {
            if do_match(expected, ei + 1, actual, skip) {
                return true;
            }
        }
        return false;
    }

    // Actual exhausted but expected remains — no match
    if ai == actual.len() {
        return false;
    }

    // Line-level match (with possible inline wildcards)
    if match_line(exp, &actual[ai]) {
        return do_match(expected, ei + 1, actual, ai + 1);
    }

    false
}

#[derive(Debug, Clone, PartialEq)]
pub enum DiffKind {
    Equal,
    Expected,
    Actual,
    Context,
}

#[derive(Debug, Clone)]
pub struct DiffLine {
    pub kind: DiffKind,
    pub text: String,
}

pub fn diff(expected: &[String], actual: &[String]) -> Vec<DiffLine> {
    let mut result = Vec::new();
    let mut ei = 0;
    let mut ai = 0;

    while ei < expected.len() || ai < actual.len() {
        if ei < expected.len() && expected[ei] == "..." {
            // Find where the wildcard ends by looking at next expected line
            let next_exp = if ei + 1 < expected.len() {
                Some(&expected[ei + 1])
            } else {
                None
            };
            if next_exp.is_none() {
                // ... at end matches everything remaining
                result.push(DiffLine { kind: DiffKind::Context, text: "...".to_string() });
                break;
            }
            // Skip actual lines until we find the next expected match
            result.push(DiffLine { kind: DiffKind::Context, text: "...".to_string() });
            ei += 1;
            let next = next_exp.unwrap();
            while ai < actual.len() && !match_line(next, &actual[ai]) {
                ai += 1;
            }
            continue;
        }

        if ei < expected.len() && ai < actual.len() {
            if match_line(&expected[ei], &actual[ai]) {
                result.push(DiffLine { kind: DiffKind::Equal, text: actual[ai].clone() });
                ei += 1;
                ai += 1;
            } else {
                result.push(DiffLine { kind: DiffKind::Expected, text: expected[ei].clone() });
                result.push(DiffLine { kind: DiffKind::Actual, text: actual[ai].clone() });
                ei += 1;
                ai += 1;
            }
        } else if ei < expected.len() {
            result.push(DiffLine { kind: DiffKind::Expected, text: expected[ei].clone() });
            ei += 1;
        } else {
            result.push(DiffLine { kind: DiffKind::Actual, text: actual[ai].clone() });
            ai += 1;
        }
    }

    result
}
