// Exact Rust translation of LibreMetaverse.Tests/SlurlParserTests.cs.
// Source SHA-256: 3626ff6fae5a303e4037ac2b5742dfc7fa226e04a33b8d358ba355fccdd05611

use libremetaverse::{Error, SlappCommand, SlurlParser, ViewerUriType};
use libremetaverse_types::UUID;
use std::collections::HashMap;

fn uuid() -> UUID {
    UUID::random().expect("UUID Random")
}

fn parse(location: &str) -> SlurlParser {
    SlurlParser::new(Some(location.into())).expect("SlurlParser constructor")
}

fn assert_location(parser: &SlurlParser, sim: &str, x: i32, y: i32, z: i32) {
    assert_eq!(parser.sim(), sim);
    assert_eq!(parser.x(), x);
    assert_eq!(parser.y(), y);
    assert_eq!(parser.z(), z);
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseSimpleLocation_ParsesCorrectly::test 4ca1200596cd99959db205bd40050c9ed62ac68fcd58079d52882e3b24af3fdf translated
#[test]
fn parse_simple_location_parses_correctly() {
    let parser = parse("Hooper/128/128/32");
    assert_location(&parser, "Hooper", 128, 128, 32);
    assert_eq!(parser.uri_type(), ViewerUriType::Location);
    assert!(parser.is_location());
    assert!(!parser.is_application());
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseSlurl_ParsesCorrectly::test b389e823a365884bb10813a587c5922416dc8d0737f36bfde99c56cc4e4accda translated
#[test]
fn parse_slurl_parses_correctly() {
    let parser = parse("secondlife://Hooper/179/18/32");
    assert_location(&parser, "Hooper", 179, 18, 32);
    assert_eq!(parser.uri_type(), ViewerUriType::Location);
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseSlurlWithTrailingSlash_ParsesCorrectly::test eb8513ad0d5dd9f67b537c1a844f738ef9363efdafd97aa31ec6fb697395c270 translated
#[test]
fn parse_slurl_with_trailing_slash_parses_correctly() {
    let parser = parse("secondlife://Hooper/179/18/32/");
    assert_eq!(
        (parser.sim(), parser.x(), parser.y(), parser.z()),
        ("Hooper".into(), 179, 18, 32)
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseLegacyUri_ParsesCorrectly::test d3e28edefeaa13a00d666cdc40c5ee60a24563462472786e0fa186262e8f6e73 translated
#[test]
fn parse_legacy_uri_parses_correctly() {
    let parser = parse("uri:Hooper&179&18&32");
    assert_location(&parser, "Hooper", 179, 18, 32);
    assert_eq!(parser.uri_type(), ViewerUriType::Location);
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseLocationWithDefaults_UsesDefaultCoordinates::test a89cceba86e86028cd70ee28b0f31a4c40c23491d582a0f70fc06b98bfeb3605 translated
#[test]
fn parse_location_with_defaults_uses_default_coordinates() {
    let parser = parse("secondlife://Hooper");
    assert_eq!(
        (parser.sim(), parser.x(), parser.y(), parser.z()),
        ("Hooper".into(), 128, 128, 0)
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseLocationPartialCoordinates_UsesDefaults::test b6418ccbb2b00b741fcc92db7d0d8577e7334360934de7418f75ed08d7b3da24 translated
#[test]
fn parse_location_partial_coordinates_uses_defaults() {
    let parser = parse("secondlife://Hooper/100");
    assert_eq!(
        (parser.sim(), parser.x(), parser.y(), parser.z()),
        ("Hooper".into(), 100, 128, 0)
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseAgentSlapp_ParsesCorrectly::test 77a274bb3b4bf189fab0ce68a729057d04376bf3d6d45d0a5443d0f301f5fbb8 translated
#[test]
fn parse_agent_slapp_parses_correctly() {
    let id = uuid();
    let id_text = id.to_string();
    let parser = parse(&format!("secondlife:///app/agent/{id_text}/about"));
    assert_eq!(parser.uri_type(), ViewerUriType::Application);
    assert_eq!(parser.command(), SlappCommand::Agent);
    assert_eq!(parser.command_path(), format!("agent/{id_text}/about"));
    assert!(parser.is_application());
    assert!(!parser.is_location());
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseGroupSlapp_ParsesCorrectly::test 5435e301f76398ff2f35e0b65a7055de6d42b1a4662936ee949005182511fcd6 translated
#[test]
fn parse_group_slapp_parses_correctly() {
    let id = uuid();
    let id_text = id.to_string();
    let parser = parse(&format!("secondlife:///app/group/{id_text}/about"));
    assert_eq!(parser.uri_type(), ViewerUriType::Application);
    assert_eq!(parser.command(), SlappCommand::Group);
    assert_eq!(parser.command_path(), format!("group/{id_text}/about"));
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseTeleportSlapp_ParsesLocationData::test 68eea168b7480f125a4bff20b582af87c0f6d62502821bf57eb734b12f87eed5 translated
#[test]
fn parse_teleport_slapp_parses_location_data() {
    let parser = parse("secondlife:///app/teleport/Hooper/100/200/50");
    assert_eq!(parser.uri_type(), ViewerUriType::Application);
    assert_eq!(parser.command(), SlappCommand::Teleport);
    assert_location(&parser, "Hooper", 100, 200, 50);
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseWorldMapSlapp_ParsesLocationData::test ad7f5ce07a837501fa74dda643f1c0d278a2f51fe27330d3419c57683127bf9e translated
#[test]
fn parse_world_map_slapp_parses_location_data() {
    let parser = parse("secondlife:///app/worldmap/Sandbox/128/128/0");
    assert_eq!(parser.uri_type(), ViewerUriType::Application);
    assert_eq!(parser.command(), SlappCommand::WorldMap);
    assert_location(&parser, "Sandbox", 128, 128, 0);
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseLoginSlapp_ParsesQueryParameters::test a0622607f53b8c19348540e7b858d3c75be22b47b9afc92505121b3430c33550 translated
#[test]
fn parse_login_slapp_parses_query_parameters() {
    let parser = parse("secondlife:///app/login?last=Resident&session=abc123&location=home");
    assert_eq!(parser.uri_type(), ViewerUriType::Application);
    assert_eq!(parser.command(), SlappCommand::Login);
    let query = parser.query_parameters();
    assert_eq!(query.get("last").map(String::as_str), Some("Resident"));
    assert_eq!(query.get("session").map(String::as_str), Some("abc123"));
    assert_eq!(query.get("location").map(String::as_str), Some("home"));
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseObjectImSlapp_ParsesQueryParameters::test af9c2bdcfb212f08618719c85da41938207b6978fe2042a3c4451f7fd0075d6f translated
#[test]
fn parse_object_im_slapp_parses_query_parameters() {
    let object = uuid();
    let owner = uuid();
    let object_text = object.to_string();
    let owner_text = owner.to_string();
    let parser = parse(&format!(
        "secondlife:///app/objectim/{object_text}?name=TestObject&owner={owner_text}&groupowned=true&slurl=Hooper/128/128/25"
    ));
    let query = parser.query_parameters();
    assert_eq!(parser.command(), SlappCommand::ObjectIm);
    assert_eq!(query.get("name").map(String::as_str), Some("TestObject"));
    assert_eq!(query.get("owner"), Some(&owner.to_string()));
    assert_eq!(query.get("groupowned").map(String::as_str), Some("true"));
    assert_eq!(
        query.get("slurl").map(String::as_str),
        Some("Hooper/128/128/25")
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseSearchSlapp_ParsesCorrectly::test a77776e74cff175bde3e974344502369e679db15f45fe5c0d614c80732549765 translated
#[test]
fn parse_search_slapp_parses_correctly() {
    let parser = parse("secondlife:///app/search/places/Sandbox");
    assert_eq!(parser.command(), SlappCommand::Search);
    assert!(parser.command_path().contains("places"));
    assert!(parser.command_path().contains("Sandbox"));
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetRawLocation_ReturnsCorrectFormat::test 30dafa3972780bbbb07f1edaf55ed4bc3b01080e616588768ebef9df54678af3 translated
#[test]
fn get_raw_location_returns_correct_format() {
    assert_eq!(
        parse("Hooper/100/200/50").get_raw_location().unwrap(),
        "Hooper/100/200/50"
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetSlurl_ReturnsCorrectFormat::test 1e6b6fef442eab1ca6874df75f1a0ea380ec7f25974979d3cd878e10fc49fb40 translated
#[test]
fn get_slurl_returns_correct_format() {
    assert_eq!(
        parse("Hooper/100/200/50").get_slurl().unwrap(),
        "secondlife://Hooper/100/200/50/"
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetStartLocationUri_ReturnsCorrectFormat::test c7cc2d49e52ed6ebb19e09607a2b191fb3835e8f3804c7b07c5c9e8fd3b9bcae translated
#[test]
fn get_start_location_uri_returns_correct_format() {
    assert_eq!(
        parse("Hooper/100/200/50").get_start_location_uri().unwrap(),
        "uri:Hooper&100&200&50"
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetAgentUrl_GeneratesCorrectUrl::test b656f795ce39c9bc50df167e20e23b76b57929feaad1756d3190920f5410d0d4 translated
#[test]
fn get_agent_url_generates_correct_url() {
    let id = uuid();
    let id_text = id.to_string();
    assert_eq!(
        SlurlParser::get_agent_url(id, Some("inspect".into())).unwrap(),
        format!("secondlife:///app/agent/{id_text}/inspect")
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetAgentUrl_DefaultAction_UsesAbout::test 642b0543f72427cf646bcb8445b9acff4c1fdb0d26ef3f0dc6c10eaaed73028b translated
#[test]
fn get_agent_url_default_action_uses_about() {
    assert!(
        SlurlParser::get_agent_url(uuid(), None)
            .unwrap()
            .contains("/about")
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetGroupUrl_GeneratesCorrectUrl::test 919cc7a17770fb65a83812836819a19d6dd4e630a848741d12df868da3d949d5 translated
#[test]
fn get_group_url_generates_correct_url() {
    let id = uuid();
    let id_text = id.to_string();
    assert_eq!(
        SlurlParser::get_group_url(id, Some("inspect".into())).unwrap(),
        format!("secondlife:///app/group/{id_text}/inspect")
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetTeleportUrl_GeneratesCorrectUrl::test 3465edd1ecdd6e521c52687570d32e288bfa29eb6e47666305c64ee5322df159 translated
#[test]
fn get_teleport_url_generates_correct_url() {
    assert_eq!(
        SlurlParser::get_teleport_url("Hooper".into(), Some(100), Some(200), Some(50)).unwrap(),
        "secondlife:///app/teleport/Hooper/100/200/50"
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetTeleportUrl_WithDefaults_UsesDefaultCoordinates::test 823d524cc81d39753549583346953122c372f5575aa03d8bf101d1be3eae845d translated
#[test]
fn get_teleport_url_with_defaults_uses_default_coordinates() {
    assert_eq!(
        SlurlParser::get_teleport_url("Hooper".into(), None, None, None).unwrap(),
        "secondlife:///app/teleport/Hooper/128/128/0"
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetWorldMapUrl_GeneratesCorrectUrl::test 7c19a936e0d641195f3806291b5b88970affa46f540ef950d55df709b52ad56b translated
#[test]
fn get_world_map_url_generates_correct_url() {
    assert_eq!(
        SlurlParser::get_world_map_url("Sandbox".into(), Some(64), Some(64), Some(100)).unwrap(),
        "secondlife:///app/worldmap/Sandbox/64/64/100"
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetObjectImUrl_GeneratesCorrectUrl::test edf724227cc86abc84d223637f34c607f9fc30f9e85b53a9ec8c8f2d2b7bcbf6 translated
#[test]
fn get_object_im_url_generates_correct_url() {
    let object = uuid();
    let owner = uuid();
    let object_text = object.to_string();
    let owner_text = owner.to_string();
    let url = SlurlParser::get_object_im_url(
        object,
        "Test Object".into(),
        owner,
        true,
        "Hooper/128/128/25".into(),
    )
    .unwrap();
    assert!(url.contains(&format!("objectim/{object_text}")));
    assert!(url.contains("name=Test%20Object"));
    assert!(url.contains(&format!("owner={owner_text}")));
    assert!(url.contains("groupowned=true"));
    assert!(url.contains("slurl=Hooper"));
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetObjectImUrl_NotGroupOwned_OmitsGroupOwnedParameter::test 764978a618429216a0bfef4e72ac44a329d2b2d1c5b13af889db9cf86814cd99 translated
#[test]
fn get_object_im_url_not_group_owned_omits_group_owned_parameter() {
    let url = SlurlParser::get_object_im_url(
        uuid(),
        "Test Object".into(),
        uuid(),
        false,
        "Hooper/128/128/25".into(),
    )
    .unwrap();
    assert!(!url.contains("groupowned"));
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetSearchUrl_GeneratesCorrectUrl::test aeb79bf4e5e51368b4520eec3cc5beeb767101b81f1eaa3f3ad118749dc5c2f0 translated
#[test]
fn get_search_url_generates_correct_url() {
    assert_eq!(
        SlurlParser::get_search_url("places".into(), "sandbox island".into()).unwrap(),
        "secondlife:///app/search/places/sandbox%20island"
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetLoginUrl_WithAllParameters_GeneratesCorrectUrl::test 0d9dc8d623e56ec1e8e0d414eb2c898bfd7ee37712972287834c2df6a99da17c translated
#[test]
fn get_login_url_with_all_parameters_generates_correct_url() {
    let url = SlurlParser::get_login_url(
        Some("Resident".into()),
        Some("session123".into()),
        Some("home".into()),
    )
    .unwrap();
    assert!(url.contains("login?"));
    assert!(url.contains("last=Resident"));
    assert!(url.contains("session=session123"));
    assert!(url.contains("location=home"));
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetLoginUrl_WithNoParameters_GeneratesBaseUrl::test 1fcfa164861bcc115f2935f1c20c276dea05802cf35dbb55664ff1c36140c135 translated
#[test]
fn get_login_url_with_no_parameters_generates_base_url() {
    assert_eq!(
        SlurlParser::get_login_url(None, None, None).unwrap(),
        "secondlife:///app/login"
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.GetSlappUrl_WithQueryParams_GeneratesCorrectUrl::test 37ca9ebe06ce42f5919d9b6ec30459d1455f78bde910a61d04187e74e7ce3d07 translated
#[test]
fn get_slapp_url_with_query_params_generates_correct_url() {
    let query = HashMap::from([
        ("test".into(), "value".into()),
        ("foo".into(), "bar".into()),
    ]);
    let url =
        SlurlParser::get_slapp_url(SlappCommand::Help, Some("topic".into()), Some(query)).unwrap();
    assert!(url.contains("help/topic"));
    assert!(url.contains("test=value"));
    assert!(url.contains("foo=bar"));
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseNull_ThrowsArgumentNullException::test 98c170ff5f03f843918c0d2aecddb23e2c2605e31100f5d3178ad7cb306eef5d translated
#[test]
fn parse_null_throws_argument_null() {
    match SlurlParser::new(None) {
        Err(error) => assert_eq!(error, Error::ArgumentNull),
        Ok(_) => panic!("expected ArgumentNull"),
    }
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseEmptyString_ThrowsArgumentException::test a5d89e3349033fa4efa1b00339556d811f175c52ca5b183a2d19b247a8eaeed8 translated
#[test]
fn parse_empty_string_throws_argument() {
    match SlurlParser::new(Some(String::new())) {
        Err(error) => assert_eq!(error, Error::Argument),
        Ok(_) => panic!("expected Argument"),
    }
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseUrlEncodedQueryParams_DecodesCorrectly::test 72ac64c89f3a08bbecfb80aaaf11f5ee59a383eeeb9578676af5b9d83f374cc4 translated
#[test]
fn parse_url_encoded_query_params_decodes_correctly() {
    assert!(
        parse("secondlife:///app/search/places/sandbox%20island")
            .command_path()
            .contains("sandbox%20island")
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.RoundTripLocation_PreservesData::test 2cba174b3609af177295b469bad1641a98d8dc79d95aa4ef22403923edd4d092 translated
#[test]
fn round_trip_location_preserves_data() {
    let original = parse("Hooper/100/200/50");
    let parsed = parse(&original.get_slurl().unwrap());
    assert_location(
        &parsed,
        &original.sim(),
        original.x(),
        original.y(),
        original.z(),
    );
}

// parity-case: LibreMetaverse.Tests/SlurlParserTests.cs::SlurlParserTests.ParseAllSlappCommands_RecognizesCorrectly::test abf53480da20d1ff57f5f06f1433c120ea84e0d6d5363cb33e4a275d76cfa306 translated
#[test]
fn parse_all_slapp_commands_recognizes_correctly() {
    let commands = [
        ("secondlife:///app/agent/uuid/about", SlappCommand::Agent),
        (
            "secondlife:///app/appearance/show",
            SlappCommand::Appearance,
        ),
        ("secondlife:///app/balance/request", SlappCommand::Balance),
        ("secondlife:///app/chat/1/hello", SlappCommand::Chat),
        (
            "secondlife:///app/classified/uuid/about",
            SlappCommand::Classified,
        ),
        ("secondlife:///app/event/uuid/about", SlappCommand::Event),
        (
            "secondlife:///app/experience/uuid/profile",
            SlappCommand::Experience,
        ),
        ("secondlife:///app/group/uuid/about", SlappCommand::Group),
        ("secondlife:///app/help/search", SlappCommand::Help),
        (
            "secondlife:///app/inventory/uuid/select",
            SlappCommand::Inventory,
        ),
        (
            "secondlife:///app/keybinding/jump",
            SlappCommand::Keybinding,
        ),
        ("secondlife:///app/login", SlappCommand::Login),
        (
            "secondlife:///app/maptrackavatar/uuid",
            SlappCommand::MapTrackAvatar,
        ),
        ("secondlife:///app/objectim/uuid", SlappCommand::ObjectIm),
        (
            "secondlife:///app/openfloater/preferences",
            SlappCommand::OpenFloater,
        ),
        ("secondlife:///app/parcel/uuid/about", SlappCommand::Parcel),
        ("secondlife:///app/region/uuid/about", SlappCommand::Region),
        ("secondlife:///app/search/all/test", SlappCommand::Search),
        (
            "secondlife:///app/sharewithavatar/uuid",
            SlappCommand::ShareWithAvatar,
        ),
        (
            "secondlife:///app/teleport/Hooper/128/128/0",
            SlappCommand::Teleport,
        ),
        (
            "secondlife:///app/voicecallavatar/uuid",
            SlappCommand::VoiceCallAvatar,
        ),
        (
            "secondlife:///app/wear_folder/?folder_id=uuid",
            SlappCommand::WearFolder,
        ),
        (
            "secondlife:///app/worldmap/Hooper/128/128/0",
            SlappCommand::WorldMap,
        ),
    ];
    for (url, expected) in commands {
        assert_eq!(parse(url).command(), expected, "failed to parse {url}");
    }
}
