//! Sort order, the extra sequences, and LCD RGI coverage.

mod common;

use std::cmp::Ordering;
use std::collections::HashSet;

use common::{default_sorted_sequences, lcd_rgi_emoji, VERSIONS};
use emoji_test_regex_pattern::generate::{compare, default_sort, sort_sequences, EXTRA_SEQUENCES};

#[test]
fn extra_sequences_has_expected_shape() {
    assert_eq!(EXTRA_SEQUENCES.len(), 41);
    // First ten are two astral code points each (base + skin tone).
    for seq in &EXTRA_SEQUENCES[..10] {
        let chars: Vec<char> = seq.chars().collect();
        assert_eq!(chars.len(), 2);
        assert!(chars.iter().all(|c| *c as u32 >= 0x1_0000));
    }
    // Remaining 31 are base char + VS16 (U+FE0F).
    for seq in &EXTRA_SEQUENCES[10..] {
        let chars: Vec<char> = seq.chars().collect();
        assert_eq!(chars.len(), 2);
        assert_eq!(chars[1], '\u{FE0F}');
    }
    // The last extra is the only one with an astral base.
    assert_eq!(EXTRA_SEQUENCES[40], "\u{1F004}\u{FE0F}");
}

#[test]
fn every_extra_sequence_appears_in_every_version() {
    for &dir in VERSIONS {
        let set: HashSet<String> = default_sorted_sequences(dir).into_iter().collect();
        for seq in EXTRA_SEQUENCES {
            assert!(set.contains(seq), "{dir} missing extra {seq:?}");
        }
    }
}

#[test]
fn compare_longest_first_then_lexicographic() {
    // Longer code-point count sorts first regardless of bytes.
    assert_eq!(compare("ab", "a"), Ordering::Less);
    assert_eq!(compare("a", "ab"), Ordering::Greater);
    // Equal length ties break ascending.
    assert_eq!(compare("a", "b"), Ordering::Less);
    assert_eq!(compare("b", "a"), Ordering::Greater);
    assert_eq!(compare("a", "a"), Ordering::Equal);
}

#[test]
fn compare_length_is_code_points_not_bytes() {
    // One astral char is one code point. It ties with a one-BMP-char string and
    // outranks a two-char string.
    let astral = "\u{1F004}"; // 1 code point, 4 UTF-8 bytes
    let bmp = "a"; // 1 code point, 1 byte
    let two = "ab"; // 2 code points
    assert_eq!(compare(astral, two), Ordering::Greater);
    assert_eq!(astral.chars().count(), bmp.chars().count());
}

#[test]
fn compare_tie_break_is_utf16_not_scalar_value() {
    // U+E000 is a BMP private-use char. U+1F004 is astral. In UTF-16 the astral
    // char leads with a high surrogate (0xD83C), which sorts below 0xE000. By
    // Unicode scalar value the astral char would sort after. The comparator must
    // pick the UTF-16 order: astral first.
    let bmp = "\u{E000}";
    let astral = "\u{1F004}";
    assert_eq!(bmp.chars().count(), astral.chars().count());
    assert_eq!(compare(astral, bmp), Ordering::Less);
    assert_eq!(compare(bmp, astral), Ordering::Greater);
    // Rust's default &str ordering disagrees, which is why the crate does not
    // use it here.
    assert_eq!(astral.cmp(bmp), Ordering::Greater);
}

#[test]
fn compare_sort_then_default_sort_round_trips() {
    // The recovered list is already default-sorted. Sorting by `compare` and
    // then default-sorting must return the original order.
    for &dir in VERSIONS {
        let original = default_sorted_sequences(dir);

        let mut by_compare = original.clone();
        sort_sequences(&mut by_compare);
        // `compare` sort is longest-first, so it is not the default order.
        assert_ne!(by_compare, original, "{dir} orders should differ");

        let mut back = by_compare;
        default_sort(&mut back);
        assert_eq!(back, original, "{dir} default sort round-trip");
    }
}

#[test]
fn every_lcd_rgi_string_is_present_in_every_version() {
    let lcd = lcd_rgi_emoji();
    assert!(!lcd.is_empty());
    for &dir in VERSIONS {
        let set: HashSet<String> = default_sorted_sequences(dir).into_iter().collect();
        for string in &lcd {
            assert!(set.contains(string), "{dir} missing LCD RGI {string:?}");
        }
    }
}
