//! Shared helpers for the test suite.
//!
//! These mirror the helpers in the source test harness: compile a generated
//! source string into an anchored regex, brute-force verify membership, and
//! enumerate zero-padded candidate strings.

#![allow(dead_code)]

use regex::Regex;
use regex_range::{to_regex_range, Options};

/// Call the library with default options and unwrap.
pub fn call(min: &str, max: Option<&str>, opts: &Options) -> String {
    to_regex_range(min, max, opts).expect("valid range")
}

/// Compile a source string anchored with `^...$`.
pub fn to_regex(src: &str) -> Regex {
    Regex::new(&format!("^{src}$")).expect("valid regex")
}

/// Build an anchored regex for `[min, max]` with default options.
pub fn range_regex(min: i64, max: i64) -> Regex {
    let src = call(
        &min.to_string(),
        Some(&max.to_string()),
        &Options::default(),
    );
    to_regex(&src)
}

/// Brute-force check that the generated regex matches a number iff it is in
/// `[min(min,max), max(min,max)]`, scanning the half-open window `(from-1, to)`.
pub fn verify_range(min: i64, max: i64, from: i64, to: i64) {
    let re = range_regex(min, max);
    let lo = min.min(max);
    let hi = min.max(max);
    let mut num = from - 1;
    loop {
        num += 1;
        if num >= to {
            break;
        }
        let want = lo <= num && hi >= num;
        let got = re.is_match(&num.to_string());
        assert_eq!(
            got,
            want,
            "num={num} range=({min},{max}) src=^{}$",
            re.as_str()
        );
    }
}

/// True when the string is zero-padded: an optional sign, a leading `0`, then at
/// least one more digit. The trailing digit may itself be `0`, so `"00"` is
/// padded. Mirrors the library's padding detection so the candidate forms match
/// the padded range under test.
fn is_padded(s: &str) -> bool {
    let bytes = s.as_bytes();
    let mut i = 0;
    if bytes.first() == Some(&b'-') {
        i = 1;
    }
    bytes.get(i) == Some(&b'0') && bytes.get(i + 1).is_some_and(|b| b.is_ascii_digit())
}

/// Enumerate the inclusive integer sequence between two endpoints, formatted the
/// way the source test helper did.
///
/// Padding kicks in only when an endpoint is zero-padded. When it does, every
/// value is zero-padded to the wider endpoint's raw character count, sign
/// included. Without padding the values use their natural form.
///
/// The exact formatting matters: the candidate strings feed a regex built for a
/// padded range, so a wrong width would test the wrong inputs.
pub fn fill(from: &str, to: &str) -> Vec<String> {
    let a: i64 = from.parse().expect("from parses");
    let b: i64 = to.parse().expect("to parses");
    let (lo, hi) = (a.min(b), a.max(b));
    let padded = is_padded(from) || is_padded(to);
    let target = from.chars().count().max(to.chars().count());

    (lo..=hi)
        .map(|n| {
            if !padded {
                return n.to_string();
            }
            let sign = if n < 0 { "-" } else { "" };
            let digits = n.unsigned_abs().to_string();
            let pad = target.saturating_sub(sign.len() + digits.len());
            format!("{sign}{}{digits}", "0".repeat(pad))
        })
        .collect()
}

/// Brute-force check over zero-padded candidate strings.
pub fn verify_zeros(min: &str, max: &str, from: &str, to: &str) {
    let src = call(min, Some(max), &Options::default());
    let re = to_regex(&src);
    let min_n: i64 = min.parse().expect("min parses");
    let max_n: i64 = max.parse().expect("max parses");
    let lo = min_n.min(max_n);
    let hi = min_n.max(max_n);
    for cand in fill(from, to) {
        let n: i64 = cand.parse().expect("candidate parses");
        let want = lo <= n && hi >= n;
        let got = re.is_match(&cand);
        assert_eq!(got, want, "cand={cand} range=({min},{max}) src=^{src}$");
    }
}
