//! Full BMP coverage: every code unit, reversed, doubled, and continuous runs.
//!
//! The public API takes `&str`, so the samples are built with
//! `String::from_utf16_lossy`. That maps lone surrogates in the U+D800..U+DFFF
//! range to U+FFFD. Round-trips compare the lossy string against itself, which
//! still drives the whole non-surrogate BMP range plus the chunk-flush paths.
//! Lone-surrogate fidelity is covered separately at the code-unit level.

mod common;

use common::{compress_streamed, decompress_streamed};

/// Every code unit U+0000..U+FFFF as a lossy string.
fn sample_unicode() -> String {
    let units: Vec<u16> = (0u32..=0xFFFF).map(|x| x as u16).collect();
    String::from_utf16_lossy(&units)
}

/// The same set in reverse.
fn sample_reversed_unicode() -> String {
    let units: Vec<u16> = (0u32..=0xFFFF).rev().map(|x| x as u16).collect();
    String::from_utf16_lossy(&units)
}

/// Runs of 99 copies of one character, for code units 0, 32, 64, ... 65535.
fn sample_continuous() -> Vec<String> {
    (0u32..=0xFFFF)
        .step_by(32)
        .map(|i| {
            let ch = char::from_u32(i).unwrap_or('\u{FFFD}');
            std::iter::repeat_n(ch, 99).collect()
        })
        .collect()
}

#[test]
fn sample_lengths_are_valid() {
    assert_eq!(sample_unicode().encode_utf16().count(), 65536);
    assert_eq!(sample_reversed_unicode().encode_utf16().count(), 65536);
    assert_eq!(sample_continuous().len(), 2048);
}

#[test]
fn full_bmp_round_trips() {
    let sample = sample_unicode();
    let compressed = lzbase62::compress(&sample);
    assert!(!compressed.is_empty());
    assert_eq!(lzbase62::decompress(&compressed), sample);
}

#[test]
fn full_bmp_round_trips_streaming() {
    let sample = sample_unicode();
    let compressed = compress_streamed(&sample);
    assert!(!compressed.is_empty());
    assert_eq!(decompress_streamed(&compressed), sample);
}

#[test]
fn doubled_bmp_round_trips() {
    let large = sample_unicode().repeat(2);
    let compressed = lzbase62::compress(&large);
    assert!(!compressed.is_empty());
    assert_eq!(lzbase62::decompress(&compressed), large);
}

#[test]
fn doubled_bmp_round_trips_streaming() {
    let large = sample_unicode().repeat(2);
    let compressed = compress_streamed(&large);
    assert!(!compressed.is_empty());
    assert_eq!(decompress_streamed(&compressed), large);
}

#[test]
fn reversed_bmp_round_trips() {
    let sample = sample_reversed_unicode();
    let compressed = lzbase62::compress(&sample);
    assert!(!compressed.is_empty());
    assert_eq!(lzbase62::decompress(&compressed), sample);
}

#[test]
fn reversed_bmp_round_trips_streaming() {
    let sample = sample_reversed_unicode();
    let compressed = compress_streamed(&sample);
    assert!(!compressed.is_empty());
    assert_eq!(decompress_streamed(&compressed), sample);
}

#[test]
fn continuous_runs_shrink_and_round_trip() {
    // The shrink check compares UTF-16 unit counts, matching JS `.length`.
    for run in sample_continuous() {
        assert!(!run.is_empty());
        let compressed = lzbase62::compress(&run);
        assert!(!compressed.is_empty());
        assert!(
            run.encode_utf16().count() > compressed.len(),
            "run must shrink"
        );
        assert_eq!(lzbase62::decompress(&compressed), run);
    }
}

#[test]
fn continuous_runs_shrink_and_round_trip_streaming() {
    for run in sample_continuous() {
        let compressed = compress_streamed(&run);
        assert!(!compressed.is_empty());
        assert!(run.encode_utf16().count() > compressed.len());
        assert_eq!(decompress_streamed(&compressed), run);
    }
}

#[test]
fn astral_run_streams_across_chunk_boundary() {
    // A non-BMP character is two UTF-16 units and the streaming chunk size is
    // odd, so a long run lands a surrogate pair across a chunk boundary.
    let sample = "\u{1F600}".repeat(32_768);
    let compressed = lzbase62::compress(&sample);
    assert_eq!(decompress_streamed(&compressed), sample);
}
