//! Movement, object, parcel, estate, and grid command groups for native `TestClient`.

#![allow(
    clippy::elidable_lifetime_names,
    clippy::format_push_string,
    clippy::needless_pass_by_value,
    clippy::too_many_lines,
    private_interfaces
)]

use super::{ClientManager, FakeBackend, InventoryFuture as BackendFuture, LiveBackend, lock};
use libremetaverse::types::compat::{CancellationToken, Subscription};
use libremetaverse::types::{AssetType, FolderType, PrimFlags, Quaternion, UUID, Vector2, Vector3};
use libremetaverse::{
    GridLayer, GridRegion, Parcel, PermissionMask, Primitive, PrimitiveObjectProperties,
    PrimitiveParticleSystemSourcePattern, PrimitiveTextureEntry, Tree,
};
use libremetaverse_structured_data::{OSD, OSDParser};
use std::collections::{BTreeSet, HashMap, HashSet};
use std::fs;
use std::path::{Component, Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::Duration;

const MAX_FILE_BYTES: u64 = 64 * 1024 * 1024;
const MAX_EXPORT_PRIMS: usize = 10_000;
const MAX_QUERY_RESULTS: usize = 65_535;
const MAX_MOVEMENT_SECONDS: u64 = 60;

#[derive(Clone, Copy)]
pub(super) enum Command {
    DownloadTerrain,
    EstateCovenant,
    UploadTerrain,
    AgentLocations,
    FindSim,
    GridLayer,
    GridMap,
    ParcelDetails,
    ParcelInfo,
    PrimOwners,
    SelectObjects,
    SyntaxId,
    Wind,
    Back,
    CrossRegion,
    Crouch,
    Fly,
    FlyTo,
    Follow,
    Forward,
    GoHome,
    Goto,
    GotoLandmark,
    Jump,
    Left,
    Location,
    MoveTo,
    Right,
    SetHome,
    Sit,
    SitOn,
    Stand,
    TurnTo,
    ChangePerms,
    DeRez,
    DownloadTexture,
    Export,
    ExportParticles,
    FindObjects,
    FindTexture,
    Import,
    PrimCount,
    PrimInfo,
    PrimRegex,
    Textures,
    Tree,
}

pub(super) fn commands() -> Vec<super::CommandEntry> {
    use super::{CommandCategory as Cat, CommandHandler};
    use Command as C;
    let world = |command| CommandHandler::World(command);
    vec![
        (
            "downloadterrain",
            "Download the current estate RAW terrain. Usage: downloadterrain [timeout-ms] [output] --confirm",
            Cat::Simulator,
            world(C::DownloadTerrain),
        ),
        (
            "getestatecovenant",
            "Retrieve estate covenant information. Usage: getestatecovenant [timeout-seconds]",
            Cat::Simulator,
            world(C::EstateCovenant),
        ),
        (
            "uploadterrain",
            "Upload a RAW terrain file. Usage: uploadterrain [file] --confirm",
            Cat::Simulator,
            world(C::UploadTerrain),
        ),
        (
            "agentlocations",
            "List agent map locations. Usage: agentlocations [region-handle]",
            Cat::Simulator,
            world(C::AgentLocations),
        ),
        (
            "findsim",
            "Find a simulator. Usage: findsim [simulator name]",
            Cat::Simulator,
            world(C::FindSim),
        ),
        (
            "gridlayer",
            "Download grid object-map layer chunks",
            Cat::Simulator,
            world(C::GridLayer),
        ),
        (
            "gridmap",
            "Download visible grid map information",
            Cat::Simulator,
            world(C::GridMap),
        ),
        (
            "parceldetails",
            "Display parcel details. Usage: parceldetails [parcel-id]",
            Cat::Parcel,
            world(C::ParcelDetails),
        ),
        (
            "parcelinfo",
            "Display all parcels in the current simulator",
            Cat::Parcel,
            world(C::ParcelInfo),
        ),
        (
            "primowners",
            "Display parcel prim owners. Usage: primowners [parcel-id]",
            Cat::Parcel,
            world(C::PrimOwners),
        ),
        (
            "selectobjects",
            "List parcel objects for an owner. Usage: selectobjects [parcel-id] [owner-uuid]",
            Cat::Parcel,
            world(C::SelectObjects),
        ),
        (
            "syntaxid",
            "Display the current native LSL syntax identifiers",
            Cat::Simulator,
            world(C::SyntaxId),
        ),
        (
            "wind",
            "Display local wind data",
            Cat::Simulator,
            world(C::Wind),
        ),
        (
            "back",
            "Move backward. Usage: back [seconds] --confirm",
            Cat::Movement,
            world(C::Back),
        ),
        (
            "crossregion",
            "Cross a region border. Usage: crossregion [direction] [walk|fly] --confirm",
            Cat::Movement,
            world(C::CrossRegion),
        ),
        (
            "crouch",
            "Start or stop crouching. Usage: crouch [start|stop] --confirm",
            Cat::Movement,
            world(C::Crouch),
        ),
        (
            "fly",
            "Start or stop flying. Usage: fly [start|stop] --confirm",
            Cat::Movement,
            world(C::Fly),
        ),
        (
            "flyto",
            "Fly toward a position. Usage: flyto x y z [seconds] --confirm",
            Cat::Movement,
            world(C::FlyTo),
        ),
        (
            "follow",
            "Follow another avatar. Usage: follow [First Last|off] --confirm",
            Cat::Movement,
            world(C::Follow),
        ),
        (
            "forward",
            "Move forward. Usage: forward [seconds] --confirm",
            Cat::Movement,
            world(C::Forward),
        ),
        (
            "gohome",
            "Teleport home. Usage: gohome --confirm",
            Cat::Movement,
            world(C::GoHome),
        ),
        (
            "goto",
            "Teleport to a location. Usage: goto sim/x/y/z --confirm",
            Cat::Movement,
            world(C::Goto),
        ),
        (
            "goto_landmark",
            "Teleport to a landmark. Usage: goto_landmark [uuid] --confirm",
            Cat::Movement,
            world(C::GotoLandmark),
        ),
        (
            "jump",
            "Jump or fly up. Usage: jump --confirm",
            Cat::Movement,
            world(C::Jump),
        ),
        (
            "left",
            "Move left. Usage: left [seconds] --confirm",
            Cat::Movement,
            world(C::Left),
        ),
        (
            "location",
            "Show the current simulator and position",
            Cat::Movement,
            world(C::Location),
        ),
        (
            "moveto",
            "Use simulator autopilot. Usage: moveto x y z --confirm",
            Cat::Movement,
            world(C::MoveTo),
        ),
        (
            "right",
            "Move right. Usage: right [seconds] --confirm",
            Cat::Movement,
            world(C::Right),
        ),
        (
            "sethome",
            "Set home to the current location. Usage: sethome --confirm",
            Cat::Movement,
            world(C::SetHome),
        ),
        (
            "sit",
            "Sit on the closest primitive. Usage: sit --confirm",
            Cat::Movement,
            world(C::Sit),
        ),
        (
            "siton",
            "Sit on a primitive. Usage: siton [uuid] --confirm",
            Cat::Movement,
            world(C::SitOn),
        ),
        (
            "stand",
            "Stand up. Usage: stand --confirm",
            Cat::Movement,
            world(C::Stand),
        ),
        (
            "turnto",
            "Turn toward a point. Usage: turnto x y z --confirm",
            Cat::Movement,
            world(C::TurnTo),
        ),
        (
            "changeperms",
            "Change linkset next-owner permissions. Usage: changeperms [uuid] [copy] [mod] [xfer] --confirm",
            Cat::Objects,
            world(C::ChangePerms),
        ),
        (
            "derez",
            "Take a primitive into Trash. Usage: derez [uuid] --confirm",
            Cat::Objects,
            world(C::DeRez),
        ),
        (
            "downloadtexture",
            "Download a texture. Usage: downloadtexture [uuid] [discard-level] [output]",
            Cat::Inventory,
            world(C::DownloadTexture),
        ),
        (
            "export",
            "Export a linkset and its textures. Usage: export [uuid] [output.xml]",
            Cat::Objects,
            world(C::Export),
        ),
        (
            "exportparticles",
            "Convert a particle system to LSL. Usage: exportparticles [uuid]",
            Cat::Objects,
            world(C::ExportParticles),
        ),
        (
            "findobjects",
            "Find objects by radius and name. Usage: findobjects [radius] [search]",
            Cat::Objects,
            world(C::FindObjects),
        ),
        (
            "findtexture",
            "Find a texture on a face. Usage: findtexture [face-index] [uuid]",
            Cat::Objects,
            world(C::FindTexture),
        ),
        (
            "import",
            "Import linksets from XML. Usage: import [input.xml] [usegroup] --confirm",
            Cat::Objects,
            world(C::Import),
        ),
        (
            "primcount",
            "Show tracked avatar and primitive counts",
            Cat::TestClient,
            world(C::PrimCount),
        ),
        (
            "priminfo",
            "Display primitive details. Usage: priminfo [uuid]",
            Cat::Objects,
            world(C::PrimInfo),
        ),
        (
            "primregex",
            "Find primitives by regular-expression-like text. Usage: primregex [predicate]",
            Cat::Objects,
            world(C::PrimRegex),
        ),
        (
            "textures",
            "Enable or disable automatic texture downloading. Usage: textures [on|off]",
            Cat::Objects,
            world(C::Textures),
        ),
        (
            "tree",
            "Rez a tree. Usage: tree [species] --confirm",
            Cat::Objects,
            world(C::Tree),
        ),
    ]
}

#[derive(Clone, Debug)]
struct AvatarView {
    id: UUID,
    name: String,
    position: Vector3,
}

#[derive(Clone, Debug)]
struct OwnerView {
    owner: UUID,
    count: i32,
}

#[derive(Clone, Debug)]
struct ParcelView {
    parcel: Parcel,
    owners: Vec<OwnerView>,
    selected: HashMap<UUID, Vec<u32>>,
}

#[derive(Clone, Debug)]
struct AgentLocation {
    count: i32,
    x: u32,
    y: u32,
}

#[derive(Clone, Debug)]
struct EstateView {
    name: String,
    owner: UUID,
    covenant: UUID,
    timestamp: u32,
    body: String,
    terrain: Vec<u8>,
}

#[derive(Clone, Debug, Default)]
struct Snapshot {
    region_name: String,
    region_handle: u64,
    position: Vector3,
    wind: Option<Vec<Vector2>>,
    primitives: Vec<Primitive>,
    avatars: Vec<AvatarView>,
    parcels: Vec<ParcelView>,
    regions: Vec<GridRegion>,
    layers: Vec<GridLayer>,
    locations: HashMap<u64, Vec<AgentLocation>>,
    estate: Option<EstateView>,
    assets: HashMap<UUID, Vec<u8>>,
    syntax: Vec<String>,
}

pub(super) struct State {
    policy: super::inventory::MutationPolicy,
    fake: Snapshot,
    textures_enabled: bool,
    requested_textures: HashSet<UUID>,
    follow_target: Option<UUID>,
}

impl State {
    pub(super) fn new(policy: super::inventory::MutationPolicy, textures_enabled: bool) -> Self {
        Self {
            policy,
            fake: Snapshot::default(),
            textures_enabled,
            requested_textures: HashSet::new(),
            follow_target: None,
        }
    }
}

#[derive(Clone, Copy, Debug)]
enum MoveDirection {
    Back,
    Forward,
    Left,
    Right,
}

#[derive(Clone, Debug)]
enum Movement {
    Pulse(MoveDirection, Duration),
    Crouch(bool),
    Fly(bool),
    FlyTo(Vector3, Duration),
    Follow(Option<(UUID, Vector3)>),
    GoHome,
    TeleportRegion(String, Vector3),
    TeleportLandmark(UUID),
    Jump,
    AutoPilot { local: Vector3, global: [f64; 3] },
    SetHome,
    Sit(UUID),
    Stand,
    Turn(Vector3),
    Cross(Vector3, bool),
}

#[derive(Clone, Debug)]
enum Mutation {
    Movement(Movement),
    ChangePermissions {
        root: UUID,
        permissions: PermissionMask,
    },
    DeRez(UUID),
    Import {
        primitives: Vec<Primitive>,
        use_group: bool,
    },
    SetTextures(bool),
    Tree(Tree),
    UploadTerrain {
        name: String,
        data: Vec<u8>,
    },
}

#[derive(Clone, Debug)]
enum Query {
    AgentLocations(u64),
    FindRegion(String),
    GridLayer,
    GridMap,
    Parcels,
    ParcelOwners(i32),
    ParcelObjects(i32, UUID),
    EstateCovenant(Duration),
    DownloadTerrain(Duration),
}

pub(super) trait Backend {
    fn world_state(&self) -> &Mutex<State>;
    fn world_agent_id(&self) -> UUID;
    fn world_snapshot<'a>(
        &'a self,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<Snapshot, String>>;
    fn world_query<'a>(
        &'a self,
        query: Query,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<Snapshot, String>>;
    fn world_mutate<'a>(
        &'a self,
        mutation: Mutation,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<String, String>>;
    fn world_texture<'a>(
        &'a self,
        id: UUID,
        discard: i32,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<Vec<u8>, String>>;
    fn world_resolve_avatar<'a>(
        &'a self,
        name: &'a str,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<Option<UUID>, String>>;
    fn apply_world_fixture(&self, _fields: &[&str]) -> Result<(), &'static str> {
        Err("world fixture requires fake-grid mode")
    }
}

pub(super) async fn execute<B: Backend + ?Sized>(
    backend: &B,
    command: Command,
    args: &[String],
    cancellation: CancellationToken,
) -> String {
    let result = match command {
        Command::DownloadTerrain => download_terrain(backend, args, cancellation).await,
        Command::EstateCovenant => estate_covenant(backend, args, cancellation).await,
        Command::UploadTerrain => upload_terrain(backend, args, cancellation).await,
        Command::AgentLocations => agent_locations(backend, args, cancellation).await,
        Command::FindSim => find_sim(backend, args, cancellation).await,
        Command::GridLayer => grid_layer(backend, args, cancellation).await,
        Command::GridMap => grid_map(backend, args, cancellation).await,
        Command::ParcelDetails => parcel_details(backend, args, cancellation).await,
        Command::ParcelInfo => parcel_info(backend, args, cancellation).await,
        Command::PrimOwners => prim_owners(backend, args, cancellation).await,
        Command::SelectObjects => select_objects(backend, args, cancellation).await,
        Command::SyntaxId => syntax_id(backend, args, cancellation).await,
        Command::Wind => wind(backend, args, cancellation).await,
        Command::Back => directional(backend, args, MoveDirection::Back, cancellation).await,
        Command::Forward => directional(backend, args, MoveDirection::Forward, cancellation).await,
        Command::Left => directional(backend, args, MoveDirection::Left, cancellation).await,
        Command::Right => directional(backend, args, MoveDirection::Right, cancellation).await,
        Command::CrossRegion => cross_region(backend, args, cancellation).await,
        Command::Crouch => toggle_movement(backend, args, true, cancellation).await,
        Command::Fly => toggle_movement(backend, args, false, cancellation).await,
        Command::FlyTo => fly_to(backend, args, cancellation).await,
        Command::Follow => follow(backend, args, cancellation).await,
        Command::GoHome => {
            simple_movement(
                backend,
                args,
                Movement::GoHome,
                "Teleport Home Succesful",
                cancellation,
            )
            .await
        }
        Command::Goto => goto(backend, args, cancellation).await,
        Command::GotoLandmark => goto_landmark(backend, args, cancellation).await,
        Command::Jump => {
            simple_movement(backend, args, Movement::Jump, "Jumped", cancellation).await
        }
        Command::Location => location(backend, args, cancellation).await,
        Command::MoveTo => move_to(backend, args, cancellation).await,
        Command::SetHome => {
            simple_movement(backend, args, Movement::SetHome, "Home Set", cancellation).await
        }
        Command::Sit => sit(backend, args, None, cancellation).await,
        Command::SitOn => sit(backend, args, Some(()), cancellation).await,
        Command::Stand => {
            simple_movement(backend, args, Movement::Stand, "Standing up.", cancellation).await
        }
        Command::TurnTo => turn_to(backend, args, cancellation).await,
        Command::ChangePerms => change_permissions(backend, args, cancellation).await,
        Command::DeRez => derez(backend, args, cancellation).await,
        Command::DownloadTexture => download_texture(backend, args, cancellation).await,
        Command::Export => export(backend, args, cancellation).await,
        Command::ExportParticles => export_particles(backend, args, cancellation).await,
        Command::FindObjects => find_objects(backend, args, cancellation).await,
        Command::FindTexture => find_texture(backend, args, cancellation).await,
        Command::Import => import(backend, args, cancellation).await,
        Command::PrimCount => prim_count(backend, args, cancellation).await,
        Command::PrimInfo => prim_info(backend, args, cancellation).await,
        Command::PrimRegex => prim_regex(backend, args, cancellation).await,
        Command::Textures => textures(backend, args, cancellation).await,
        Command::Tree => tree(backend, args, cancellation).await,
    };
    result.unwrap_or_else(|error| error)
}

fn authorize<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    spending: bool,
    estate: bool,
) -> Result<Vec<String>, String> {
    let policy = lock(backend.world_state()).policy;
    if !policy.mutations() {
        return Err("Live world mutation blocked; restart with --allow-live-mutations".into());
    }
    if spending && !policy.spending() {
        return Err("Upload blocked; restart with --allow-spending".into());
    }
    if estate && !policy.estate() {
        return Err("Estate action blocked; restart with --allow-estate-actions".into());
    }
    if !args.iter().any(|value| value == "--confirm") {
        return Err("Operation requires an explicit --confirm argument".into());
    }
    Ok(args
        .iter()
        .filter(|value| value.as_str() != "--confirm")
        .cloned()
        .collect())
}

fn parse_uuid(value: &str) -> Result<UUID, String> {
    UUID::new_with_string(value.into()).map_err(|_| format!("{value} is not a valid UUID"))
}

fn parse_vector(args: &[String], usage: &str) -> Result<Vector3, String> {
    if args.len() != 3 {
        return Err(usage.into());
    }
    let values = args
        .iter()
        .map(|value| value.parse::<f32>())
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| usage.to_owned())?;
    if !values.iter().all(|value| value.is_finite()) {
        return Err(usage.into());
    }
    Vector3::new_with_single_single_single(values[0], values[1], values[2])
        .map_err(|_| usage.into())
}

fn safe_path(value: &str) -> Result<PathBuf, String> {
    let path = PathBuf::from(value);
    if path
        .components()
        .any(|component| matches!(component, Component::ParentDir))
    {
        return Err("Parent path traversal is not allowed".into());
    }
    Ok(path)
}

fn read_bounded(path: &Path) -> Result<Vec<u8>, String> {
    let metadata = fs::metadata(path)
        .map_err(|error| format!("Could not inspect {}: {error}", path.display()))?;
    if metadata.len() > MAX_FILE_BYTES {
        return Err(format!("{} exceeds the 64 MiB limit", path.display()));
    }
    fs::read(path).map_err(|error| format!("Could not read {}: {error}", path.display()))
}

fn write_bounded(path: &Path, data: &[u8]) -> Result<(), String> {
    if u64::try_from(data.len()).unwrap_or(u64::MAX) > MAX_FILE_BYTES {
        return Err("Output exceeds the 64 MiB limit".into());
    }
    if let Some(parent) = path
        .parent()
        .filter(|parent| !parent.as_os_str().is_empty())
    {
        fs::create_dir_all(parent)
            .map_err(|error| format!("Could not create {}: {error}", parent.display()))?;
    }
    fs::write(path, data).map_err(|error| format!("Could not write {}: {error}", path.display()))
}

async fn directional<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    direction: MoveDirection,
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    if args.len() > 1 {
        return Err(format!(
            "Usage: {} [seconds] --confirm",
            direction_name(direction)
        ));
    }
    let seconds = args
        .first()
        .map_or(Ok(0), |value| value.parse::<u64>())
        .map_err(|_| format!("Usage: {} [seconds] --confirm", direction_name(direction)))?;
    if seconds > MAX_MOVEMENT_SECONDS {
        return Err("Movement duration must not exceed 60 seconds".into());
    }
    backend
        .world_mutate(
            Mutation::Movement(Movement::Pulse(direction, Duration::from_secs(seconds))),
            cancellation,
        )
        .await?;
    Ok(format!("Moved {}", direction_past(direction)))
}

const fn direction_name(value: MoveDirection) -> &'static str {
    match value {
        MoveDirection::Back => "back",
        MoveDirection::Forward => "forward",
        MoveDirection::Left => "left",
        MoveDirection::Right => "right",
    }
}
const fn direction_past(value: MoveDirection) -> &'static str {
    match value {
        MoveDirection::Back => "backward",
        MoveDirection::Forward => "forward",
        MoveDirection::Left => "left",
        MoveDirection::Right => "right",
    }
}

async fn toggle_movement<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    crouch: bool,
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    if args.len() > 1 {
        return Err(if crouch {
            "Usage: crouch [start/stop] --confirm"
        } else {
            "Usage: fly [start/stop] --confirm"
        }
        .into());
    }
    let enabled = !args
        .first()
        .is_some_and(|arg| arg.eq_ignore_ascii_case("stop"));
    let mutation = if crouch {
        Movement::Crouch(enabled)
    } else {
        Movement::Fly(enabled)
    };
    backend
        .world_mutate(Mutation::Movement(mutation), cancellation)
        .await?;
    Ok(format!(
        "{} {}",
        if enabled { "Started" } else { "Stopped" },
        if crouch { "crouching" } else { "flying" }
    ))
}

async fn simple_movement<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    movement: Movement,
    output: &str,
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    if !args.is_empty() {
        return Err("This command accepts only --confirm".into());
    }
    backend
        .world_mutate(Mutation::Movement(movement), cancellation)
        .await?;
    Ok(output.into())
}

async fn fly_to<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    if !(3..=4).contains(&args.len()) {
        return Err("Usage: flyto x y z [seconds] --confirm".into());
    }
    let target = parse_vector(&args[..3], "Usage: flyto x y z [seconds] --confirm")?;
    let seconds = args
        .get(3)
        .map_or(Ok(10), |value| value.parse::<u64>())
        .map_err(|_| "Usage: flyto x y z [seconds] --confirm")?;
    if seconds == 0 || seconds > MAX_MOVEMENT_SECONDS {
        return Err("FlyTo duration must be between 1 and 60 seconds".into());
    }
    backend
        .world_mutate(
            Mutation::Movement(Movement::FlyTo(target, Duration::from_secs(seconds))),
            cancellation,
        )
        .await?;
    Ok(format!("flying to {target:?} in {seconds} seconds"))
}

async fn cross_region<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    if args.is_empty() || args.len() > 2 {
        return Err("Usage: crossregion [direction] [walk/fly] --confirm".into());
    }
    let (direction, name) = match args[0].to_ascii_lowercase().as_str() {
        "n" | "north" => ((0.0, 1.0), "North"),
        "s" | "south" => ((0.0, -1.0), "South"),
        "e" | "east" => ((1.0, 0.0), "East"),
        "w" | "west" => ((-1.0, 0.0), "West"),
        "ne" | "northeast" => ((1.0, 1.0), "Northeast"),
        "nw" | "northwest" => ((-1.0, 1.0), "Northwest"),
        "se" | "southeast" => ((1.0, -1.0), "Southeast"),
        "sw" | "southwest" => ((-1.0, -1.0), "Southwest"),
        value => {
            return Err(format!(
                "Unknown direction: {value}\nValid directions: north, south, east, west, northeast, northwest, southeast, southwest"
            ));
        }
    };
    let fly = match args
        .get(1)
        .map_or("walk", String::as_str)
        .to_ascii_lowercase()
        .as_str()
    {
        "walk" => false,
        "fly" => true,
        value => return Err(format!("Unknown mode: {value}. Use 'walk' or 'fly'")),
    };
    let vector = Vector3::new_with_single_single_single(direction.0, direction.1, 0.0)
        .map_err(|_| "Invalid direction")?;
    let result = backend
        .world_mutate(
            Mutation::Movement(Movement::Cross(vector, fly)),
            cancellation,
        )
        .await?;
    Ok(format!(
        "{result} {name} by {}",
        if fly { "flying" } else { "walking" }
    ))
}

async fn follow<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    if args.len() == 1 && args[0].eq_ignore_ascii_case("off") {
        backend
            .world_mutate(Mutation::Movement(Movement::Follow(None)), cancellation)
            .await?;
        return Ok("Following is off".into());
    }
    if args.len() != 2 {
        return Err("Usage: follow [FirstName LastName]/off --confirm".into());
    }
    let name = args.join(" ");
    let id = backend
        .world_resolve_avatar(&name, cancellation.clone())
        .await?
        .ok_or_else(|| format!("Unable to find {name}"))?;
    let snapshot = backend.world_snapshot(cancellation.clone()).await?;
    let position = snapshot
        .avatars
        .iter()
        .find(|avatar| avatar.id == id)
        .map(|avatar| avatar.position)
        .ok_or_else(|| format!("Unable to locate {name} in the current simulator"))?;
    backend
        .world_mutate(
            Mutation::Movement(Movement::Follow(Some((id, position)))),
            cancellation,
        )
        .await?;
    Ok(format!("Following {name}"))
}

async fn goto<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    let destination = args.join(" ");
    let parts: Vec<_> = destination.split('/').collect();
    if parts.len() != 4 {
        return Err("Usage: goto sim/x/y/z --confirm".into());
    }
    let position = parse_vector(
        &parts[1..]
            .iter()
            .map(|part| (*part).to_owned())
            .collect::<Vec<_>>(),
        "Usage: goto sim/x/y/z --confirm",
    )?;
    backend
        .world_mutate(
            Mutation::Movement(Movement::TeleportRegion(parts[0].into(), position)),
            cancellation,
        )
        .await?;
    Ok(format!("Teleported to {}", parts[0]))
}

async fn goto_landmark<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    if args.len() != 1 {
        return Err("Usage: goto_landmark [UUID] --confirm".into());
    }
    let id = parse_uuid(&args[0])?;
    backend
        .world_mutate(
            Mutation::Movement(Movement::TeleportLandmark(id)),
            cancellation,
        )
        .await?;
    Ok("Teleport Successful".into())
}

async fn location<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if !args.is_empty() {
        return Err("Usage: location".into());
    }
    let snapshot = backend.world_snapshot(cancellation).await?;
    Ok(format!(
        "CurrentSim: '{}' Position: {:?}",
        snapshot.region_name, snapshot.position
    ))
}

async fn move_to<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    let local = parse_vector(&args, "Usage: moveto x y z --confirm")?;
    let snapshot = backend.world_snapshot(cancellation.clone()).await?;
    let (x, y) = region_origin(snapshot.region_handle)?;
    let global = [
        f64::from(local.x) + f64::from(x),
        f64::from(local.y) + f64::from(y),
        f64::from(local.z),
    ];
    backend
        .world_mutate(
            Mutation::Movement(Movement::AutoPilot { local, global }),
            cancellation,
        )
        .await?;
    Ok(format!(
        "Attempting to move to <{},{},{}>",
        global[0], global[1], global[2]
    ))
}

async fn sit<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    explicit: Option<()>,
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    let snapshot = backend.world_snapshot(cancellation.clone()).await?;
    let prim = if explicit.is_some() {
        if args.len() != 1 {
            return Err("Usage: siton UUID --confirm".into());
        }
        let id = parse_uuid(&args[0])?;
        snapshot
            .primitives
            .iter()
            .find(|prim| prim.id == id)
            .ok_or_else(|| format!("Couldn't find a prim to sit on with UUID {id}"))?
    } else {
        if !args.is_empty() {
            return Err("Usage: sit --confirm".into());
        }
        snapshot
            .primitives
            .iter()
            .min_by(|left, right| {
                distance(snapshot.position, left.position)
                    .total_cmp(&distance(snapshot.position, right.position))
            })
            .ok_or("Couldn't find a nearby prim to sit on")?
    };
    let distance = distance(snapshot.position, prim.position);
    backend
        .world_mutate(Mutation::Movement(Movement::Sit(prim.id)), cancellation)
        .await?;
    Ok(if explicit.is_some() {
        format!("Requested to sit on prim {} ({})", prim.id, prim.local_id)
    } else {
        format!(
            "Sat on {} ({}). Distance: {distance}",
            prim.id, prim.local_id
        )
    })
}

fn distance(left: Vector3, right: Vector3) -> f32 {
    let x = left.x - right.x;
    let y = left.y - right.y;
    let z = left.z - right.z;
    (x * x + y * y + z * z).sqrt()
}

fn region_origin(handle: u64) -> Result<(u32, u32), String> {
    let x = u32::try_from(handle >> 32).map_err(|_| "Invalid region handle")?;
    let y = u32::try_from(handle & u64::from(u32::MAX)).map_err(|_| "Invalid region handle")?;
    Ok((x, y))
}

async fn turn_to<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    let target = parse_vector(&args, "Usage: turnto x y z --confirm")?;
    backend
        .world_mutate(Mutation::Movement(Movement::Turn(target)), cancellation)
        .await?;
    Ok(format!("Turned to {target:?}"))
}

async fn wind<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if !args.is_empty() {
        return Err("Usage: wind".into());
    }
    let snapshot = backend.world_snapshot(cancellation).await?;
    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
    let (x, y) = (
        snapshot.position.x.clamp(0.0, 255.0) as usize / 16,
        snapshot.position.y.clamp(0.0, 255.0) as usize / 16,
    );
    let wind = snapshot
        .wind
        .as_ref()
        .and_then(|values| values.get(y * 16 + x))
        .ok_or("Wind data is not available")?;
    Ok(format!("Local wind speed is {wind:?}"))
}

async fn agent_locations<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.len() > 1 {
        return Err("Usage: agentlocations [regionhandle]".into());
    }
    let initial = backend.world_snapshot(cancellation.clone()).await?;
    let handle = args
        .first()
        .map_or(Ok(initial.region_handle), |arg| arg.parse::<u64>())
        .map_err(|_| "Usage: agentlocations [regionhandle]")?;
    let snapshot = backend
        .world_query(Query::AgentLocations(handle), cancellation)
        .await?;
    let locations = snapshot
        .locations
        .get(&handle)
        .filter(|values| !values.is_empty())
        .ok_or("Failed to fetch agent locations")?;
    let mut output = String::from("Agent locations:\n");
    for location in locations.iter().take(MAX_QUERY_RESULTS) {
        output.push_str(&format!(
            "{} avatar(s) at {},{}\n",
            location.count, location.x, location.y
        ));
    }
    Ok(output)
}

async fn find_sim<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.is_empty() {
        return Err("Usage: findsim [Simulator Name]".into());
    }
    let name = args.join(" ").trim().to_ascii_lowercase();
    let snapshot = backend
        .world_query(Query::FindRegion(name.clone()), cancellation)
        .await?;
    snapshot
        .regions
        .iter()
        .find(|region| region.name.eq_ignore_ascii_case(&name))
        .map(|region| {
            format!(
                "{}: handle={} ({},{})",
                region.name, region.region_handle, region.x, region.y
            )
        })
        .ok_or_else(|| format!("Lookup of {name} failed"))
}

async fn grid_layer<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if !args.is_empty() {
        return Err("Usage: gridlayer".into());
    }
    let snapshot = backend.world_query(Query::GridLayer, cancellation).await?;
    let mut output = String::new();
    for layer in snapshot.layers.iter().take(MAX_QUERY_RESULTS) {
        output.push_str(&format!(
            "Layer({}) Bottom: {} Left: {} Top: {} Right: {}\n",
            layer.image_id, layer.bottom, layer.left, layer.top, layer.right
        ));
    }
    output.push_str(&format!("Received {} layer chunks", snapshot.layers.len()));
    Ok(output)
}

async fn grid_map<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if !args.is_empty() {
        return Err("Usage: gridmap".into());
    }
    let snapshot = backend.world_query(Query::GridMap, cancellation).await?;
    let mut regions = snapshot.regions;
    regions.sort_by(|a, b| a.name.cmp(&b.name));
    let mut output = String::new();
    for region in regions.iter().take(MAX_QUERY_RESULTS) {
        output.push_str(&format!(
            "{}: handle={} ({},{}) agents={}\n",
            region.name, region.region_handle, region.x, region.y, region.agents
        ));
    }
    output.push_str(&format!("Received {} grid regions", regions.len()));
    Ok(output)
}

async fn parcel_info<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if !args.is_empty() {
        return Err("Usage: parcelinfo".into());
    }
    let snapshot = backend.world_query(Query::Parcels, cancellation).await?;
    let mut output = format!(
        "Downloaded {} Parcels in {}\n",
        snapshot.parcels.len(),
        snapshot.region_name
    );
    for view in snapshot.parcels.iter().take(MAX_QUERY_RESULTS) {
        let p = &view.parcel;
        output.push_str(&format!("Parcel[{}]: Name: \"{}\", Description: \"{}\" ACLBlacklist Count: {}, ACLWhiteList Count: {} Traffic: {}\n", p.local_id, p.name, p.desc, p.access_black_list.len(), p.access_white_list.len(), p.dwell));
    }
    Ok(output)
}

async fn parcel_details<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.len() != 1 {
        return Err("Usage: parceldetails parcelID (use parcelinfo to get ID)".into());
    }
    let id = args[0]
        .parse::<i32>()
        .map_err(|_| "Usage: parceldetails parcelID (use parcelinfo to get ID)")?;
    let snapshot = backend.world_query(Query::Parcels, cancellation).await?;
    let p=&snapshot.parcels.iter().find(|view| view.parcel.local_id==id).ok_or_else(||format!("Unable to find Parcel {} in Parcels Dictionary, Did you run parcelinfo to populate the dictionary first?",args[0]))?.parcel;
    Ok(format!(
        "LocalID = {}\nName = {}\nDescription = {}\nOwnerID = {}\nGroupID = {}\nArea = {}\nDwell = {}\nTotalPrims = {}\nMaxPrims = {}\nSalePrice = {}\nLanding = {:?}\nFlags = {:?}",
        p.local_id,
        p.name,
        p.desc,
        p.owner_id,
        p.group_id,
        p.area,
        p.dwell,
        p.total_prims,
        p.max_prims,
        p.sale_price,
        p.landing,
        p.flags
    ))
}

async fn prim_owners<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.len() != 1 {
        return Err("Usage: primowners parcelID (use parcelinfo to get ID)".into());
    }
    let id = args[0]
        .parse::<i32>()
        .map_err(|_| "Usage: primowners parcelID (use parcelinfo to get ID)")?;
    let snapshot = backend
        .world_query(Query::ParcelOwners(id), cancellation)
        .await?;
    let view=snapshot.parcels.iter().find(|view|view.parcel.local_id==id).ok_or_else(||format!("Unable to find Parcel {} in Parcels Dictionary, Did you run parcelinfo to populate the dictionary first?",args[0]))?;
    if view.owners.is_empty() {
        return Ok("No primitive owners returned".into());
    }
    let mut output = String::new();
    for owner in view.owners.iter().take(MAX_QUERY_RESULTS) {
        output.push_str(&format!("Owner: {} Count: {}\n", owner.owner, owner.count));
    }
    Ok(output)
}

async fn select_objects<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.len() != 2 {
        return Err("Usage: selectobjects parcelID OwnerUUID (use parcelinfo to get ID, use primowners to get ownerUUID)".into());
    }
    let id=args[0].parse::<i32>().map_err(|_|"Usage: selectobjects parcelID OwnerUUID (use parcelinfo to get ID, use primowners to get ownerUUID)")?;
    let owner = parse_uuid(&args[1])?;
    let snapshot = backend
        .world_query(Query::ParcelObjects(id, owner), cancellation)
        .await?;
    let ids = snapshot
        .parcels
        .iter()
        .find(|view| view.parcel.local_id == id)
        .and_then(|view| view.selected.get(&owner))
        .cloned()
        .unwrap_or_default();
    let mut output = ids
        .iter()
        .take(MAX_QUERY_RESULTS)
        .map(u32::to_string)
        .collect::<Vec<_>>()
        .join(" ");
    if !output.is_empty() {
        output.push(' ');
    }
    output.push_str(&format!("Found a total of {} Objects", ids.len()));
    Ok(output)
}

async fn syntax_id<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if !args.is_empty() {
        return Err("Usage: syntaxid".into());
    }
    let mut syntax = backend.world_snapshot(cancellation).await?.syntax;
    syntax.sort();
    syntax.dedup();
    Ok(format!("LSL Tokens:\n{}", syntax.join("\n")))
}

async fn estate_covenant<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.len() > 1 {
        return Err("Usage: getestatecovenant [timeout]".into());
    }
    let seconds = args
        .first()
        .map_or(Ok(20), |v| v.parse::<u64>())
        .map_err(|_| "Usage: getestatecovenant [timeout]")?;
    if !(1..=120).contains(&seconds) {
        return Err("Covenant timeout must be between 1 and 120 seconds".into());
    }
    let snapshot = backend
        .world_query(
            Query::EstateCovenant(Duration::from_secs(seconds)),
            cancellation,
        )
        .await?;
    let estate = snapshot
        .estate
        .ok_or("Timeout waiting for covenant info.")?;
    let mut output = format!(
        "Estate name: {}\nEstate owner: {}\n",
        estate.name, estate.owner
    );
    if estate.covenant != UUID::zero() {
        output.push_str(&format!(
            "Estate Covenant ID: {}\nEstate Covenant Update Time: {}\nCovenant:\n{}",
            estate.covenant, estate.timestamp, estate.body
        ));
    }
    Ok(output)
}

async fn download_terrain<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, true)?;
    if args.len() > 2 {
        return Err("Usage: downloadterrain [timeout-ms] [output] --confirm".into());
    }
    let timeout = args
        .first()
        .map_or(Ok(120_000), |v| v.parse::<u64>())
        .map_err(|_| "Usage: downloadterrain [timeout-ms] [output] --confirm")?;
    if !(1..=300_000).contains(&timeout) {
        return Err("Terrain timeout must be between 1 and 300000 ms".into());
    }
    let snapshot = backend
        .world_query(
            Query::DownloadTerrain(Duration::from_millis(timeout)),
            cancellation,
        )
        .await?;
    let estate = snapshot
        .estate
        .ok_or("Timeout while waiting for terrain data")?;
    let path = args.get(1).map_or_else(
        || PathBuf::from(format!("{}.raw", snapshot.region_name)),
        PathBuf::from,
    );
    let path = safe_path(path.to_string_lossy().as_ref())?;
    write_bounded(&path, &estate.terrain)?;
    Ok(format!(
        "Terrain file {} ({} bytes) downloaded successfully, written to {}",
        snapshot.region_name,
        estate.terrain.len(),
        path.display()
    ))
}

async fn upload_terrain<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, true, true)?;
    if args.len() != 1 {
        return Err("Usage: uploadterrain filename --confirm".into());
    }
    let path = safe_path(&args[0])?;
    let data = read_bounded(&path)?;
    backend
        .world_mutate(
            Mutation::UploadTerrain {
                name: path
                    .file_name()
                    .and_then(|v| v.to_str())
                    .unwrap_or("terrain.raw")
                    .into(),
                data,
            },
            cancellation,
        )
        .await?;
    Ok("Terrain raw file uploaded and applied".into())
}

async fn change_permissions<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    if args.is_empty() || args.len() > 4 {
        return Err("Usage: changeperms prim-uuid [copy] [mod] [xfer] --confirm".into());
    }
    let root = parse_uuid(&args[0])?;
    let mut permissions = PermissionMask::NONE;
    for value in &args[1..] {
        match value.to_ascii_lowercase().as_str() {
            "copy" => permissions.0 |= PermissionMask::COPY.0,
            "mod" => permissions.0 |= PermissionMask::MODIFY.0,
            "xfer" => permissions.0 |= PermissionMask::TRANSFER.0,
            _ => return Err("Usage: changeperms prim-uuid [copy] [mod] [xfer] --confirm".into()),
        }
    }
    backend
        .world_mutate(
            Mutation::ChangePermissions { root, permissions },
            cancellation,
        )
        .await
}

async fn derez<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    if args.len() != 1 {
        return Err("Usage: derez [prim-uuid] --confirm".into());
    }
    let id = parse_uuid(&args[0])?;
    let snapshot = backend.world_snapshot(cancellation.clone()).await?;
    let prim = snapshot
        .primitives
        .iter()
        .find(|prim| prim.id == id)
        .ok_or_else(|| format!("Could not find object {id}"))?;
    let name = prim
        .properties
        .as_ref()
        .map_or("Object", |properties| properties.name.as_str());
    backend
        .world_mutate(Mutation::DeRez(id), cancellation)
        .await?;
    Ok(format!("Removing {name} ({id})"))
}

async fn download_texture<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.is_empty() || args.len() > 3 {
        return Err("Usage: downloadtexture [texture-uuid] [discardlevel] [output]".into());
    }
    let id = parse_uuid(&args[0])?;
    let discard = args
        .get(1)
        .map_or(Ok(0), |value| value.parse::<i32>())
        .map_err(|_| "Usage: downloadtexture [texture-uuid] [discardlevel] [output]")?;
    if !(0..=5).contains(&discard) {
        return Err("Discard level must be between 0 and 5".into());
    }
    let data = backend.world_texture(id, discard, cancellation).await?;
    let path = safe_path(
        args.get(2)
            .map_or_else(|| format!("{id}.jp2"), Clone::clone)
            .as_str(),
    )?;
    write_bounded(&path, &data)?;
    let dimensions = libremetaverse_imaging::J2kCodec::decode_bytes(
        &data,
        libremetaverse_imaging::J2kDecodeOptions::default(),
    )
    .map_or_else(
        |_| "undecoded".into(),
        |image| format!("{}x{}", image.width, image.height),
    );
    Ok(format!("Saved {} ({dimensions})", path.display()))
}

fn primitive_name(prim: &Primitive) -> &str {
    prim.properties
        .as_ref()
        .map_or("(unknown)", |properties| properties.name.as_str())
}
fn primitive_description(prim: &Primitive) -> &str {
    prim.properties
        .as_ref()
        .map_or("(unknown)", |properties| properties.description.as_str())
}

fn texture_ids(prim: &Primitive) -> BTreeSet<UUID> {
    let mut ids = BTreeSet::new();
    if let Some(textures) = &prim.textures {
        if let Some(face) = &textures.default_texture {
            ids.insert(face.texture_id());
        }
        for face in textures.face_textures.iter().flatten() {
            ids.insert(face.texture_id());
        }
    }
    if let Some(sculpt) = &prim.sculpt
        && sculpt.sculpt_texture != UUID::zero()
    {
        ids.insert(sculpt.sculpt_texture);
    }
    ids.remove(&PrimitiveTextureEntry::white_texture());
    ids
}

fn linkset(primitives: &[Primitive], id: UUID) -> Result<Vec<&Primitive>, String> {
    let selected = primitives
        .iter()
        .find(|prim| prim.id == id)
        .ok_or_else(|| {
            format!(
                "Couldn't find UUID {id} in the objects currently indexed in the current simulator"
            )
        })?;
    let root = if selected.parent_id == 0 {
        selected.local_id
    } else {
        selected.parent_id
    };
    let mut values: Vec<_> = primitives
        .iter()
        .filter(|prim| prim.local_id == root || prim.parent_id == root)
        .take(MAX_EXPORT_PRIMS + 1)
        .collect();
    if values.len() > MAX_EXPORT_PRIMS {
        return Err("Linkset exceeds the 10,000 primitive export limit".into());
    }
    values.sort_by_key(|prim| prim.local_id);
    Ok(values)
}

async fn export<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.len() != 2 {
        return Err("Usage: export uuid outputfile.xml".into());
    }
    let id = parse_uuid(&args[0])?;
    let output_path = safe_path(&args[1])?;
    let snapshot = backend.world_snapshot(cancellation.clone()).await?;
    let prims = linkset(&snapshot.primitives, id)?;
    let owner = prims
        .first()
        .and_then(|prim| prim.properties.as_ref())
        .map_or_else(|| prims[0].owner_id, |properties| properties.owner_id);
    if owner != backend.world_agent_id() {
        return Err(format!(
            "That object is owned by {owner}, we don't have permission to export it"
        ));
    }
    let values = prims
        .iter()
        .map(|prim| prim.get_osd())
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| "Could not serialize primitive linkset")?;
    let xml = OSDParser::serialize_llsd_xml_string(OSD::Array(values))
        .map_err(|_| "Could not serialize primitive linkset")?;
    write_bounded(&output_path, xml.as_bytes())?;
    let texture_directory = output_path.parent().unwrap_or_else(|| Path::new("."));
    let mut textures = BTreeSet::new();
    for prim in &prims {
        textures.extend(texture_ids(prim));
    }
    let mut downloaded = 0usize;
    for texture in textures {
        let data = backend
            .world_texture(texture, 0, cancellation.clone())
            .await?;
        let path = texture_directory.join(format!("{texture}.jp2"));
        write_bounded(&path, &data)?;
        let image = libremetaverse_imaging::J2kCodec::decode_bytes(
            &data,
            libremetaverse_imaging::J2kDecodeOptions::default(),
        )
        .map_err(|_| format!("Failed to decode exported texture {texture}"))?;
        let tga_path = texture_directory.join(format!("{texture}.tga"));
        write_bounded(&tga_path, &super::inventory::encode_tga(&image)?)?;
        downloaded += 1;
    }
    Ok(format!(
        "Exported {} prims to {}; downloaded {downloaded} textures",
        prims.len(),
        output_path.display()
    ))
}

fn particle_lsl(prim: &Primitive) -> Result<String, String> {
    let particle = &prim.particle_sys;
    if particle.crc == 0 {
        return Err(format!(
            "Prim {} does not have a particle system",
            prim.local_id
        ));
    }
    let pattern = match particle.pattern {
        PrimitiveParticleSystemSourcePattern::DROP => "PSYS_SRC_PATTERN_DROP",
        PrimitiveParticleSystemSourcePattern::EXPLODE => "PSYS_SRC_PATTERN_EXPLODE",
        PrimitiveParticleSystemSourcePattern::ANGLE => "PSYS_SRC_PATTERN_ANGLE",
        PrimitiveParticleSystemSourcePattern::ANGLE_CONE => "PSYS_SRC_PATTERN_ANGLE_CONE",
        PrimitiveParticleSystemSourcePattern::ANGLE_CONE_EMPTY => {
            "PSYS_SRC_PATTERN_ANGLE_CONE_EMPTY"
        }
        _ => "0",
    };
    let acceleration = lsl_vector(particle.part_acceleration);
    let omega = lsl_vector(particle.angular_velocity);
    Ok(format!(
        "default\n{{\n    state_entry()\n    {{\n        llParticleSystem([\n            PSYS_PART_FLAGS, {},\n            PSYS_SRC_PATTERN, {pattern},\n            PSYS_PART_START_ALPHA, {:.5},\n            PSYS_PART_END_ALPHA, {:.5},\n            PSYS_PART_START_SCALE, <{:.5}, {:.5}, 0>,\n            PSYS_PART_END_SCALE, <{:.5}, {:.5}, 0>,\n            PSYS_PART_MAX_AGE, {:.5},\n            PSYS_SRC_MAX_AGE, {:.5},\n            PSYS_SRC_ACCEL, {},\n            PSYS_SRC_BURST_PART_COUNT, {},\n            PSYS_SRC_BURST_RADIUS, {:.5},\n            PSYS_SRC_BURST_RATE, {:.5},\n            PSYS_SRC_BURST_SPEED_MIN, {:.5},\n            PSYS_SRC_BURST_SPEED_MAX, {:.5},\n            PSYS_SRC_INNERANGLE, {:.5},\n            PSYS_SRC_OUTERANGLE, {:.5},\n            PSYS_SRC_OMEGA, {},\n            PSYS_SRC_TEXTURE, (key)\"{}\",\n            PSYS_SRC_TARGET_KEY, (key)\"{}\"\n        ]);\n    }}\n}}\n",
        particle.part_data_flags.0,
        particle.part_start_color.a,
        particle.part_end_color.a,
        particle.part_start_scale_x,
        particle.part_start_scale_y,
        particle.part_end_scale_x,
        particle.part_end_scale_y,
        particle.part_max_age,
        particle.max_age,
        acceleration,
        particle.burst_part_count,
        particle.burst_radius,
        particle.burst_rate,
        particle.burst_speed_min,
        particle.burst_speed_max,
        particle.inner_angle,
        particle.outer_angle,
        omega,
        particle.texture,
        particle.target
    ))
}

fn lsl_vector(value: Vector3) -> String {
    format!("<{:.5}, {:.5}, {:.5}>", value.x, value.y, value.z)
}

async fn export_particles<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.len() != 1 {
        return Err("Usage: exportparticles [prim-uuid]".into());
    }
    let id = parse_uuid(&args[0])?;
    let snapshot = backend.world_snapshot(cancellation).await?;
    let prim = snapshot
        .primitives
        .iter()
        .find(|prim| prim.id == id)
        .ok_or_else(|| format!("Could not find {id} object"))?;
    particle_lsl(prim)
}

async fn find_objects<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.is_empty() || args.len() > 2 {
        return Err("Usage: findobjects [radius] <search-string>".into());
    }
    let radius = args[0]
        .parse::<f32>()
        .map_err(|_| "Usage: findobjects [radius] <search-string>")?;
    if !radius.is_finite() || !(0.0..=4096.0).contains(&radius) {
        return Err("Radius must be between 0 and 4096 metres".into());
    }
    let search = args.get(1).map_or("", String::as_str);
    let snapshot = backend.world_snapshot(cancellation).await?;
    let mut output = String::new();
    let mut count = 0;
    for prim in snapshot
        .primitives
        .iter()
        .filter(|prim| distance(snapshot.position, prim.position) < radius)
        .take(MAX_QUERY_RESULTS)
    {
        let name = primitive_name(prim);
        if search.is_empty() || name.contains(search) {
            output.push_str(&format!("Object '{name}': {}\n", prim.id));
            count += 1;
        }
    }
    output.push_str(&format!("Done searching; found {count} objects"));
    Ok(output)
}

async fn find_texture<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.len() != 2 {
        return Err("Usage: findtexture [face-index] [texture-uuid]".into());
    }
    let face = args[0]
        .parse::<usize>()
        .map_err(|_| "Usage: findtexture [face-index] [texture-uuid]")?;
    if face >= PrimitiveTextureEntry::MAX_FACES as usize {
        return Err("Face index is outside the supported range".into());
    }
    let id = parse_uuid(&args[1])?;
    let snapshot = backend.world_snapshot(cancellation).await?;
    let mut output = String::new();
    let mut count = 0;
    for prim in &snapshot.primitives {
        if let Some(texture) = prim
            .textures
            .as_ref()
            .and_then(|textures| textures.face_textures.get(face))
            .and_then(Option::as_ref)
            && texture.texture_id() == id
        {
            output.push_str(&format!(
                "Primitive {} ({}) has face index {face} set to {id}\n",
                prim.id, prim.local_id
            ));
            count += 1;
        }
    }
    output.push_str(&format!("Done searching; found {count} faces"));
    Ok(output)
}

async fn import<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, true, false)?;
    if args.is_empty() || args.len() > 2 {
        return Err("Usage: import inputfile.xml [usegroup] --confirm".into());
    }
    if args.len() == 2 && !args[1].eq_ignore_ascii_case("usegroup") {
        return Err("Usage: import inputfile.xml [usegroup] --confirm".into());
    }
    let path = safe_path(&args[0])?;
    let data = read_bounded(&path)?;
    let osd = OSDParser::deserialize_llsd_xml_with_bytes(data)
        .map_err(|_| format!("Failed to deserialize {}", path.display()))?;
    let OSD::Array(values) = osd else {
        return Err("Import document must contain an LLSD array".into());
    };
    if values.is_empty() || values.len() > MAX_EXPORT_PRIMS {
        return Err("Import must contain between 1 and 10,000 primitives".into());
    }
    let primitives = values
        .into_iter()
        .map(Primitive::from_osd)
        .collect::<Result<Vec<_>, _>>()
        .map_err(|_| format!("Failed to deserialize {}", path.display()))?;
    backend
        .world_mutate(
            Mutation::Import {
                primitives,
                use_group: args.len() == 2,
            },
            cancellation,
        )
        .await?;
    Ok("Import complete.".into())
}

async fn prim_count<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if !args.is_empty() {
        return Err("Usage: primcount".into());
    }
    let snapshot = backend.world_snapshot(cancellation).await?;
    Ok(format!(
        "{} (Avatars: {} Primitives: {})\nTracking a total of {} objects",
        snapshot.region_name,
        snapshot.avatars.len(),
        snapshot.primitives.len(),
        snapshot.avatars.len() + snapshot.primitives.len()
    ))
}

async fn prim_info<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.len() != 1 {
        return Err("Usage: priminfo [prim-uuid]".into());
    }
    let id = parse_uuid(&args[0])?;
    let snapshot = backend.world_snapshot(cancellation).await?;
    let prim = snapshot
        .primitives
        .iter()
        .find(|prim| prim.id == id)
        .ok_or_else(|| format!("Could not find object {id}"))?;
    let mut output = format!(
        "ID: {}\nLocalID: {}\nParentID: {}\nName: {}\nDescription: {}\nPosition: {:?}\nScale: {:?}\nRotation: {:?}\nFlags: {:?}\nText: {}\nParticleCRC: {}\n",
        prim.id,
        prim.local_id,
        prim.parent_id,
        primitive_name(prim),
        primitive_description(prim),
        prim.position,
        prim.scale,
        prim.rotation,
        prim.flags,
        prim.text,
        prim.particle_sys.crc
    );
    for texture in texture_ids(prim) {
        output.push_str(&format!("Texture: {texture}\n"));
    }
    if let Some(properties) = &prim.properties {
        output.push_str(&format!("OwnerID: {}\nCreatorID: {}\nCategory: {:?}\nFolderID: {}\nFromTaskID: {}\nInventorySerial: {}\nItemID: {}\n",properties.owner_id,properties.creator_id,properties.category,properties.folder_id,properties.from_task_id,properties.inventory_serial,properties.item_id));
    }
    output.push_str("Done.");
    Ok(output)
}

async fn prim_regex<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.is_empty() {
        return Err("Usage: primregex [text predicate]".into());
    }
    let predicate = args.join(" ");
    if predicate.len() > 1024 {
        return Err("Predicate exceeds 1,024 bytes".into());
    }
    let regex = regex::RegexBuilder::new(&predicate)
        .case_insensitive(true)
        .size_limit(1024 * 1024)
        .build()
        .map_err(|error| format!("Error searching: {error}"))?;
    let snapshot = backend.world_snapshot(cancellation).await?;
    let mut output = format!(
        "Searching prim for [{predicate}] ({} prims loaded in simulator)\n",
        snapshot.primitives.len()
    );
    let mut count = 0;
    for prim in &snapshot.primitives {
        if regex.is_match(&prim.text)
            || regex.is_match(primitive_name(prim))
            || regex.is_match(primitive_description(prim))
        {
            output.push_str(&format!(
                "NAME={}\nID = {}\nFLAGS = {:?}\nTEXT = '{}'\nDESC='{}'\n",
                primitive_name(prim),
                prim.id,
                prim.flags,
                prim.text,
                primitive_description(prim)
            ));
            count += 1;
        }
    }
    output.push_str(&format!("Done searching; found {count} objects"));
    Ok(output)
}

async fn textures<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    if args.len() != 1 {
        return Err("Usage: textures [on/off]".into());
    }
    let enabled = match args[0].to_ascii_lowercase().as_str() {
        "on" => true,
        "off" => false,
        _ => return Err("Usage: textures [on/off]".into()),
    };
    backend
        .world_mutate(Mutation::SetTextures(enabled), cancellation)
        .await?;
    Ok(format!(
        "Texture downloading is {}",
        if enabled { "on" } else { "off" }
    ))
}

fn parse_tree(value: &str) -> Option<Tree> {
    match value.to_ascii_lowercase().as_str() {
        "beachgrass1" => Some(Tree::BeachGrass1),
        "cypress1" => Some(Tree::Cypress1),
        "cypress2" => Some(Tree::Cypress2),
        "dogwood" => Some(Tree::Dogwood),
        "eelgrass" => Some(Tree::Eelgrass),
        "eucalyptus" => Some(Tree::Eucalyptus),
        "fern" => Some(Tree::Fern),
        "kelp1" => Some(Tree::Kelp1),
        "kelp2" => Some(Tree::Kelp2),
        "oak" => Some(Tree::Oak),
        "palm1" => Some(Tree::Palm1),
        "palm2" => Some(Tree::Palm2),
        "pine1" => Some(Tree::Pine1),
        "pine2" => Some(Tree::Pine2),
        "plumeria" => Some(Tree::Plumeria),
        "seasword" => Some(Tree::SeaSword),
        "tropicalbush1" => Some(Tree::TropicalBush1),
        "tropicalbush2" => Some(Tree::TropicalBush2),
        "winteraspen" => Some(Tree::WinterAspen),
        "winterpine1" => Some(Tree::WinterPine1),
        "winterpine2" => Some(Tree::WinterPine2),
        _ => None,
    }
}

async fn tree<B: Backend + ?Sized>(
    backend: &B,
    args: &[String],
    cancellation: CancellationToken,
) -> Result<String, String> {
    let args = authorize(backend, args, false, false)?;
    if args.len() != 1 {
        return Err("Usage: tree [BeachGrass1,Cypress1,Cypress2,Dogwood,Eelgrass,Eucalyptus,Fern,Kelp1,Kelp2,Oak,Palm1,Palm2,Pine1,Pine2,Plumeria,SeaSword,TropicalBush1,TropicalBush2,WinterAspen,WinterPine1,WinterPine2] --confirm".into());
    }
    let species = parse_tree(&args[0]).ok_or("Type !tree for usage")?;
    backend
        .world_mutate(Mutation::Tree(species), cancellation)
        .await?;
    Ok(format!("Attempted to rez a {} tree", args[0]))
}

impl Backend for FakeBackend {
    fn world_state(&self) -> &Mutex<State> {
        &self.world_state
    }
    fn world_agent_id(&self) -> UUID {
        self.id
    }
    fn world_snapshot<'a>(
        &'a self,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<Snapshot, String>> {
        Box::pin(async move {
            if cancellation.is_cancellation_requested() {
                Err("World query cancelled".into())
            } else {
                Ok(lock(&self.world_state).fake.clone())
            }
        })
    }
    fn world_query<'a>(
        &'a self,
        query: Query,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<Snapshot, String>> {
        Box::pin(async move {
            if cancellation.is_cancellation_requested() {
                return Err("World query cancelled".into());
            }
            self.record(format!("CALL world-query {}", query_name(&query)));
            let snapshot = lock(&self.world_state).fake.clone();
            match query {
                Query::FindRegion(name)
                    if !snapshot
                        .regions
                        .iter()
                        .any(|region| region.name.eq_ignore_ascii_case(&name)) =>
                {
                    Err(format!("Lookup of {name} failed"))
                }
                Query::EstateCovenant(_) | Query::DownloadTerrain(_)
                    if snapshot.estate.is_none() =>
                {
                    Err("Estate fixture is missing".into())
                }
                _ => Ok(snapshot),
            }
        })
    }
    fn world_mutate<'a>(
        &'a self,
        mutation: Mutation,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<String, String>> {
        Box::pin(async move {
            if cancellation.is_cancellation_requested() {
                return Err("World mutation cancelled".into());
            }
            let mut state = lock(&self.world_state);
            let result = match mutation {
                Mutation::Movement(action) => apply_fake_movement(self, &mut state, action)?,
                Mutation::ChangePermissions { root, permissions } => {
                    let root_local = state
                        .fake
                        .primitives
                        .iter()
                        .find(|prim| prim.id == root)
                        .map(|prim| {
                            if prim.parent_id == 0 {
                                prim.local_id
                            } else {
                                prim.parent_id
                            }
                        })
                        .ok_or_else(|| format!("Cannot find requested object {root}"))?;
                    let mut count = 0;
                    for prim in &mut state.fake.primitives {
                        if prim.local_id == root_local || prim.parent_id == root_local {
                            if let Some(properties) = &mut prim.properties {
                                properties.permissions.next_owner_mask = permissions;
                            }
                            count += 1;
                        }
                    }
                    self.record(format!(
                        "CALL object-permissions {root} {:08x}",
                        permissions.0
                    ));
                    format!(
                        "Set permissions to {permissions:?} on {count} objects and 0 inventory items"
                    )
                }
                Mutation::DeRez(id) => {
                    let before = state.fake.primitives.len();
                    state.fake.primitives.retain(|prim| prim.id != id);
                    if before == state.fake.primitives.len() {
                        return Err(format!("Could not find object {id}"));
                    }
                    self.record(format!("CALL object-derez {id}"));
                    "Object removed".into()
                }
                Mutation::Import {
                    mut primitives,
                    use_group,
                } => {
                    let base = state
                        .fake
                        .primitives
                        .iter()
                        .map(|prim| prim.local_id)
                        .max()
                        .unwrap_or(100);
                    let local_map = primitives
                        .iter()
                        .enumerate()
                        .map(|(index, prim)| {
                            let local = base
                                .saturating_add(u32::try_from(index).unwrap_or(u32::MAX))
                                .saturating_add(1);
                            (prim.local_id, local)
                        })
                        .collect::<HashMap<_, _>>();
                    for prim in &mut primitives {
                        let old_parent = prim.parent_id;
                        prim.local_id = *local_map
                            .get(&prim.local_id)
                            .ok_or("Imported primitive ID mapping is incomplete")?;
                        prim.parent_id = if old_parent == 0 {
                            0
                        } else {
                            *local_map
                                .get(&old_parent)
                                .ok_or("Imported primitive parent is outside the linkset")?
                        };
                        prim.id = UUID::new_with_u_int64(
                            0x9300_0000_0000_0000 | u64::from(prim.local_id),
                        )
                        .map_err(|_| "Creating imported primitive UUID")?;
                        prim.position.x += state.fake.position.x;
                        prim.position.y += state.fake.position.y;
                        prim.position.z += state.fake.position.z + 3.0;
                    }
                    let count = primitives.len();
                    state.fake.primitives.extend(primitives);
                    self.record(format!(
                        "CALL object-import count={count} use-group={use_group}"
                    ));
                    format!("Imported {count} primitives")
                }
                Mutation::SetTextures(enabled) => {
                    state.textures_enabled = enabled;
                    self.record(format!("CALL texture-stream enabled={enabled}"));
                    "Texture setting updated".into()
                }
                Mutation::Tree(species) => {
                    self.record(format!("CALL object-tree {species:?}"));
                    "Tree rez requested".into()
                }
                Mutation::UploadTerrain { name, data } => {
                    let estate = state
                        .fake
                        .estate
                        .as_mut()
                        .ok_or("Estate fixture is missing")?;
                    estate.terrain = data;
                    self.record(format!("CALL estate-upload-terrain {name}"));
                    "Terrain upload completed".into()
                }
            };
            Ok(result)
        })
    }
    fn world_texture<'a>(
        &'a self,
        id: UUID,
        _discard: i32,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<Vec<u8>, String>> {
        Box::pin(async move {
            if cancellation.is_cancellation_requested() {
                return Err("Texture download cancelled".into());
            }
            self.record(format!("CALL texture-download {id}"));
            lock(&self.world_state)
                .fake
                .assets
                .get(&id)
                .cloned()
                .ok_or_else(|| format!("Download failed or texture not found: {id}"))
        })
    }
    fn world_resolve_avatar<'a>(
        &'a self,
        name: &'a str,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<Option<UUID>, String>> {
        Box::pin(async move {
            if cancellation.is_cancellation_requested() {
                return Err("Avatar lookup cancelled".into());
            }
            Ok(lock(&self.world_state)
                .fake
                .avatars
                .iter()
                .find(|avatar| avatar.name.eq_ignore_ascii_case(name))
                .map(|avatar| avatar.id))
        })
    }
    fn apply_world_fixture(&self, fields: &[&str]) -> Result<(), &'static str> {
        apply_fixture(self, fields)
    }
}

fn query_name(query: &Query) -> &'static str {
    match query {
        Query::AgentLocations(_) => "agent-locations",
        Query::FindRegion(_) => "find-region",
        Query::GridLayer => "grid-layer",
        Query::GridMap => "grid-map",
        Query::Parcels => "parcels",
        Query::ParcelOwners(_) => "parcel-owners",
        Query::ParcelObjects(_, _) => "parcel-objects",
        Query::EstateCovenant(_) => "estate-covenant",
        Query::DownloadTerrain(_) => "download-terrain",
    }
}

fn apply_fake_movement(
    backend: &FakeBackend,
    state: &mut State,
    action: Movement,
) -> Result<String, String> {
    let value = match action {
        Movement::Pulse(direction, duration) => {
            backend.record(format!(
                "CALL movement-{} duration-ms={}",
                direction_name(direction),
                duration.as_millis()
            ));
            format!("movement {} complete", direction_name(direction))
        }
        Movement::Crouch(enabled) => {
            backend.record(format!("CALL movement-crouch {enabled}"));
            "crouch updated".into()
        }
        Movement::Fly(enabled) => {
            backend.record(format!("CALL movement-fly {enabled}"));
            "flight updated".into()
        }
        Movement::FlyTo(position, duration) => {
            state.fake.position = position;
            backend.record(format!(
                "CALL movement-flyto {position:?} duration-ms={}",
                duration.as_millis()
            ));
            "FlyTo complete".into()
        }
        Movement::Follow(target) => {
            state.follow_target = target.as_ref().map(|value| value.0);
            backend.record(format!(
                "CALL movement-follow {}",
                target
                    .as_ref()
                    .map_or_else(|| "off".into(), |(id, _)| id.to_string())
            ));
            "Follow updated".into()
        }
        Movement::GoHome => {
            state.fake.position = Vector3::new_with_single_single_single(128.0, 128.0, 25.0)
                .map_err(|_| "Invalid home position")?;
            backend.record("CALL teleport-home".into());
            "Teleport complete".into()
        }
        Movement::TeleportRegion(name, position) => {
            state.fake.region_name.clone_from(&name);
            state.fake.position = position;
            backend.record(format!("CALL teleport-region {name} {position:?}"));
            "Teleport complete".into()
        }
        Movement::TeleportLandmark(id) => {
            backend.record(format!("CALL teleport-landmark {id}"));
            "Teleport complete".into()
        }
        Movement::Jump => {
            backend.record("CALL movement-jump".into());
            "Jump complete".into()
        }
        Movement::AutoPilot { local, global } => {
            state.fake.position = local;
            backend.record(format!("CALL movement-autopilot {global:?}"));
            "Autopilot started".into()
        }
        Movement::SetHome => {
            backend.record("CALL movement-set-home".into());
            "Home set".into()
        }
        Movement::Sit(id) => {
            backend.record(format!("CALL movement-sit {id}"));
            "Sit requested".into()
        }
        Movement::Stand => {
            backend.record("CALL movement-stand".into());
            "Stand requested".into()
        }
        Movement::Turn(position) => {
            backend.record(format!("CALL movement-turn {position:?}"));
            "Turn complete".into()
        }
        Movement::Cross(direction, fly) => {
            state.fake.position.x = if direction.x < 0.0 { 246.0 } else { 10.0 };
            state.fake.position.y = if direction.y < 0.0 { 246.0 } else { 10.0 };
            backend.record(format!("CALL movement-cross {direction:?} fly={fly}"));
            "Successfully crossed region border".into()
        }
    };
    Ok(value)
}

pub(super) fn apply_fake_directive(
    manager: &ClientManager,
    fields: &[&str],
) -> Option<Result<(), &'static str>> {
    if !fields
        .first()
        .is_some_and(|name| name.starts_with("world-"))
    {
        return None;
    }
    let client = fields
        .get(1)
        .and_then(|value| UUID::new_with_string((*value).into()).ok());
    let Some(client) = client else {
        return Some(Err("world fixture client UUID is invalid"));
    };
    let Some(client) = manager.clients.get(&client) else {
        return Some(Err("world fixture client is not registered"));
    };
    Some(client.backend.apply_world_fixture(fields))
}

fn fixture_uuid(value: &str) -> Result<UUID, &'static str> {
    UUID::new_with_string(value.into()).map_err(|_| "world fixture UUID is invalid")
}
fn fixture_number<T: std::str::FromStr>(value: &str) -> Result<T, &'static str> {
    value.parse().map_err(|_| "world fixture number is invalid")
}
fn fixture_vector(x: &str, y: &str, z: &str) -> Result<Vector3, &'static str> {
    Vector3::new_with_single_single_single(
        fixture_number(x)?,
        fixture_number(y)?,
        fixture_number(z)?,
    )
    .map_err(|_| "world fixture vector is invalid")
}
fn fixture_hex(value: &str) -> Result<Vec<u8>, &'static str> {
    if !value.len().is_multiple_of(2)
        || u64::try_from(value.len() / 2).unwrap_or(u64::MAX) > MAX_FILE_BYTES
    {
        return Err("world asset fixture is invalid or too large");
    }
    value
        .as_bytes()
        .chunks_exact(2)
        .map(|pair| {
            let high = hex_nibble(pair[0]).ok_or("world asset fixture is invalid")?;
            let low = hex_nibble(pair[1]).ok_or("world asset fixture is invalid")?;
            Ok(high << 4 | low)
        })
        .collect()
}
const fn hex_nibble(value: u8) -> Option<u8> {
    match value {
        b'0'..=b'9' => Some(value - b'0'),
        b'a'..=b'f' => Some(value - b'a' + 10),
        b'A'..=b'F' => Some(value - b'A' + 10),
        _ => None,
    }
}

fn apply_fixture(backend: &FakeBackend, fields: &[&str]) -> Result<(), &'static str> {
    let mut state = lock(&backend.world_state);
    let world = &mut state.fake;
    match fields {
        ["world-region", _, handle, name, x, y, z, wind_x, wind_y] => {
            world.region_handle = fixture_number(handle)?;
            world.region_name = (*name).into();
            world.position = fixture_vector(x, y, z)?;
            let wind =
                Vector2::new_with_single_single(fixture_number(wind_x)?, fixture_number(wind_y)?)
                    .map_err(|_| "world fixture wind is invalid")?;
            world.wind = Some(vec![wind; 256]);
        }
        ["world-avatar", _, id, name, x, y, z] => world.avatars.push(AvatarView {
            id: fixture_uuid(id)?,
            name: (*name).into(),
            position: fixture_vector(x, y, z)?,
        }),
        [
            "world-prim",
            _,
            id,
            local,
            parent,
            owner,
            name,
            description,
            x,
            y,
            z,
            texture,
            particle,
        ] => {
            let mut prim = Primitive::new_with_constructor()
                .map_err(|_| "world primitive fixture could not be created")?;
            prim.id = fixture_uuid(id)?;
            prim.local_id = fixture_number(local)?;
            prim.parent_id = fixture_number(parent)?;
            prim.owner_id = fixture_uuid(owner)?;
            prim.position = fixture_vector(x, y, z)?;
            prim.scale = Vector3::new_with_single_single_single(1.0, 1.0, 1.0)
                .map_err(|_| "world primitive fixture scale is invalid")?;
            let mut properties = PrimitiveObjectProperties::new()
                .map_err(|_| "world primitive properties fixture could not be created")?;
            properties.object_id = prim.id;
            properties.owner_id = prim.owner_id;
            properties.creator_id = prim.owner_id;
            properties.name = (*name).into();
            properties.description = (*description).into();
            properties.permissions = libremetaverse::Permissions::full_permissions();
            prim.properties = Some(properties);
            let texture = fixture_uuid(texture)?;
            let mut textures = PrimitiveTextureEntry::new_with_uuid(texture)
                .map_err(|_| "world texture fixture could not be created")?;
            textures
                .create_face(0)
                .map_err(|_| "world texture face fixture could not be created")?
                .set_texture_id(texture);
            prim.textures = Some(textures);
            if *particle == "true" {
                prim.particle_sys.crc = 1;
                prim.particle_sys.pattern = PrimitiveParticleSystemSourcePattern::DROP;
                prim.particle_sys.texture = texture;
            } else if *particle != "false" {
                return Err("world particle fixture must be true or false");
            }
            world.primitives.push(prim);
        }
        [
            "world-parcel",
            _,
            local,
            owner,
            name,
            description,
            dwell,
            area,
            total,
            max,
        ] => {
            let mut parcel = Parcel::new(fixture_number(local)?)
                .map_err(|_| "world parcel fixture could not be created")?;
            parcel.owner_id = fixture_uuid(owner)?;
            parcel.name = (*name).into();
            parcel.desc = (*description).into();
            parcel.dwell = fixture_number(dwell)?;
            parcel.area = fixture_number(area)?;
            parcel.total_prims = fixture_number(total)?;
            parcel.max_prims = fixture_number(max)?;
            world.parcels.push(ParcelView {
                parcel,
                owners: Vec::new(),
                selected: HashMap::new(),
            });
        }
        ["world-parcel-owner", _, local, owner, count] => {
            let local: i32 = fixture_number(local)?;
            world
                .parcels
                .iter_mut()
                .find(|view| view.parcel.local_id == local)
                .ok_or("world parcel fixture must precede owner")?
                .owners
                .push(OwnerView {
                    owner: fixture_uuid(owner)?,
                    count: fixture_number(count)?,
                });
        }
        ["world-parcel-object", _, local, owner, ids] => {
            let local: i32 = fixture_number(local)?;
            let view = world
                .parcels
                .iter_mut()
                .find(|view| view.parcel.local_id == local)
                .ok_or("world parcel fixture must precede object")?;
            let values = if *ids == "-" {
                Vec::new()
            } else {
                ids.split(',')
                    .map(fixture_number)
                    .collect::<Result<_, _>>()?
            };
            view.selected.insert(fixture_uuid(owner)?, values);
        }
        ["world-grid-region", _, name, handle, x, y, agents] => world.regions.push(GridRegion {
            access: libremetaverse::SimAccess::UNKNOWN,
            agents: fixture_number(agents)?,
            map_image_id: UUID::zero(),
            name: (*name).into(),
            region_flags: libremetaverse::RegionFlags(0),
            region_handle: fixture_number(handle)?,
            water_height: 20,
            x: fixture_number(x)?,
            y: fixture_number(y)?,
        }),
        ["world-layer", _, image, bottom, left, top, right] => world.layers.push(GridLayer {
            bottom: fixture_number(bottom)?,
            image_id: fixture_uuid(image)?,
            left: fixture_number(left)?,
            right: fixture_number(right)?,
            top: fixture_number(top)?,
        }),
        ["world-agent-location", _, handle, count, x, y] => world
            .locations
            .entry(fixture_number(handle)?)
            .or_default()
            .push(AgentLocation {
                count: fixture_number(count)?,
                x: fixture_number(x)?,
                y: fixture_number(y)?,
            }),
        [
            "world-estate",
            _,
            name,
            owner,
            covenant,
            timestamp,
            body,
            terrain,
        ] => {
            world.estate = Some(EstateView {
                name: (*name).into(),
                owner: fixture_uuid(owner)?,
                covenant: fixture_uuid(covenant)?,
                timestamp: fixture_number(timestamp)?,
                body: (*body).into(),
                terrain: fixture_hex(terrain)?,
            });
        }
        ["world-asset", _, id, data] => {
            world.assets.insert(fixture_uuid(id)?, fixture_hex(data)?);
        }
        ["world-syntax", _, tokens] => world.syntax.extend(tokens.split(',').map(str::to_owned)),
        _ => return Err("invalid world fake directive"),
    }
    Ok(())
}

pub(super) fn install_live(backend: &Arc<LiveBackend>, subscriptions: &mut Vec<Subscription>) {
    let weak = Arc::downgrade(backend);
    let grid = lock(&backend.client).grid();
    subscriptions.push(
        grid.subscribe_coarse_location_update(Arc::new(move |event| {
            let Some(backend) = weak.upgrade() else {
                return;
            };
            let positions = event.positions();
            let simulator = event.simulator();
            let mut state = lock(&backend.world_state);
            for (id, position) in positions {
                if let Some(avatar) = state.fake.avatars.iter_mut().find(|avatar| avatar.id == id) {
                    avatar.position = position;
                } else {
                    state.fake.avatars.push(AvatarView {
                        id,
                        name: String::new(),
                        position,
                    });
                }
            }
            let removed = event.removed_entries();
            state
                .fake
                .avatars
                .retain(|avatar| !removed.contains(&avatar.id));
            let follow_position = state.follow_target.and_then(|target| {
                state
                    .fake
                    .avatars
                    .iter()
                    .find(|avatar| avatar.id == target)
                    .map(|avatar| avatar.position)
            });
            drop(state);
            if let Some(position) = follow_position {
                let Ok((x, y)) = region_origin(simulator.handle) else {
                    return;
                };
                let _ = backend.with_agent(|agent| {
                    agent.auto_pilot_with_double_double_double(
                        f64::from(position.x) + f64::from(x),
                        f64::from(position.y) + f64::from(y),
                        f64::from(position.z),
                    )
                });
            }
        })),
    );
    let weak = Arc::downgrade(backend);
    let (objects, assets) = {
        let client = lock(&backend.client);
        (client.objects(), client.assets())
    };
    subscriptions.push(objects.subscribe_object_update(Arc::new(move |event| {
        let Some(backend) = weak.upgrade() else {
            return;
        };
        let requests = {
            let mut state = lock(&backend.world_state);
            if !state.textures_enabled {
                return;
            }
            texture_ids(&event.prim())
                .into_iter()
                .filter(|id| state.requested_textures.insert(*id))
                .collect::<Vec<_>>()
        };
        for id in requests {
            let assets = assets.clone();
            tokio::spawn(async move {
                let _ = assets
                    .request_asset_with_uuid_asset_type_boolean_cancellation_token(
                        id,
                        AssetType::Texture,
                        false,
                        None,
                    )
                    .await;
            });
        }
    })));
}

impl Backend for LiveBackend {
    fn world_state(&self) -> &Mutex<State> {
        &self.world_state
    }
    fn world_agent_id(&self) -> UUID {
        self.id
    }
    fn world_snapshot<'a>(
        &'a self,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<Snapshot, String>> {
        Box::pin(async move {
            if cancellation.is_cancellation_requested() {
                return Err("World query cancelled".into());
            }
            let (position, simulator, grid) = {
                let mut client = lock(&self.client);
                (
                    client.self_().sim_position(),
                    client.network().current_sim(),
                    client.grid(),
                )
            };
            let simulator = simulator.ok_or("No current simulator available")?;
            let primitives = simulator
                .objects_primitives
                .read()
                .unwrap_or_else(std::sync::PoisonError::into_inner)
                .values()
                .take(MAX_QUERY_RESULTS + 1)
                .cloned()
                .collect::<Vec<_>>();
            if primitives.len() > MAX_QUERY_RESULTS {
                return Err("Simulator primitive cache exceeds 65,535 entries".into());
            }
            let parcels = simulator
                .parcels
                .read()
                .unwrap_or_else(std::sync::PoisonError::into_inner)
                .values()
                .take(MAX_QUERY_RESULTS + 1)
                .cloned()
                .map(|parcel| ParcelView {
                    parcel,
                    owners: Vec::new(),
                    selected: HashMap::new(),
                })
                .collect::<Vec<_>>();
            if parcels.len() > MAX_QUERY_RESULTS {
                return Err("Simulator parcel cache exceeds 65,535 entries".into());
            }
            let state = lock(&self.world_state);
            Ok(Snapshot {
                region_name: simulator.name.clone(),
                region_handle: simulator.handle,
                position,
                wind: simulator
                    .wind_speeds
                    .read()
                    .unwrap_or_else(std::sync::PoisonError::into_inner)
                    .clone(),
                primitives,
                avatars: state.fake.avatars.clone(),
                parcels,
                regions: grid.regions_read_only().into_values().collect(),
                layers: Vec::new(),
                locations: HashMap::new(),
                estate: None,
                assets: HashMap::new(),
                syntax: native_syntax(),
            })
        })
    }
    fn world_query<'a>(
        &'a self,
        query: Query,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<Snapshot, String>> {
        Box::pin(async move {
            match query {
                Query::AgentLocations(handle) => {
                    let grid = lock(&self.client).grid();
                    let items = grid
                        .map_items(
                            handle,
                            libremetaverse::GridItemType::AgentLocations,
                            libremetaverse::GridLayerType::Objects,
                            Some(cancellation.clone()),
                        )
                        .await
                        .map_err(|_| "Failed to fetch agent locations")?;
                    let mut snapshot = self.world_snapshot(cancellation).await?;
                    let mut values = Vec::new();
                    for item in items.into_iter().take(MAX_QUERY_RESULTS) {
                        if let libremetaverse::MapItemData::AgentLocation(ref location) = item.data
                        {
                            values.push(AgentLocation {
                                count: location.avatar_count,
                                x: item.local_x(),
                                y: item.local_y(),
                            });
                        }
                    }
                    snapshot.locations.insert(handle, values);
                    Ok(snapshot)
                }
                Query::FindRegion(name) => {
                    let grid = lock(&self.client).grid();
                    let region = grid
                        .get_grid_region_with_string_grid_layer_type_cancellation_token(
                            name,
                            libremetaverse::GridLayerType::Objects,
                            Some(cancellation.clone()),
                        )
                        .await
                        .map_err(|_| "Simulator lookup failed")?
                        .flatten();
                    let mut snapshot = self.world_snapshot(cancellation).await?;
                    if let Some(region) = region {
                        snapshot.regions.push(region);
                    }
                    Ok(snapshot)
                }
                Query::GridLayer => {
                    let grid = lock(&self.client).grid();
                    let layers = Arc::new(Mutex::new(Vec::new()));
                    let callback = Arc::clone(&layers);
                    let subscription = grid.subscribe_grid_layer(Arc::new(move |event| {
                        let mut values = lock(&callback);
                        if values.len() < MAX_QUERY_RESULTS {
                            values.push(event.layer());
                        }
                    }));
                    grid.request_map_layer(
                        libremetaverse::GridLayerType::Objects,
                        Some(cancellation.clone()),
                    )
                    .await
                    .map_err(|_| "Grid layer request failed")?;
                    tokio::select! {()=tokio::time::sleep(Duration::from_millis(250))=>{},()=cancellation.cancelled()=>return Err("Grid layer request cancelled".into())}
                    drop(subscription);
                    let mut snapshot = self.world_snapshot(cancellation).await?;
                    snapshot.layers.clone_from(&lock(&layers));
                    Ok(snapshot)
                }
                Query::GridMap => {
                    let grid = lock(&self.client).grid();
                    grid.request_mainland_sims(libremetaverse::GridLayerType::Objects)
                        .map_err(|_| "Grid map request failed")?;
                    tokio::select! {()=tokio::time::sleep(Duration::from_millis(500))=>{},()=cancellation.cancelled()=>return Err("Grid map request cancelled".into())}
                    self.world_snapshot(cancellation).await
                }
                Query::Parcels => {
                    let (parcels, sim) = {
                        let client = lock(&self.client);
                        (client.parcels(), client.network().current_sim())
                    };
                    let sim = sim.ok_or("No current simulator available")?;
                    parcels.request_all_sim_parcels_with_simulator_boolean_time_span_cancellation_token(sim,false,Duration::from_millis(25),Some(cancellation.clone())).await.map_err(|_|"Failed to retrieve information on all the simulator parcels")?;
                    self.world_snapshot(cancellation).await
                }
                Query::ParcelOwners(local) => live_parcel_owners(self, local, cancellation).await,
                Query::ParcelObjects(local, owner) => {
                    live_parcel_objects(self, local, owner, cancellation).await
                }
                Query::EstateCovenant(timeout) => {
                    live_estate_covenant(self, timeout, cancellation).await
                }
                Query::DownloadTerrain(timeout) => {
                    live_download_terrain(self, timeout, cancellation).await
                }
            }
        })
    }
    fn world_mutate<'a>(
        &'a self,
        mutation: Mutation,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<String, String>> {
        Box::pin(async move {
            if cancellation.is_cancellation_requested() {
                return Err("World mutation cancelled".into());
            }
            match mutation {
                Mutation::Movement(movement) => live_movement(self, movement, cancellation).await,
                Mutation::ChangePermissions { root, permissions } => {
                    live_change_permissions(self, root, permissions, cancellation).await
                }
                Mutation::DeRez(id) => {
                    let (inventory, sim) = {
                        let client = lock(&self.client);
                        (client.inventory(), client.network().current_sim())
                    };
                    let sim = sim.ok_or("No current simulator available")?;
                    let prim = sim
                        .objects_primitives
                        .read()
                        .unwrap_or_else(std::sync::PoisonError::into_inner)
                        .values()
                        .find(|prim| prim.id == id)
                        .cloned()
                        .ok_or_else(|| format!("Could not find object {id}"))?;
                    let trash = inventory
                        .find_folder_for_type_with_folder_type(FolderType::Trash)
                        .map_err(|_| "Trash folder is unavailable")?;
                    inventory
                        .request_de_rez_to_inventory_with_u_int32_de_rez_destination_uuid_uuid(
                            prim.local_id,
                            libremetaverse::DeRezDestination::AgentInventoryTake,
                            trash,
                            UUID::random().map_err(|_| "Could not create transaction UUID")?,
                        )
                        .map_err(|_| "DeRez request failed")?;
                    Ok("Object removed".into())
                }
                Mutation::Import {
                    primitives,
                    use_group,
                } => live_import(self, primitives, use_group, cancellation).await,
                Mutation::SetTextures(enabled) => {
                    lock(&self.world_state).textures_enabled = enabled;
                    if enabled {
                        let snapshot = self.world_snapshot(cancellation.clone()).await?;
                        let mut requests = BTreeSet::new();
                        for prim in &snapshot.primitives {
                            requests.extend(texture_ids(prim));
                        }
                        let assets = lock(&self.client).assets();
                        for id in requests {
                            cancellation
                                .throw_if_cancellation_requested()
                                .map_err(|_| "Texture download cancelled")?;
                            let should_request =
                                lock(&self.world_state).requested_textures.insert(id);
                            if should_request {
                                assets
                                    .request_asset_with_uuid_asset_type_boolean_cancellation_token(
                                        id,
                                        AssetType::Texture,
                                        false,
                                        Some(cancellation.clone()),
                                    )
                                    .await
                                    .map_err(|_| format!("Texture request failed: {id}"))?;
                            }
                        }
                    }
                    Ok("Texture setting updated".into())
                }
                Mutation::Tree(species) => {
                    let (objects, sim, position, group) = {
                        let mut client = lock(&self.client);
                        let position = client.self_().sim_position();
                        let group = client.self_().active_group();
                        (
                            client.objects(),
                            client.network().current_sim(),
                            position,
                            group,
                        )
                    };
                    let sim = sim.ok_or("No current simulator available")?;
                    let position = Vector3::new_with_single_single_single(
                        position.x,
                        position.y,
                        position.z + 3.0,
                    )
                    .map_err(|_| "Invalid tree position")?;
                    objects
                        .add_tree(
                            sim,
                            Vector3::new_with_single_single_single(0.5, 0.5, 0.5)
                                .map_err(|_| "Invalid tree scale")?,
                            Quaternion::identity(),
                            position,
                            species,
                            group,
                            false,
                        )
                        .map_err(|_| "Tree rez request failed")?;
                    Ok("Tree rez requested".into())
                }
                Mutation::UploadTerrain { name, data } => {
                    live_upload_terrain(self, name, data, cancellation).await
                }
            }
        })
    }
    fn world_texture<'a>(
        &'a self,
        id: UUID,
        _discard: i32,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<Vec<u8>, String>> {
        Box::pin(async move {
            let assets = lock(&self.client).assets();
            let texture = assets
                .request_asset_with_uuid_asset_type_boolean_cancellation_token(
                    id,
                    AssetType::Texture,
                    true,
                    Some(cancellation),
                )
                .await
                .map_err(|_| format!("Texture request failed: {id}"))?
                .ok_or_else(|| format!("Download failed or texture not found: {id}"))?;
            Ok(texture.asset_data)
        })
    }
    fn world_resolve_avatar<'a>(
        &'a self,
        name: &'a str,
        cancellation: CancellationToken,
    ) -> BackendFuture<'a, Result<Option<UUID>, String>> {
        Box::pin(async move {
            super::ClientBackend::resolve_avatar(self, name, cancellation)
                .await
                .map_err(str::to_owned)
        })
    }
}

fn native_syntax() -> Vec<String> {
    [
        "default",
        "state",
        "state_entry",
        "touch_start",
        "timer",
        "integer",
        "float",
        "string",
        "key",
        "vector",
        "rotation",
        "list",
        "if",
        "else",
        "for",
        "while",
        "return",
        "jump",
        "TRUE",
        "FALSE",
        "NULL_KEY",
        "llSay",
        "llOwnerSay",
        "llParticleSystem",
    ]
    .into_iter()
    .map(str::to_owned)
    .collect()
}

async fn live_parcel_owners(
    backend: &LiveBackend,
    local: i32,
    cancellation: CancellationToken,
) -> Result<Snapshot, String> {
    let parcels = lock(&backend.client).parcels();
    let sim = backend
        .network
        .current_sim()
        .ok_or("No current simulator available")?;
    let (sender, receiver) = tokio::sync::oneshot::channel();
    let sender = Arc::new(Mutex::new(Some(sender)));
    let callback = Arc::clone(&sender);
    let subscription = parcels.subscribe_parcel_object_owners_reply(Arc::new(move |event| {
        if let Some(sender) = lock(&callback).take() {
            let _ = sender.send(event.prim_owners());
        }
    }));
    parcels
        .request_object_owners(sim, local)
        .map_err(|_| "Parcel owner request failed")?;
    let owners = tokio::select! {value=receiver=>value.map_err(|_|"Parcel owner reply channel closed")?,()=tokio::time::sleep(Duration::from_secs(10))=>return Err("Timed out waiting for packet.".into()),()=cancellation.cancelled()=>return Err("Parcel owner request cancelled".into())};
    drop(subscription);
    let mut snapshot = backend.world_snapshot(cancellation).await?;
    let view=snapshot.parcels.iter_mut().find(|view|view.parcel.local_id==local).ok_or_else(||format!("Unable to find Parcel {local} in Parcels Dictionary, Did you run parcelinfo to populate the dictionary first?"))?;
    view.owners = owners
        .into_iter()
        .take(MAX_QUERY_RESULTS)
        .map(|owner| OwnerView {
            owner: owner.owner_id,
            count: owner.count,
        })
        .collect();
    Ok(snapshot)
}

async fn live_parcel_objects(
    backend: &LiveBackend,
    local: i32,
    owner: UUID,
    cancellation: CancellationToken,
) -> Result<Snapshot, String> {
    let parcels = lock(&backend.client).parcels();
    let (sender, receiver) = tokio::sync::oneshot::channel();
    let sender = Arc::new(Mutex::new(Some(sender)));
    let callback = Arc::clone(&sender);
    let subscription = parcels.subscribe_force_select_objects_reply(Arc::new(move |event| {
        let ids = event.object_i_ds();
        if ids.len() < 251
            && let Some(sender) = lock(&callback).take()
        {
            let _ = sender.send(ids);
        }
    }));
    parcels
        .request_select_objects(local, libremetaverse::ObjectReturnType::List, owner)
        .map_err(|_| "Parcel object selection request failed")?;
    let ids = tokio::select! {value=receiver=>value.map_err(|_|"Parcel object reply channel closed")?,()=tokio::time::sleep(Duration::from_secs(30))=>return Err("Timed out waiting for packet.".into()),()=cancellation.cancelled()=>return Err("Parcel object request cancelled".into())};
    drop(subscription);
    let mut snapshot = backend.world_snapshot(cancellation).await?;
    let view = snapshot
        .parcels
        .iter_mut()
        .find(|view| view.parcel.local_id == local)
        .ok_or_else(|| format!("Unable to find Parcel {local} in Parcels Dictionary"))?;
    view.selected.insert(owner, ids);
    Ok(snapshot)
}

async fn live_estate_covenant(
    backend: &LiveBackend,
    timeout: Duration,
    cancellation: CancellationToken,
) -> Result<Snapshot, String> {
    let estate = lock(&backend.client).estate();
    let (sender, receiver) = tokio::sync::oneshot::channel();
    let sender = Arc::new(Mutex::new(Some(sender)));
    let callback = Arc::clone(&sender);
    let subscription = estate.subscribe_estate_covenant_reply(Arc::new(move |event| {
        if let Some(sender) = lock(&callback).take() {
            let _ = sender.send(event);
        }
    }));
    estate
        .request_covenant()
        .map_err(|_| "Covenant request failed")?;
    let reply = tokio::select! {value=receiver=>value.map_err(|_|"Covenant reply channel closed")?,()=tokio::time::sleep(timeout)=>return Err("Timeout waiting for covenant info.".into()),()=cancellation.cancelled()=>return Err("Covenant request cancelled".into())};
    drop(subscription);
    let body = if reply.covenant_id() == UUID::zero() {
        String::new()
    } else {
        estate
            .request_covenant_notecard_with_uuid_cancellation_token(
                reply.covenant_id(),
                Some(cancellation.clone()),
            )
            .await
            .map_err(|_| "Could not retrieve covenant notecard")?
            .map_or_else(
                || "Could not retrieve covenant notecard.".into(),
                |asset| String::from_utf8_lossy(&asset.asset_data).into_owned(),
            )
    };
    let mut snapshot = backend.world_snapshot(cancellation).await?;
    snapshot.estate = Some(EstateView {
        name: reply.estate_name(),
        owner: reply.estate_owner_id(),
        covenant: reply.covenant_id(),
        timestamp: u32::try_from(reply.timestamp()).unwrap_or_default(),
        body,
        terrain: Vec::new(),
    });
    Ok(snapshot)
}

async fn live_download_terrain(
    backend: &LiveBackend,
    timeout: Duration,
    cancellation: CancellationToken,
) -> Result<Snapshot, String> {
    let (assets, estate, region) = {
        let client = lock(&backend.client);
        (
            client.assets(),
            client.estate(),
            client
                .network()
                .current_sim()
                .map_or_else(|| "terrain".into(), |sim| sim.name.clone()),
        )
    };
    estate.enable_live_mutations();
    let (sender, receiver) = tokio::sync::oneshot::channel();
    let sender = Arc::new(Mutex::new(Some(sender)));
    let callback = Arc::clone(&sender);
    let xfer_assets = assets.clone();
    let initiate = assets.subscribe_initiate_download(Arc::new(move |event| {
        let _ = xfer_assets.request_asset_xfer(
            event.sim_file_name(),
            false,
            false,
            UUID::zero(),
            AssetType::Unknown,
            false,
        );
    }));
    let xfer = assets.subscribe_xfer_received(Arc::new(move |event| {
        let transfer = event.xfer();
        if transfer.base.success
            && let Some(sender) = lock(&callback).take()
        {
            let _ = sender.send(transfer.base.asset_data);
        }
    }));
    estate
        .estate_owner_message_with_string_list(
            "terrain".into(),
            vec!["download filename".into(), format!("{region}.raw")],
        )
        .map_err(|_| "Terrain download request failed")?;
    let data = tokio::select! {value=receiver=>value.map_err(|_|"Terrain transfer channel closed")?,()=tokio::time::sleep(timeout)=>return Err("Timeout while waiting for terrain data".into()),()=cancellation.cancelled()=>return Err("Terrain download cancelled".into())};
    drop(initiate);
    drop(xfer);
    if data.len() as u64 > MAX_FILE_BYTES {
        return Err("Terrain transfer exceeds the 64 MiB limit".into());
    }
    let mut snapshot = backend.world_snapshot(cancellation).await?;
    snapshot.estate = Some(EstateView {
        name: String::new(),
        owner: UUID::zero(),
        covenant: UUID::zero(),
        timestamp: 0,
        body: String::new(),
        terrain: data,
    });
    Ok(snapshot)
}

async fn live_movement(
    backend: &LiveBackend,
    movement: Movement,
    cancellation: CancellationToken,
) -> Result<String, String> {
    match movement {
        Movement::Pulse(direction, duration) => {
            let mut agent = live_agent(backend)?;
            let movement = &mut agent.movement;
            set_direction(movement, direction, true);
            movement
                .send_update_with_boolean(Some(duration.is_zero()))
                .map_err(|_| "Movement update failed")?;
            if !duration.is_zero() {
                let deadline = tokio::time::Instant::now() + duration;
                loop {
                    tokio::select! {()=tokio::time::sleep(Duration::from_millis(100))=>{if tokio::time::Instant::now()>=deadline{break;}movement.send_update_with_boolean(Some(false)).map_err(|_|"Movement update failed")?;},()=cancellation.cancelled()=>{set_direction(movement,direction,false);let _=movement.send_update_with_boolean(Some(true));return Err("Movement cancelled".into());}}
                }
            }
            set_direction(movement, direction, false);
            movement
                .send_update_with_boolean(Some(true))
                .map_err(|_| "Movement stop failed")?;
            Ok("Movement complete".into())
        }
        Movement::Crouch(enabled) => {
            live_agent(backend)?
                .crouch(enabled)
                .map_err(|_| "Crouch request failed")?;
            Ok("Crouch updated".into())
        }
        Movement::Fly(enabled) => {
            live_agent(backend)?
                .fly(enabled)
                .map_err(|_| "Flight request failed")?;
            Ok("Flight updated".into())
        }
        Movement::FlyTo(target, duration) => {
            live_fly_to(backend, target, duration, cancellation).await
        }
        Movement::Follow(target) => {
            lock(&backend.world_state).follow_target = target.as_ref().map(|value| value.0);
            if let Some((id, position)) = target {
                let snapshot = backend.world_snapshot(cancellation).await?;
                let (x, y) = region_origin(snapshot.region_handle)?;
                live_agent(backend)?
                    .auto_pilot_with_double_double_double(
                        f64::from(position.x) + f64::from(x),
                        f64::from(position.y) + f64::from(y),
                        f64::from(position.z),
                    )
                    .map_err(|_| "Follow autopilot failed")?;
                Ok(format!("Following {id}"))
            } else {
                let _ = live_agent(backend)?.auto_pilot_cancel();
                Ok("Following stopped".into())
            }
        }
        Movement::GoHome => {
            let agent = live_agent(backend)?;
            let success = agent
                .go_home(Some(cancellation))
                .await
                .map_err(|_| "Teleport Home Failed")?;
            if success {
                Ok("Teleport complete".into())
            } else {
                Err("Teleport Home Failed".into())
            }
        }
        Movement::TeleportRegion(name, position) => {
            let agent = live_agent(backend)?;
            let success = agent
                .teleport_with_string_vector3_cancellation_token(name, position, Some(cancellation))
                .await
                .map_err(|_| "Teleport failed")?;
            if success {
                Ok("Teleport complete".into())
            } else {
                Err(format!("Teleport failed: {}", agent.teleport_message()))
            }
        }
        Movement::TeleportLandmark(id) => {
            let agent = live_agent(backend)?;
            if agent
                .teleport_with_uuid_cancellation_token(id, Some(cancellation))
                .await
                .map_err(|_| "Teleport Failed")?
            {
                Ok("Teleport complete".into())
            } else {
                Err("Teleport Failed".into())
            }
        }
        Movement::Jump => {
            live_agent(backend)?
                .jump(true)
                .map_err(|_| "Jump request failed")?;
            Ok("Jump complete".into())
        }
        Movement::AutoPilot { global, .. } => {
            live_agent(backend)?
                .auto_pilot_with_double_double_double(global[0], global[1], global[2])
                .map_err(|_| "Autopilot request failed")?;
            Ok("Autopilot started".into())
        }
        Movement::SetHome => {
            live_agent(backend)?
                .set_home()
                .map_err(|_| "Set home request failed")?;
            Ok("Home set".into())
        }
        Movement::Sit(id) => {
            let sim = backend
                .network
                .current_sim()
                .ok_or("No current simulator available")?;
            let prim = sim
                .objects_primitives
                .read()
                .unwrap_or_else(std::sync::PoisonError::into_inner)
                .values()
                .find(|prim| prim.id == id)
                .cloned()
                .ok_or_else(|| format!("Could not find object {id}"))?;
            let agent = live_agent(backend)?;
            agent
                .request_sit(id, Vector3::zero())
                .map_err(|_| "Sit request failed")?;
            agent.sit().map_err(|_| "Sit request failed")?;
            Ok(format!("Sit requested on {}", prim.local_id))
        }
        Movement::Stand => {
            live_agent(backend)?
                .stand()
                .map_err(|_| "Stand request failed")?;
            Ok("Stand requested".into())
        }
        Movement::Turn(target) => {
            let agent = live_agent(backend)?;
            agent
                .movement
                .turn_toward(target, Some(true))
                .map_err(|_| "Turn request failed")?;
            Ok("Turn complete".into())
        }
        Movement::Cross(direction, fly) => live_cross(backend, direction, fly, cancellation).await,
    }
}

fn live_agent(backend: &LiveBackend) -> Result<libremetaverse::AgentManager, String> {
    let client = lock(&backend.client).clone();
    libremetaverse::AgentManager::new(Some(Arc::new(client)))
        .map_err(|_| "Could not acquire the live agent manager".into())
}

fn set_direction(
    movement: &mut libremetaverse::AgentManagerAgentMovement,
    direction: MoveDirection,
    value: bool,
) {
    match direction {
        MoveDirection::Back => movement.set_at_neg(value),
        MoveDirection::Forward => movement.set_at_pos(value),
        MoveDirection::Left => movement.set_left_pos(value),
        MoveDirection::Right => movement.set_left_neg(value),
    }
}

async fn live_fly_to(
    backend: &LiveBackend,
    target: Vector3,
    duration: Duration,
    cancellation: CancellationToken,
) -> Result<String, String> {
    let snapshot = backend.world_snapshot(cancellation.clone()).await?;
    let (x, y) = region_origin(snapshot.region_handle)?;
    let agent = live_agent(backend)?;
    agent
        .fly(true)
        .map_err(|_| "FlyTo could not enable flight")?;
    agent
        .auto_pilot_with_double_double_double(
            f64::from(target.x) + f64::from(x),
            f64::from(target.y) + f64::from(y),
            f64::from(target.z),
        )
        .map_err(|_| "FlyTo autopilot failed")?;
    let deadline = tokio::time::Instant::now() + duration;
    loop {
        if distance(agent.sim_position(), target) <= 2.0 {
            let _ = agent.auto_pilot_cancel();
            return Ok("FlyTo target reached".into());
        }
        tokio::select! {()=tokio::time::sleep(Duration::from_millis(100))=>{if tokio::time::Instant::now()>=deadline{let _=agent.auto_pilot_cancel();return Ok("FlyTo duration elapsed".into());}},()=cancellation.cancelled()=>{let _=agent.auto_pilot_cancel();return Err("FlyTo cancelled".into());}}
    }
}

async fn live_cross(
    backend: &LiveBackend,
    direction: Vector3,
    fly: bool,
    cancellation: CancellationToken,
) -> Result<String, String> {
    let snapshot = backend.world_snapshot(cancellation.clone()).await?;
    let start = snapshot.region_handle;
    let target_x = if direction.x > 0.0 {
        266.0
    } else if direction.x < 0.0 {
        -10.0
    } else {
        snapshot.position.x
    };
    let target_y = if direction.y > 0.0 {
        266.0
    } else if direction.y < 0.0 {
        -10.0
    } else {
        snapshot.position.y
    };
    let agent = live_agent(backend)?;
    agent
        .fly(fly)
        .map_err(|_| "Could not set crossing movement mode")?;
    let (region_x, region_y) = region_origin(start)?;
    agent
        .auto_pilot_with_double_double_double(
            f64::from(target_x) + f64::from(region_x),
            f64::from(target_y) + f64::from(region_y),
            f64::from(snapshot.position.z),
        )
        .map_err(|_| "Region crossing autopilot failed")?;
    let deadline = tokio::time::Instant::now() + Duration::from_mins(1);
    loop {
        if backend
            .network
            .current_sim()
            .is_some_and(|sim| sim.handle != start)
        {
            let _ = agent.auto_pilot_cancel();
            return Ok("Successfully crossed region border".into());
        }
        tokio::select! {()=tokio::time::sleep(Duration::from_millis(100))=>{if tokio::time::Instant::now()>=deadline{let _=agent.auto_pilot_cancel();return Err("Failed to cross region border: timeout".into());}},()=cancellation.cancelled()=>{let _=agent.auto_pilot_cancel();return Err("Region crossing cancelled".into());}}
    }
}

async fn live_change_permissions(
    backend: &LiveBackend,
    root: UUID,
    permissions: PermissionMask,
    cancellation: CancellationToken,
) -> Result<String, String> {
    let (objects, inventory, sim) = {
        let client = lock(&backend.client);
        (
            client.objects(),
            client.inventory(),
            client.network().current_sim(),
        )
    };
    let sim = sim.ok_or("No current simulator available")?;
    let linkset = {
        let prims = sim
            .objects_primitives
            .read()
            .unwrap_or_else(std::sync::PoisonError::into_inner);
        let selected = prims
            .values()
            .find(|prim| prim.id == root)
            .cloned()
            .ok_or_else(|| format!("Cannot find requested object {root}"))?;
        let root_prim = if selected.parent_id == 0 {
            selected
        } else {
            prims
                .get(&selected.parent_id)
                .cloned()
                .ok_or("Cannot find root prim for requested object")?
        };
        prims
            .values()
            .filter(|prim| {
                prim.local_id == root_prim.local_id || prim.parent_id == root_prim.local_id
            })
            .take(MAX_EXPORT_PRIMS + 1)
            .cloned()
            .collect::<Vec<_>>()
    };
    if linkset.len() > MAX_EXPORT_PRIMS {
        return Err("Linkset exceeds the 10,000 primitive limit".into());
    }
    let ids = linkset.iter().map(|prim| prim.local_id).collect::<Vec<_>>();
    for mask in [
        PermissionMask::MODIFY,
        PermissionMask::COPY,
        PermissionMask::TRANSFER,
    ] {
        cancellation
            .throw_if_cancellation_requested()
            .map_err(|_| "Permission update cancelled")?;
        objects
            .set_permissions(
                sim.clone(),
                ids.clone(),
                libremetaverse::PermissionWho::NEXT_OWNER,
                mask,
                permissions.0 & mask.0 != 0,
            )
            .map_err(|_| "Failed to set linkset permissions")?;
        tokio::select! {()=tokio::time::sleep(Duration::from_millis(250))=>{},()=cancellation.cancelled()=>return Err("Permission update cancelled".into())}
    }
    let mut task_items = 0;
    for prim in &linkset {
        if prim.flags.0 & PrimFlags::INVENTORY_EMPTY.0 != 0 {
            continue;
        }
        let items = inventory
            .get_task_inventory(
                prim.id,
                prim.local_id,
                Some(sim.clone()),
                Some(cancellation.clone()),
            )
            .await
            .map_err(|_| "Task inventory request failed")?;
        for entry in items {
            if let Some(mut item) = entry
                .as_any()
                .downcast_ref::<libremetaverse::InventoryItem>()
                .cloned()
            {
                let mut perms = item.permissions();
                perms.next_owner_mask = permissions;
                item.set_permissions(perms);
                inventory
                    .update_task_inventory(prim.local_id, item, Some(sim.clone()), Some(true))
                    .map_err(|_| "Task inventory permission update failed")?;
                task_items += 1;
            }
        }
    }
    Ok(format!(
        "Set permissions to {:?} on {} objects and {task_items} inventory items",
        permissions,
        ids.len()
    ))
}

async fn next_created(
    receiver: &mut tokio::sync::mpsc::UnboundedReceiver<Primitive>,
    cancellation: &CancellationToken,
) -> Result<Primitive, String> {
    tokio::select! {value=receiver.recv()=>value.ok_or_else(||"Primitive creation event channel closed".into()),()=tokio::time::sleep(Duration::from_secs(10))=>Err("Rez failed, timed out while creating prim.".into()),()=cancellation.cancelled()=>Err("Import cancelled".into())}
}

fn apply_prim_properties(
    objects: &libremetaverse::ObjectManager,
    sim: &libremetaverse::Simulator,
    created: &Primitive,
    source: &Primitive,
    position: Vector3,
) -> Result<(), String> {
    objects
        .set_position_with_simulator_u_int32_vector3(sim.clone(), created.local_id, position)
        .map_err(|_| "Setting imported primitive position failed")?;
    if let Some(textures) = &source.textures {
        objects
            .set_textures_with_simulator_u_int32_texture_entry(
                sim.clone(),
                created.local_id,
                textures.clone(),
            )
            .map_err(|_| "Setting imported primitive textures failed")?;
    }
    if let Some(light) = &source.light {
        objects
            .set_light(sim.clone(), created.local_id, light.clone())
            .map_err(|_| "Setting imported primitive light failed")?;
    }
    if let Some(flexible) = &source.flexible {
        objects
            .set_flexible(sim.clone(), created.local_id, flexible.clone())
            .map_err(|_| "Setting imported primitive flexibility failed")?;
    }
    if let Some(sculpt) = &source.sculpt {
        objects
            .set_sculpt(sim.clone(), created.local_id, sculpt.clone())
            .map_err(|_| "Setting imported primitive sculpt failed")?;
    }
    if let Some(properties) = &source.properties {
        if !properties.name.is_empty() {
            objects
                .set_name(sim.clone(), created.local_id, properties.name.clone())
                .map_err(|_| "Setting imported primitive name failed")?;
        }
        if !properties.description.is_empty() {
            objects
                .set_description(
                    sim.clone(),
                    created.local_id,
                    properties.description.clone(),
                )
                .map_err(|_| "Setting imported primitive description failed")?;
        }
    }
    Ok(())
}

async fn live_import(
    backend: &LiveBackend,
    primitives: Vec<Primitive>,
    use_group: bool,
    cancellation: CancellationToken,
) -> Result<String, String> {
    let (objects, sim, agent_position, group) = {
        let mut client = lock(&backend.client);
        let objects = client.objects();
        let sim = client.network().current_sim();
        let agent = client.self_();
        (
            objects,
            sim,
            agent.sim_position(),
            if use_group {
                agent.active_group()
            } else {
                UUID::zero()
            },
        )
    };
    let sim = sim.ok_or("No current simulator available")?;
    let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
    let subscription = objects.subscribe_object_update(Arc::new(move |event| {
        if event.is_new() && event.prim().flags.0 & PrimFlags::CREATE_SELECTED.0 != 0 {
            let _ = sender.send(event.prim());
        }
    }));
    let mut roots: Vec<_> = primitives
        .iter()
        .filter(|prim| prim.parent_id == 0)
        .collect();
    roots.sort_by_key(|prim| prim.local_id);
    if roots.is_empty() {
        return Err("Import contains no root primitives".into());
    }
    let mut created_count = 0;
    for root in roots {
        let base = Vector3::new_with_single_single_single(
            agent_position.x,
            agent_position.y,
            agent_position.z + 3.0,
        )
        .map_err(|_| "Invalid import position")?;
        objects
            .add_prim_with_simulator_construction_data_uuid_vector3_vector3_quaternion(
                sim.clone(),
                root.prim_data.clone(),
                group,
                base,
                root.scale,
                Quaternion::identity(),
            )
            .map_err(|_| "Rez request failed for root primitive")?;
        let created_root = next_created(&mut receiver, &cancellation).await?;
        apply_prim_properties(&objects, &sim, &created_root, root, base)?;
        created_count += 1;
        let mut ids = vec![created_root.local_id];
        let mut children: Vec<_> = primitives
            .iter()
            .filter(|prim| prim.parent_id == root.local_id)
            .collect();
        children.sort_by_key(|prim| prim.local_id);
        for child in children {
            let position = Vector3::new_with_single_single_single(
                base.x + child.position.x,
                base.y + child.position.y,
                base.z + child.position.z,
            )
            .map_err(|_| "Invalid child import position")?;
            objects
                .add_prim_with_simulator_construction_data_uuid_vector3_vector3_quaternion(
                    sim.clone(),
                    child.prim_data.clone(),
                    group,
                    position,
                    child.scale,
                    child.rotation,
                )
                .map_err(|_| "Rez request failed for child primitive")?;
            let created = next_created(&mut receiver, &cancellation).await?;
            apply_prim_properties(&objects, &sim, &created, child, position)?;
            ids.push(created.local_id);
            created_count += 1;
        }
        if ids.len() > 1 {
            objects
                .link_prims(sim.clone(), ids.clone())
                .map_err(|_| "Linking imported primitives failed")?;
        }
        objects
            .set_rotation_with_simulator_u_int32_quaternion(
                sim.clone(),
                created_root.local_id,
                root.rotation,
            )
            .map_err(|_| "Setting imported root rotation failed")?;
        objects
            .set_permissions(
                sim.clone(),
                ids,
                libremetaverse::PermissionWho::ALL,
                PermissionMask::ALL,
                true,
            )
            .map_err(|_| "Setting imported permissions failed")?;
    }
    drop(subscription);
    Ok(format!("Imported {created_count} primitives"))
}

async fn live_upload_terrain(
    backend: &LiveBackend,
    name: String,
    data: Vec<u8>,
    cancellation: CancellationToken,
) -> Result<String, String> {
    let (estate, assets) = {
        let client = lock(&backend.client);
        (client.estate(), client.assets())
    };
    estate.enable_live_mutations();
    let (sender, receiver) = tokio::sync::oneshot::channel();
    let sender = Arc::new(Mutex::new(Some(sender)));
    let callback = Arc::clone(&sender);
    let subscription = assets.subscribe_upload_progress(Arc::new(move |event| {
        let upload = event.upload();
        if upload.base.transferred == upload.base.size
            && let Some(sender) = lock(&callback).take()
        {
            let _ = sender.send(upload.base.success);
        }
    }));
    estate
        .upload_terrain(data, name)
        .map_err(|_| "Terrain upload request failed")?;
    let success = tokio::select! {value=receiver=>value.map_err(|_|"Terrain upload channel closed")?,()=tokio::time::sleep(Duration::from_mins(2))=>return Err("Timeout waiting for terrain file upload".into()),()=cancellation.cancelled()=>return Err("Terrain upload cancelled".into())};
    drop(subscription);
    if success {
        Ok("Terrain upload completed".into())
    } else {
        Err("Terrain upload failed".into())
    }
}
