//! Targeted match and not-match assertions on the compiled regex.

mod common;

use common::{call, to_regex};
use regex_range::Options;

fn matches(min: &str, max: &str, value: &str) -> bool {
    let src = call(min, Some(max), &Options::default());
    to_regex(&src).is_match(value)
}

#[test]
fn matches_given_numbers() {
    // toRange(1, 150)
    let src = call("1", Some("150"), &Options::default());
    let re = to_regex(&src);
    assert!(re.is_match("125"));
    assert!(!re.is_match("0"));
    assert!(re.is_match("1"));
    assert!(re.is_match("126"));
    assert!(re.is_match("150"));
    assert!(!re.is_match("151"));

    // toRange(1, 125)
    let src = call("1", Some("125"), &Options::default());
    let re = to_regex(&src);
    assert!(re.is_match("125"));
    assert!(!re.is_match("0"));
    assert!(re.is_match("1"));
    assert!(!re.is_match("126"));
    assert!(!re.is_match("150"));
    assert!(!re.is_match("151"));
}

#[test]
fn matches_all_in_range_1_to_59() {
    let src = call("1", Some("59"), &Options::default());
    let re = to_regex(&src);
    for i in 0..100 {
        let want = (1..=59).contains(&i);
        assert_eq!(re.is_match(&i.to_string()), want, "i={i}");
    }
}

/// Mirror of the source `matchRange` helper: lock the exact string, then assert
/// member and non-member values.
fn match_range(min: &str, max: &str, expected: &str, member: &[&str], non_member: &[&str]) {
    let src = call(min, Some(max), &Options::default());
    assert_eq!(src, expected, "({min},{max})");
    let re = to_regex(&src);
    for v in member {
        assert!(re.is_match(v), "{src} should match {v}");
    }
    for v in non_member {
        assert!(!re.is_match(v), "{src} should not match {v}");
    }
}

#[test]
fn match_range_negatives() {
    match_range("-1", "-1", "-1", &["-1"], &["-2", "0", "1"]);
    match_range(
        "-1",
        "-10",
        "(?:-[1-9]|-10)",
        &["-1", "-5", "-10"],
        &["-11", "0"],
    );
    match_range(
        "-1",
        "3",
        "(?:-1|[0-3])",
        &["-1", "0", "1", "2", "3"],
        &["-2", "4"],
    );
}

#[test]
fn match_range_positives() {
    match_range(
        "99",
        "100000",
        "(?:99|[1-9][0-9]{2,4}|100000)",
        &[
            "99", "999", "989", "100", "9999", "9899", "10009", "10999", "100000",
        ],
        &["0", "9", "100001", "100009"],
    );
}

#[test]
fn large_in_range_inputs_do_not_panic() {
    // 18 and 19 digit bounds are valid i64 values. The break-point math used to
    // overflow and panic here. It must return Ok and match the endpoints.
    let cases = [
        ("1", "999999999999999999"),  // 18 nines
        ("-999999999999999999", "1"), // negative 18-digit bound
        ("1", "9223372036854775807"), // i64::MAX
        ("9000000000000000000", "9223372036854775807"),
    ];
    for (a, b) in cases {
        let src = call(a, Some(b), &Options::default());
        let re = to_regex(&src);
        let lo: i64 = a.parse().unwrap();
        let hi: i64 = b.parse().unwrap();
        let (lo, hi) = (lo.min(hi), lo.max(hi));
        assert!(re.is_match(&lo.to_string()), "{src} should match lo {lo}");
        assert!(re.is_match(&hi.to_string()), "{src} should match hi {hi}");
        assert!(
            re.is_match(&(lo + 1).to_string()),
            "{src} should match lo+1"
        );
        if let Some(below) = lo.checked_sub(1) {
            assert!(
                !re.is_match(&below.to_string()),
                "{src} should not match {below}"
            );
        }
    }
}

#[test]
fn padded_adjacent_allows_optional_zeros() {
    // A padded adjacent range took a fast path that emitted raw literals and
    // dropped the optional-zero forms. With padding it must accept "1" and "01".
    let src = call("01", Some("02"), &Options::default());
    let re = to_regex(&src);
    for v in ["1", "2", "01", "02"] {
        assert!(re.is_match(v), "{src} should match {v}");
    }
    for v in ["0", "3", "001"] {
        assert!(!re.is_match(v), "{src} should not match {v}");
    }
}

#[test]
fn reversed_padded_range_rejects_overwide_form() {
    // pad_zeros used an absolute width difference, so a band wider than the
    // padded max could gain a spurious leading zero. "1000" must not match a
    // five-character "01000".
    let src = call("1000", Some("099"), &Options::default());
    let re = to_regex(&src);
    assert!(re.is_match("1000"), "{src} should match 1000");
    assert!(re.is_match("99"), "{src} should match 99");
    assert!(!re.is_match("01000"), "{src} should not match 01000");
}

#[test]
fn negative_sign_required_where_expected() {
    // -[1-9] requires the sign; 1 alone is matched by the positive half only
    // when the range includes it.
    assert!(matches("-9", "9", "-5"));
    assert!(matches("-9", "9", "5"));
    assert!(!matches("-9", "9", "-50"));
}
