//! Rejection tests and format contracts the source test suite does not cover.

mod common;

use std::collections::BTreeSet;

use common::{default_sorted_sequences, golden, js_u_to_rust, VERSIONS};
use emoji_test_regex_pattern::generate::generate_css_unicode_range;
use emoji_test_regex_pattern::OutputKind;
use regex::Regex;

fn compiled_u(dir: &str) -> Regex {
    let pattern = golden(dir, OutputKind::JavaScriptU);
    Regex::new(&format!("^(?:{})", js_u_to_rust(pattern.trim()))).unwrap()
}

#[test]
fn pattern_rejects_plain_text() {
    let re = compiled_u("emoji-17.0");
    assert!(re.find("hello").is_none());
    assert!(re.find("xyz").is_none());
    assert!(re.find("").is_none());
}

#[test]
fn pattern_matches_only_the_emoji_prefix() {
    let re = compiled_u("emoji-17.0");
    // A pile of poo followed by a letter matches the emoji, not the letter.
    let input = "\u{1F4A9}x";
    let m = re.find(input).expect("emoji prefix matches");
    assert_eq!(m.as_str(), "\u{1F4A9}");
}

#[test]
fn keycap_consumes_both_code_points() {
    // `#` followed by the combining enclosing keycap must match both, not just
    // the `#`.
    let re = compiled_u("emoji-13.0");
    let input = "#\u{20E3}";
    let m = re.find(input).expect("keycap matches");
    assert_eq!(m.as_str(), input);
}

#[test]
fn javascript_v_has_rgi_prefix() {
    for &dir in VERSIONS {
        let v = golden(dir, OutputKind::JavaScriptV);
        assert!(
            v.starts_with("\\p{RGI_Emoji}|"),
            "{dir}: javascript-v missing RGI prefix"
        );
    }
}

#[test]
fn java_equals_javascript_u_with_x_escape() {
    // The two files share the same trie. They differ only in astral escape
    // syntax: `\u{HEX}` in the JS variant, `\x{HEX}` in Java.
    for &dir in VERSIONS {
        let java = golden(dir, OutputKind::Java);
        let ju = golden(dir, OutputKind::JavaScriptU);
        assert_eq!(
            ju.replace("\\u{", "\\x{"),
            java,
            "{dir}: java vs javascript-u"
        );
    }
}

#[test]
fn cpp_re2_has_no_bare_backslash_u() {
    // RE2 renders every non `\xHH` code point as `\x{...}`. It never uses the
    // `\uXXXX` or `\u{...}` forms.
    for &dir in VERSIONS {
        let re2 = golden(dir, OutputKind::CppRe2);
        assert!(
            !re2.contains("\\u"),
            "{dir}: cpp-re2 should not contain a \\u escape"
        );
        assert!(
            re2.contains("\\x{"),
            "{dir}: cpp-re2 should use \\x{{...}} escapes"
        );
    }
}

#[test]
fn css_recomputed_from_index_strings() {
    // Independent recomputation, not a snapshot. Collect the code-point set from
    // the sequences and rebuild the CSS ranges.
    for &dir in VERSIONS {
        let sequences = default_sorted_sequences(dir);
        let produced = format!("{}\n", generate_css_unicode_range(&sequences));
        assert_eq!(produced, golden(dir, OutputKind::Css), "{dir}: css");
    }
}

#[test]
fn index_strings_are_default_sorted() {
    // Every version's index-strings list is in UTF-16 code-unit order.
    for &dir in VERSIONS {
        let seqs = default_sorted_sequences(dir);
        let mut expected = seqs.clone();
        expected.sort_by(|a, b| a.encode_utf16().cmp(b.encode_utf16()));
        assert_eq!(seqs, expected, "{dir}: index-strings not default-sorted");
    }
}

#[test]
fn sequence_sets_grow_with_version() {
    // Emoji are only added between versions. Each version's set contains the
    // previous version's set.
    let mut prev: Option<BTreeSet<String>> = None;
    for &dir in VERSIONS {
        let set: BTreeSet<String> = default_sorted_sequences(dir).into_iter().collect();
        if let Some(previous) = &prev {
            for s in previous {
                assert!(set.contains(s), "{dir}: dropped sequence {s:?}");
            }
        }
        prev = Some(set);
    }
}
