//! The `EmojiVersion` public surface: parsing, display, and resolution.

use std::str::FromStr;

use emoji_test_regex_pattern::{EmojiVersion, OutputKind};

#[test]
fn from_str_accepts_every_version_number() {
    let cases = [
        ("13.0", EmojiVersion::V13_0),
        ("13.1", EmojiVersion::V13_1),
        ("14.0", EmojiVersion::V14_0),
        ("15.0", EmojiVersion::V15_0),
        ("15.1", EmojiVersion::V15_1),
        ("16.0", EmojiVersion::V16_0),
        ("17.0", EmojiVersion::V17_0),
        ("latest", EmojiVersion::Latest),
    ];
    for (text, expected) in cases {
        assert_eq!(EmojiVersion::from_str(text), Ok(expected), "parse {text}");
    }
}

#[test]
fn from_str_rejects_unknown_input() {
    // Unknown numbers, empty input, whitespace, wrong casing, and directory-style
    // names all fail. Only the eight exact strings parse.
    let bad = [
        "",
        " ",
        "13",
        "13.",
        "13.2",
        "18.0",
        "99.9",
        "0.0",
        "Latest",
        "LATEST",
        "13.0 ",
        " 13.0",
        "emoji-13.0",
        "v13.0",
        "\u{1F004}",
    ];
    for text in bad {
        let err = EmojiVersion::from_str(text).expect_err("should reject");
        assert_eq!(err.input(), text, "error should carry the input {text:?}");
    }
}

#[test]
fn from_str_round_trips_through_number() {
    // Every concrete version prints its number and parses back to itself.
    for version in EmojiVersion::ALL {
        let text = version.number();
        assert_eq!(
            EmojiVersion::from_str(text),
            Ok(version),
            "round trip {text}"
        );
    }
    // Latest prints "latest" and parses back to Latest, not the resolved version.
    assert_eq!(EmojiVersion::Latest.number(), "latest");
    assert_eq!(
        EmojiVersion::from_str("latest"),
        Ok(EmojiVersion::Latest),
        "latest round trip stays Latest"
    );
}

#[test]
fn display_matches_number() {
    for version in EmojiVersion::ALL {
        assert_eq!(version.to_string(), version.number());
    }
    assert_eq!(EmojiVersion::Latest.to_string(), "latest");
}

#[test]
fn number_and_dir_name_values() {
    let cases = [
        (EmojiVersion::V13_0, "13.0", "emoji-13.0"),
        (EmojiVersion::V13_1, "13.1", "emoji-13.1"),
        (EmojiVersion::V14_0, "14.0", "emoji-14.0"),
        (EmojiVersion::V15_0, "15.0", "emoji-15.0"),
        (EmojiVersion::V15_1, "15.1", "emoji-15.1"),
        (EmojiVersion::V16_0, "16.0", "emoji-16.0"),
        (EmojiVersion::V17_0, "17.0", "emoji-17.0"),
        (EmojiVersion::Latest, "latest", "latest"),
    ];
    for (version, number, dir) in cases {
        assert_eq!(version.number(), number, "number for {version}");
        assert_eq!(version.dir_name(), dir, "dir_name for {version}");
    }
}

#[test]
fn resolve_maps_latest_to_highest_and_is_identity_otherwise() {
    assert_eq!(EmojiVersion::Latest.resolve(), EmojiVersion::V17_0);
    assert_eq!(EmojiVersion::HIGHEST, EmojiVersion::V17_0);
    for version in EmojiVersion::ALL {
        assert_eq!(version.resolve(), version, "resolve identity for {version}");
    }
}

#[test]
fn all_lists_seven_concrete_versions_without_latest() {
    assert_eq!(EmojiVersion::ALL.len(), 7);
    assert!(!EmojiVersion::ALL.contains(&EmojiVersion::Latest));
    // ALL is ascending.
    for pair in EmojiVersion::ALL.windows(2) {
        assert!(pair[0] < pair[1], "ALL not ascending at {:?}", pair);
    }
}

#[test]
fn parse_version_error_displays_message() {
    let err = "12.0".parse::<EmojiVersion>().unwrap_err();
    assert_eq!(err.to_string(), "unknown emoji version: 12.0");
    assert_eq!(err.input(), "12.0");
}

#[test]
fn output_kind_file_names_and_display() {
    let cases = [
        (OutputKind::Index, "index.txt", true),
        (OutputKind::IndexStrings, "index-strings.txt", true),
        (OutputKind::Css, "css.txt", true),
        (OutputKind::JavaScript, "javascript.txt", false),
        (OutputKind::JavaScriptU, "javascript-u.txt", false),
        (OutputKind::JavaScriptV, "javascript-v.txt", false),
        (OutputKind::Java, "java.txt", false),
        (OutputKind::CppRe2, "cpp-re2.txt", false),
    ];
    for (kind, name, trailing) in cases {
        assert_eq!(kind.file_name(), name, "file_name for {kind:?}");
        assert_eq!(kind.to_string(), name, "display for {kind:?}");
        assert_eq!(
            kind.has_trailing_newline(),
            trailing,
            "trailing newline flag for {kind:?}"
        );
    }
    assert_eq!(OutputKind::ALL.len(), 8);
}
