syntax = "proto3";

option java_package = "com.soulfiremc.grpc.generated";
option java_multiple_files = true;

package soulfire.v1;

import "soulfire/common.proto";
import "soulfire/api_docs.proto";
import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/struct.proto";

// ============================================================================
// BotService - Minecraft Bot Control and Information Service
// ============================================================================
//
// The BotService provides comprehensive control and monitoring capabilities for
// Minecraft bots managed by the SoulFire server. It enables clients to:
//
// - Query bot status and live state (position, health, inventory, etc.)
// - Control bot movement (WASD, jump, sneak, sprint)
// - Control bot view direction (yaw/pitch rotation)
// - Manage inventory interactions (clicking slots, opening/closing containers)
// - Render the bot's point-of-view as images
// - Interact with server dialogs (Minecraft 1.21.6+)
// - Update bot-specific configuration
//
// All RPC methods require appropriate permissions checked via the user context.
// Most methods require the bot to be online (connected to a Minecraft server)
// for the operation to succeed.
// ============================================================================

// Request message for retrieving detailed information about a specific bot.
// Used by the GetBotInfo RPC to fetch a single bot's live state with full inventory data.
message BotInfoRequest {
  // The UUID of the SoulFire instance containing the bot.
  // Must be a valid UUID string format (e.g., "550e8400-e29b-41d4-a716-446655440000").
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot (Minecraft account UUID).
  // Must be a valid UUID string format.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Request message for listing all bots in an instance.
// Used by the GetBotList RPC to enumerate all configured bots.
message BotListRequest {
  // The UUID of the SoulFire instance to list bots from.
  // Must be a valid UUID string format.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Persistent operator intent for a bot.
enum BotDesiredState {
  BOT_DESIRED_STATE_UNSPECIFIED = 0;
  // The bot should remain disconnected.
  BOT_DESIRED_STATE_STOPPED = 1;
  // SoulFire should keep the bot connected according to its reconnect policy.
  BOT_DESIRED_STATE_RUNNING = 2;
}

// Current state of SoulFire's per-bot connection controller.
enum BotRuntimeState {
  BOT_RUNTIME_STATE_UNSPECIFIED = 0;
  BOT_RUNTIME_STATE_STOPPED = 1;
  BOT_RUNTIME_STATE_QUEUED = 2;
  BOT_RUNTIME_STATE_STARTING = 3;
  BOT_RUNTIME_STATE_RUNNING = 4;
  BOT_RUNTIME_STATE_RETRYING = 5;
  BOT_RUNTIME_STATE_STOPPING = 6;
  BOT_RUNTIME_STATE_FAILED = 7;
}

// Durable intent and current controller state for one configured bot.
message BotStatus {
  string profile_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  BotDesiredState desired_state = 2 [(google.api.field_behavior) = REQUIRED];
  BotRuntimeState runtime_state = 3 [(google.api.field_behavior) = REQUIRED];
  optional string last_error = 4;
  google.protobuf.Timestamp updated_at = 5 [(google.api.field_behavior) = REQUIRED];
}

// Aggregate bot state for an instance.
message BotFleetSummary {
  uint32 total_bots = 1 [(google.api.field_behavior) = REQUIRED];
  uint32 desired_bots = 2 [(google.api.field_behavior) = REQUIRED];
  uint32 online_bots = 3 [(google.api.field_behavior) = REQUIRED];
  uint32 starting_bots = 4 [(google.api.field_behavior) = REQUIRED];
  uint32 retrying_bots = 5 [(google.api.field_behavior) = REQUIRED];
  uint32 failed_bots = 6 [(google.api.field_behavior) = REQUIRED];
}

// Changes persistent desired state for a batch of configured bots.
message SetBotsDesiredStateRequest {
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  repeated string bot_ids = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  BotDesiredState desired_state = 3 [(google.api.field_behavior) = REQUIRED];
}

message SetBotsDesiredStateResponse {
  repeated BotStatus bots = 1 [(google.api.field_behavior) = REQUIRED];
}

// Reconnects a batch of bots and leaves their desired state set to running.
message RestartBotsRequest {
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  repeated string bot_ids = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

message RestartBotsResponse {
  repeated BotStatus bots = 1 [(google.api.field_behavior) = REQUIRED];
}

message WatchBotStatusesRequest {
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

message BotStatusSnapshot {
  repeated BotStatus bots = 1 [(google.api.field_behavior) = REQUIRED];
}

message WatchBotStatusesResponse {
  oneof event {
    BotStatusSnapshot snapshot = 1;
    BotStatus update = 2;
    string removed_bot_id = 3 [(soulfire.v1.api_field).format = "uuid"];
  }
}

// Fine-grained connection phase for a bot, more detailed than the is_online
// boolean. SPAWNED implies the player entity exists and live_state is available.
enum BotConnectionPhase {
  // Unknown phase (e.g. for a bot that has never been started).
  BOT_CONNECTION_PHASE_UNSPECIFIED = 0;
  // The bot has been created and is establishing its connection / logging in,
  // but the player has not spawned yet.
  BOT_CONNECTION_PHASE_CONNECTING = 1;
  // The bot is connected to the server (post-login) but has not fully spawned.
  BOT_CONNECTION_PHASE_CONNECTED = 2;
  // The bot's player has spawned; live_state reflects the player.
  BOT_CONNECTION_PHASE_SPAWNED = 3;
  // The bot's player is currently dead, awaiting respawn.
  BOT_CONNECTION_PHASE_DIED = 4;
  // The bot is not connected to a server.
  BOT_CONNECTION_PHASE_DISCONNECTED = 5;
}

// Entry representing a single bot in the bot list.
// Contains summary information suitable for display in a list view.
message BotListEntry {
  // The Minecraft profile UUID of the bot.
  // This uniquely identifies the bot within the instance.
  string profile_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // Whether the bot is currently connected to a Minecraft server.
  // When false, the bot exists in configuration but is not active.
  bool is_online = 2 [(google.api.field_behavior) = REQUIRED];

  // Live state of the bot if online and player data is available.
  // Will be absent if the bot is offline or the player hasn't fully spawned.
  // Note: For list views, inventory data is NOT included (too expensive).
  optional BotLiveState live_state = 3;

  // Network latency (ping) of the bot to the connected Minecraft server, in
  // milliseconds. Present only when the bot is online and the server has
  // reported a latency value for it.
  optional int32 ping_ms = 4;

  // Fine-grained connection phase. Where is_online is a coarse boolean, this
  // distinguishes connecting / connected / spawned / disconnected states.
  BotConnectionPhase connection_phase = 5 [(google.api.field_behavior) = REQUIRED];

  // Last known account (player) name of the bot, for convenient display in
  // list views without joining against the instance account configuration.
  // Absent if the name is unknown.
  optional string account_name = 6;

  // Persistent desired state and current controller state.
  BotStatus status = 7 [(google.api.field_behavior) = REQUIRED];
}

// Response containing the list of all bots in an instance.
message BotListResponse {
  // All bots configured in the instance, both online and offline.
  // The order is determined by the account configuration order.
  repeated BotListEntry bots = 1 [(google.api.field_behavior) = REQUIRED];
}

// Represents an item in an inventory slot.
// Used for both container slots and carried (cursor) items.
message InventorySlot {
  // The slot index within the container.
  // - For container menus: 0 to (total_slots - 1)
  // - For player inventory: 0-4 crafting, 5-8 armor, 9-35 main inventory, 36-44 hotbar, 45 offhand
  // - For carried items (cursor): -1 is used as a special indicator
  int32 slot = 1 [(google.api.field_behavior) = REQUIRED];

  // The Minecraft item identifier in namespaced format.
  // Examples: "minecraft:diamond_sword", "minecraft:stone", "minecraft:enchanted_book"
  string item_id = 2 [(google.api.field_behavior) = REQUIRED];

  // The number of items in this stack.
  // Range: 1 to the item's maximum stack size (typically 1, 16, or 64).
  int32 count = 3 [(google.api.field_behavior) = REQUIRED];

  // The custom display name of the item, if it has been renamed.
  // Only present when the item has a custom name component (e.g., renamed in an anvil).
  // Contains the plain text string, not JSON text components.
  optional string display_name = 4;

  // Base64-encoded square icon image for this item's inventory render.
  // The image is rendered server-side to match the item's resolved in-game appearance.
  // Static items are sent as PNG. Animated items may be sent as GIF.
  optional string icon_base64 = 5;

  // MIME type for the rendered item icon image.
  // Values: "image/png" or "image/gif".
  optional string icon_mime_type = 6;
}

// Real-time state of a connected bot.
// Contains position, health, inventory, and other live game data.
message BotLiveState {
  // X coordinate in the Minecraft world (east/west axis).
  // East is positive, West is negative.
  double x = 1 [(google.api.field_behavior) = REQUIRED];

  // Y coordinate in the Minecraft world (vertical axis).
  // Increases upward. Sea level is typically around 63.
  double y = 2 [(google.api.field_behavior) = REQUIRED];

  // Z coordinate in the Minecraft world (north/south axis).
  // South is positive, North is negative.
  double z = 3 [(google.api.field_behavior) = REQUIRED];

  // Vertical rotation (pitch) in degrees.
  // Range: -90 (looking straight up) to 90 (looking straight down).
  // 0 is looking at the horizon.
  float xRot = 4 [(google.api.field_behavior) = REQUIRED];

  // Horizontal rotation (yaw) in degrees.
  // Range: -180 to 180. 0 = South, 90 = West, -90 = East, +/-180 = North.
  float yRot = 5 [(google.api.field_behavior) = REQUIRED];

  // Current health points.
  // Range: 0 (dead) to max_health. 1 point = half a heart in the UI.
  float health = 6 [(google.api.field_behavior) = REQUIRED];

  // Maximum health points the player can have.
  // Default is 20 (10 hearts). Can be modified by effects or attributes.
  float max_health = 7 [(google.api.field_behavior) = REQUIRED];

  // Current hunger/food level.
  // Range: 0 (starving) to 20 (full). 1 point = half a drumstick in the UI.
  // Below 6, the player cannot sprint. At 0, health starts draining.
  int32 food_level = 8 [(google.api.field_behavior) = REQUIRED];

  // Current saturation level.
  // Range: 0 to food_level. Acts as a buffer before hunger depletes.
  // When saturation is above 0, hunger bar doesn't decrease while performing actions.
  float saturation_level = 9 [(google.api.field_behavior) = REQUIRED];

  // Inventory slots containing items (only non-empty slots are included).
  // For list views (GetBotList), this will be empty for performance.
  // For detail views (GetBotInfo), contains all non-empty slots from the player inventory.
  repeated InventorySlot inventory = 10;

  // Currently selected hotbar slot index.
  // Range: 0 to 8, corresponding to hotbar slots 1-9 in the UI.
  int32 selected_hotbar_slot = 11 [(google.api.field_behavior) = REQUIRED];

  // The dimension the player is currently in.
  // Format: namespaced identifier (e.g., "minecraft:overworld", "minecraft:the_nether", "minecraft:the_end").
  // Custom dimensions will use their server-defined namespace.
  string dimension = 12 [(google.api.field_behavior) = REQUIRED];

  // Player's current experience level (the number shown on the XP bar).
  // Range: 0 to theoretically unlimited (typically 0-30+ for enchanting).
  int32 experience_level = 13 [(google.api.field_behavior) = REQUIRED];

  // Progress towards the next experience level.
  // Range: 0.0 (just reached current level) to 1.0 (about to level up).
  float experience_progress = 14 [(google.api.field_behavior) = REQUIRED];

  // Skin texture hash for avatar rendering.
  // This is the hash extracted from the texture URL in the player's game profile.
  // Can be used with services like mc-heads.net: https://mc-heads.net/body/<hash>
  // Will be absent if the player has no custom skin or skin data is unavailable.
  optional string skin_texture_hash = 15;

  // Current game mode of the player.
  // Determines available actions (breaking blocks, flying, taking damage, etc.).
  GameMode game_mode = 16 [(google.api.field_behavior) = REQUIRED];
}

// Minecraft game modes that determine player capabilities.
enum GameMode {
  // Unknown or not yet determined game mode.
  GAME_MODE_UNSPECIFIED = 0;

  // Survival mode: resource gathering, health/hunger management, mob combat.
  GAME_MODE_SURVIVAL = 1;

  // Creative mode: unlimited resources, instant block breaking, flying, invulnerability.
  GAME_MODE_CREATIVE = 2;

  // Adventure mode: like survival but cannot break/place blocks without proper tools.
  GAME_MODE_ADVENTURE = 3;

  // Spectator mode: invisible, can fly through blocks, cannot interact with the world.
  GAME_MODE_SPECTATOR = 4;
}

// Response containing detailed information about a specific bot.
message BotInfoResponse {
  // Live state of the bot including full inventory data.
  // Will be absent if the bot is offline or player data is not available.
  // When present, includes complete inventory slot information.
  optional BotLiveState live_state = 2;
  // Persistent desired state and current controller state.
  BotStatus status = 3 [(google.api.field_behavior) = REQUIRED];
}

// Request to update a single configuration entry for a specific bot.
// This allows granular updates without replacing the entire configuration.
message BotUpdateConfigEntryRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot to update configuration for.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The settings namespace (e.g., plugin ID or "bot" for core bot settings).
  // Examples: "bot", "auto-reconnect", "chat-message-controller"
  string namespace = 3 [(google.api.field_behavior) = REQUIRED];

  // The key within the namespace to update.
  // Must be a valid setting key defined for that namespace.
  string key = 4 [(google.api.field_behavior) = REQUIRED];

  // The new value to set for this configuration entry.
  // Must be a valid protobuf Value (null, number, string, bool, list, or struct).
  google.protobuf.Value value = 5 [(google.api.field_behavior) = REQUIRED];
}

// Response for bot configuration entry update.
// Empty on success; errors are returned via gRPC status.
message BotUpdateConfigEntryResponse {
}

// Request to render the bot's point-of-view as an image.
// Uses software rendering to generate a PNG image of what the bot "sees".
message BotRenderPovRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot to render POV for.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // Desired image width in pixels.
  // Default: 854 (if 0 or not provided).
  // Maximum: 1920 (values above this are clamped).
  // Minimum: 1 (values below this are clamped).
  int32 width = 3 [(google.api.field_behavior) = REQUIRED];

  // Desired image height in pixels.
  // Default: 480 (if 0 or not provided).
  // Maximum: 1080 (values above this are clamped).
  // Minimum: 1 (values below this are clamped).
  int32 height = 4 [(google.api.field_behavior) = REQUIRED];

  // Maximum render distance in blocks.
  // Default: the bot's current render distance setting.
  // Maximum: 2048 (values above this are clamped).
  optional int32 max_distance = 5;

  // Vertical field of view in degrees.
  // Default: the renderer default FOV.
  // Valid range: 1.0 to 179.0.
  optional double fov = 6;

  // Optional camera X coordinate.
  // Defaults to the bot's current eye position.
  optional double camera_x = 7;

  // Optional camera Y coordinate.
  // Defaults to the bot's current eye position.
  optional double camera_y = 8;

  // Optional camera Z coordinate.
  // Defaults to the bot's current eye position.
  optional double camera_z = 9;

  // Optional yaw in degrees.
  // Defaults to the bot's current yaw.
  optional float y_rot = 10;

  // Optional pitch in degrees.
  // Defaults to the bot's current pitch.
  optional float x_rot = 11;

  // Whether to render the HUD overlay.
  // Default: true.
  optional bool include_hud = 12;

  // Whether to render first-person hands and held items.
  // Default: true.
  optional bool include_hands = 13;

  // Whether to include renderer debug counters in the response.
  // Default: false.
  bool include_debug_trace = 14;
}

// Response containing the rendered POV image.
message BotRenderPovResponse {
  // Base64-encoded PNG image data.
  // Decode with standard Base64 decoder to get raw PNG bytes.
  // See metadata for the effective render settings used.
  string image_base64 = 1 [(google.api.field_behavior) = REQUIRED];

  // MIME type for image_base64.
  string image_mime_type = 2 [(google.api.field_behavior) = REQUIRED];

  // Effective render settings used for this frame.
  BotRenderPovMetadata metadata = 3 [(google.api.field_behavior) = REQUIRED];

  // Optional debug counters and samples.
  // Present only when include_debug_trace is true.
  optional BotRenderPovDebugTrace debug_trace = 4;
}

// Effective settings used for a rendered POV frame.
message BotRenderPovMetadata {
  // Final image width in pixels after defaults and clamping.
  int32 width = 1;

  // Final image height in pixels after defaults and clamping.
  int32 height = 2;

  // Field of view in degrees.
  double fov = 3;

  // Maximum render distance in blocks.
  int32 max_distance = 4;

  // Camera X coordinate.
  double camera_x = 5;

  // Camera Y coordinate.
  double camera_y = 6;

  // Camera Z coordinate.
  double camera_z = 7;

  // Camera yaw in degrees.
  float y_rot = 8;

  // Camera pitch in degrees.
  float x_rot = 9;

  // Whether the HUD overlay was rendered.
  bool included_hud = 10;

  // Whether first-person hands and held items were rendered.
  bool included_hands = 11;
}

// Renderer debug counters and samples for a single POV render.
message BotRenderPovDebugTrace {
  int64 render_id = 1;
  int64 chunks_considered = 2;
  int64 chunks_loaded = 3;
  int64 sections_visible = 4;
  int64 sections_meshed = 5;
  int64 section_cache_hits = 6;
  int64 section_cache_misses = 7;
  int64 block_quads = 8;
  int64 entities_considered = 9;
  int64 entities_visible = 10;
  int64 billboards = 11;
  int64 weather_billboards = 12;
  int64 vanilla_block_geometry_hits = 13;
  int64 vanilla_block_geometry_fallbacks = 14;
  int64 resource_block_geometry_fallbacks = 15;
  int64 inventory_icon_ignored = 16;
  int64 unknown_render_pipelines = 17;
  int64 runtime_texture_mirror_skips = 18;
  int64 opaque_triangles = 19;
  int64 cutout_triangles = 20;
  int64 translucent_triangles = 21;
  int64 world_collect_nanos = 22;
  int64 dynamic_collect_nanos = 23;
  int64 raster_nanos = 24;
  int64 total_nanos = 25;
  int64 text_submissions = 26;
  repeated BotRenderPovTextSubmission text_samples = 27;
  repeated string notable_events = 28;
  repeated string detailed_failures = 29;
}

// Sample of text submitted while rendering a debug frame.
message BotRenderPovTextSubmission {
  string source = 1;
  string text = 2;
  bool shadow = 3;
  string display_mode = 4;
  int32 light = 5;
  string color = 6;
  string background_color = 7;
  string outline_color = 8;
}

// Request to continuously render a bot's point of view.
message BotWatchPovRequest {
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  int32 width = 3;
  int32 height = 4;
  optional int32 max_distance = 5;
  optional double fov = 6;
  optional double camera_x = 7;
  optional double camera_y = 8;
  optional double camera_z = 9;
  optional float y_rot = 10;
  optional float x_rot = 11;
  optional bool include_hud = 12;
  optional bool include_hands = 13;
  bool include_debug_trace = 14;

  // Target interval between frames. Defaults to 1000 ms and is clamped to
  // 100-60000 ms. Frames are dropped while the transport is not ready.
  int32 interval_ms = 15;
}

// A rendered frame from a POV stream.
message BotPovFrame {
  uint64 sequence = 1 [(google.api.field_behavior) = REQUIRED];
  google.protobuf.Timestamp captured_at = 2 [(google.api.field_behavior) = REQUIRED];
  BotRenderPovResponse render = 3 [(google.api.field_behavior) = REQUIRED];

  // Number of scheduled frames skipped since the previous delivered frame
  // because the client or transport could not accept more data.
  uint64 dropped_before = 4 [(google.api.field_behavior) = REQUIRED];
}

// Request for bounded, top-down world data suitable for map viewers.
message BotWorldMapRequest {
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // Center coordinates. Each defaults to the bot's current block position.
  optional int32 center_x = 3;
  optional int32 center_z = 4;

  // Radius in blocks. Defaults to 64 and is clamped to 1-256.
  int32 radius = 5;

  // Distance between sampled columns. Defaults to 1 and is clamped to 1-16.
  int32 sample_step = 6;

  // Include nearby entity overlays in the returned snapshot.
  bool include_entities = 7;
}

// One sampled surface column in a top-down world map.
message BotWorldMapColumn {
  int32 x = 1 [(google.api.field_behavior) = REQUIRED];
  int32 z = 2 [(google.api.field_behavior) = REQUIRED];
  bool loaded = 3 [(google.api.field_behavior) = REQUIRED];
  optional int32 surface_y = 4;
  optional string block_id = 5;
  optional string biome_id = 6;
  int32 sky_light = 7 [(google.api.field_behavior) = REQUIRED];
  int32 block_light = 8 [(google.api.field_behavior) = REQUIRED];
}

// Entity overlay for a top-down world map.
message BotWorldMapEntity {
  string entity_id = 1 [(google.api.field_behavior) = REQUIRED];
  string entity_type = 2 [(google.api.field_behavior) = REQUIRED];
  string display_name = 3 [(google.api.field_behavior) = REQUIRED];
  double x = 4 [(google.api.field_behavior) = REQUIRED];
  double y = 5 [(google.api.field_behavior) = REQUIRED];
  double z = 6 [(google.api.field_behavior) = REQUIRED];
  float yaw = 7 [(google.api.field_behavior) = REQUIRED];
}

// A bounded, rectangular top-down world sample.
message BotWorldMapResponse {
  string dimension = 1 [(google.api.field_behavior) = REQUIRED];
  int32 center_x = 2 [(google.api.field_behavior) = REQUIRED];
  int32 center_z = 3 [(google.api.field_behavior) = REQUIRED];
  int32 radius = 4 [(google.api.field_behavior) = REQUIRED];
  int32 sample_step = 5 [(google.api.field_behavior) = REQUIRED];
  int32 min_y = 6 [(google.api.field_behavior) = REQUIRED];
  int32 max_y = 7 [(google.api.field_behavior) = REQUIRED];
  int64 world_revision = 8 [(google.api.field_behavior) = REQUIRED];
  google.protobuf.Timestamp sampled_at = 9 [(google.api.field_behavior) = REQUIRED];
  repeated BotWorldMapColumn columns = 10;
  repeated BotWorldMapEntity entities = 11;
}

// ============================================================================
// Inventory Management Messages
// ============================================================================

// Request to perform a click action on an inventory slot.
// Simulates the various mouse click interactions available in Minecraft inventory UIs.
message BotInventoryClickRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot performing the inventory action.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The slot index to click.
  // Valid range depends on the currently open container.
  // Special value: -999 for clicking outside the inventory (dropping items).
  int32 slot = 3 [(google.api.field_behavior) = REQUIRED];

  // The type of click to perform.
  // Different click types have different effects on items.
  ClickType click_type = 4 [(google.api.field_behavior) = REQUIRED];

  // For SWAP_HOTBAR click type: which hotbar slot to swap with.
  // Range: 0-8 (corresponding to hotbar slots 1-9).
  // Ignored for other click types.
  int32 hotbar_slot = 5 [(google.api.field_behavior) = REQUIRED];
}

// Types of inventory click actions available in Minecraft.
enum ClickType {
  // Invalid/unspecified click type. Will result in an error.
  CLICK_TYPE_UNSPECIFIED = 0;

  // Left mouse click on a slot.
  // - Empty cursor + item in slot: Pick up entire stack to cursor
  // - Item on cursor + empty slot: Place entire stack in slot
  // - Item on cursor + same item in slot: Combine stacks (up to max stack size)
  // - Item on cursor + different item in slot: Swap cursor and slot items
  LEFT_CLICK = 1;

  // Right mouse click on a slot.
  // - Empty cursor + item in slot: Pick up half the stack (rounded up)
  // - Item on cursor + empty slot: Place one item from cursor
  // - Item on cursor + same item in slot: Place one item from cursor
  // - Item on cursor + different item in slot: Swap cursor and slot items
  RIGHT_CLICK = 2;

  // Shift + left click on a slot.
  // Quick-moves the item stack to the opposite inventory section.
  // - From player inventory to container (chest, furnace, etc.)
  // - From container to player inventory
  // - Between hotbar and main inventory
  SHIFT_LEFT_CLICK = 3;

  // Drop one item from the slot (Q key while hovering over slot).
  // Drops a single item from the stack, leaving the rest.
  DROP_ONE = 4;

  // Drop entire stack from the slot (Ctrl+Q while hovering over slot).
  // Drops all items from the stack at once.
  DROP_ALL = 5;

  // Swap slot contents with a hotbar slot (number keys 1-9).
  // Requires hotbar_slot field to specify which hotbar slot (0-8).
  // Works regardless of what's in the cursor.
  SWAP_HOTBAR = 6;

  // Middle mouse click (creative mode clone).
  // In creative mode, clones the item to cursor without affecting the slot.
  // No effect in survival mode.
  MIDDLE_CLICK = 7;
}

// Response for inventory click action.
message BotInventoryClickResponse {
  // Whether the click action was successfully sent to the server.
  // Note: This indicates the action was sent, not that the server accepted it.
  bool success = 1 [(google.api.field_behavior) = REQUIRED];

  // Error message if success is false.
  // Possible errors: "Invalid click type"
  optional string error = 2;
}

// Request to get the current state of the bot's open container/inventory.
message BotInventoryStateRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot to get inventory state for.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Describes how a region of slots should be rendered in the UI.
// Different region types have different visual treatments.
enum SlotRegionType {
  // Regular interactive slots that can accept most items.
  SLOT_REGION_NORMAL = 0;

  // Output-only slots (crafting result, furnace output).
  // Items can only be taken out, not placed in.
  SLOT_REGION_OUTPUT = 1;

  // Display-only slots (lectern book display, some special UIs).
  // May not allow any direct interaction.
  SLOT_REGION_DISPLAY = 2;

  // Player hotbar slots.
  // Should show selection indicator for the currently selected slot.
  SLOT_REGION_HOTBAR = 3;

  // Armor slots in player inventory.
  // May display placeholder armor icons when empty.
  SLOT_REGION_ARMOR = 4;
}

// A group of slots that should be rendered together in the UI.
// Defines the layout grid for a section of the inventory.
message SlotRegion {
  // Unique identifier for this region within the container.
  // Examples: "container", "player_inventory", "player_hotbar", "crafting_output"
  string id = 1 [(google.api.field_behavior) = REQUIRED];

  // Display label for this region.
  // Examples: "Chest", "Inventory", "Hotbar", "Crafting Grid"
  string label = 2 [(google.api.field_behavior) = REQUIRED];

  // First slot index belonging to this region.
  // Slots are numbered sequentially from 0 across all regions.
  int32 start_index = 3 [(google.api.field_behavior) = REQUIRED];

  // Total number of slots in this region.
  int32 slot_count = 4 [(google.api.field_behavior) = REQUIRED];

  // Number of columns for grid layout.
  // Common values: 9 (standard inventory width), 3 (dispenser), 1 (armor column)
  int32 columns = 5 [(google.api.field_behavior) = REQUIRED];

  // The type of region, affecting rendering behavior.
  SlotRegionType type = 6 [(google.api.field_behavior) = REQUIRED];
}

// A clickable button in a container menu.
// Used for special interactions like enchantment selection, stonecutter recipes, trade offers, etc.
message ContainerButton {
  // Button ID to send to the server when clicking.
  // The meaning depends on the container type.
  int32 button_id = 1 [(google.api.field_behavior) = REQUIRED];

  // Display label for the button.
  // Examples: "Enchant Slot 1", "Recipe 5", "8x Emerald -> 1x Diamond Pickaxe"
  string label = 2 [(google.api.field_behavior) = REQUIRED];

  // Optional item ID for displaying an icon.
  // Example: "minecraft:enchanted_book", "minecraft:stone_bricks"
  optional string icon_item_id = 3;

  // Optional tooltip/description text with additional details.
  // Examples: "Requires 3 lapis, 30 levels", "Out of stock"
  optional string description = 4;

  // Whether the button is currently disabled (cannot be clicked).
  // Reasons vary by container: missing materials, out of stock, insufficient levels, etc.
  bool disabled = 5 [(google.api.field_behavior) = REQUIRED];

  // Whether this button is currently selected/active.
  // Used for toggle-style buttons or to show current selection.
  bool selected = 6 [(google.api.field_behavior) = REQUIRED];
}

// Text input field in a container menu.
// Currently only used by the anvil for item renaming.
message ContainerTextInput {
  // Field identifier for the SetContainerText RPC.
  // Currently supported: "item_name" (anvil rename)
  string id = 1 [(google.api.field_behavior) = REQUIRED];

  // Display label for the input field.
  string label = 2 [(google.api.field_behavior) = REQUIRED];

  // Current text value in the field.
  // For anvil: the item's current custom name, or empty if none.
  string current_value = 3 [(google.api.field_behavior) = REQUIRED];

  // Maximum allowed character length.
  // For anvil: 50 characters.
  // Value of 0 means no limit.
  int32 max_length = 4 [(google.api.field_behavior) = REQUIRED];

  // Placeholder text shown when field is empty.
  string placeholder = 5 [(google.api.field_behavior) = REQUIRED];
}

// A page from a book, used for lectern display.
message BookPage {
  // Zero-indexed page number.
  int32 page_number = 1 [(google.api.field_behavior) = REQUIRED];

  // Page content text.
  // May contain Minecraft formatting codes (section symbols).
  string content = 2 [(google.api.field_behavior) = REQUIRED];
}

// Complete layout description for an open container menu.
// Provides all information needed to render the container UI.
message ContainerLayout {
  // Container title/name displayed at the top.
  // Examples: "Chest", "Crafting Table", "Inventory", custom container names.
  string title = 1 [(google.api.field_behavior) = REQUIRED];

  // Ordered list of slot regions defining the container layout.
  // Regions are listed in rendering order (typically container slots first, then player inventory).
  repeated SlotRegion regions = 2;

  // Total number of slots in the container.
  // Sum of all region slot_counts.
  int32 total_slots = 3 [(google.api.field_behavior) = REQUIRED];

  // Available action buttons for this container.
  // Present for: stonecutter (recipes), enchanting table (3 enchant options),
  // loom (patterns), villager trading (trade offers), beacon (effects), etc.
  repeated ContainerButton buttons = 4;

  // Container type identifier for client-specific rendering.
  // Values: "inventory", "chest", "dispenser", "hopper", "furnace", "crafting",
  //         "anvil", "enchanting", "brewing", "beacon", "shulker", "grindstone",
  //         "stonecutter", "loom", "cartography", "smithing", "merchant",
  //         "crafter", "lectern", "generic"
  string container_type = 5 [(google.api.field_behavior) = REQUIRED];

  // Text input fields available in this container.
  // Currently only populated for anvil menus.
  repeated ContainerTextInput text_inputs = 6;

  // Book pages for lectern containers.
  // Contains all pages of the book on the lectern.
  repeated BookPage book_pages = 7;

  // Current page being displayed (for lectern).
  // Zero-indexed into book_pages.
  int32 current_book_page = 8 [(google.api.field_behavior) = REQUIRED];
}

// Response containing the current inventory/container state.
message BotInventoryStateResponse {
  // Layout information describing the container structure.
  ContainerLayout layout = 1 [(google.api.field_behavior) = REQUIRED];

  // All slots containing items (empty slots are omitted).
  repeated InventorySlot slots = 2 [(google.api.field_behavior) = REQUIRED];

  // Item currently held on the cursor (being moved).
  // Absent if cursor is empty.
  optional InventorySlot carried_item = 3;

  // Currently selected hotbar slot index (0-8).
  int32 selected_hotbar_slot = 4 [(google.api.field_behavior) = REQUIRED];
}

// Request to close the currently open container.
message BotCloseContainerRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot to close container for.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Response for container close action.
message BotCloseContainerResponse {
  // Whether the close action was successful.
  bool success = 1 [(google.api.field_behavior) = REQUIRED];
}

// Request to open the player's inventory screen.
// This sends a packet to the server to open the inventory UI.
message BotOpenInventoryRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot to open inventory for.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Response for inventory open action.
message BotOpenInventoryResponse {
  // Whether the open inventory action was successful.
  bool success = 1 [(google.api.field_behavior) = REQUIRED];
}

// ============================================================================
// Mouse Click Messages
// ============================================================================

// Request to simulate a mouse click action in the game world.
// Used for attacking entities, breaking blocks, using items, and interacting with blocks/entities.
message BotMouseClickRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot performing the click.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // Which mouse button to simulate clicking.
  MouseButton button = 3 [(google.api.field_behavior) = REQUIRED];
}

// Mouse buttons for world interaction.
enum MouseButton {
  // Invalid/unspecified button. Will result in an error.
  MOUSE_BUTTON_UNSPECIFIED = 0;

  // Left mouse button.
  // Actions: Attack entity (if looking at one), start breaking block (if looking at one).
  LEFT_BUTTON = 1;

  // Right mouse button.
  // Actions: Use held item, interact with entity (if looking at one),
  //          interact with block (if looking at one, e.g., open chest, press button).
  RIGHT_BUTTON = 2;
}

// Response for mouse click action.
message BotMouseClickResponse {
  // Whether the click action was successfully performed.
  bool success = 1 [(google.api.field_behavior) = REQUIRED];

  // Error message if success is false.
  // Possible errors: "Invalid mouse button"
  optional string error = 2;
}

// Request to click a container-specific action button.
// Used for stonecutter recipes, enchantment selection, villager trades, beacon effects, etc.
message BotContainerButtonClickRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot clicking the button.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The button ID to click.
  // Valid values depend on container type:
  // - Stonecutter: 0-99 (recipe index)
  // - Enchanting table: 0-2 (enchantment slot)
  // - Loom: 0-99 (pattern index)
  // - Villager: 0 to (offer count - 1)
  // - Beacon: mob effect registry ID, -1 for confirm
  // - Crafter: 0-8 (slot toggle index)
  // - Lectern: 1=prev page, 2=next page, 3=take book
  int32 button_id = 3 [(google.api.field_behavior) = REQUIRED];
}

// Response for container button click action.
message BotContainerButtonClickResponse {
  // Whether the button click was successfully queued.
  bool success = 1 [(google.api.field_behavior) = REQUIRED];

  // Error message if success is false.
  // Possible errors: "Invalid button ID for this container type"
  optional string error = 2;
}

// Request to set text in a container text input field.
// Currently only supports anvil item renaming.
message BotSetContainerTextRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot setting the text.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // Which field to set the text for.
  // Currently supported: "item_name" (anvil rename)
  string field_id = 3 [(google.api.field_behavior) = REQUIRED];

  // The text value to set.
  // For anvil: max 50 characters for the item name.
  string text = 4 [(google.api.field_behavior) = REQUIRED];
}

// Response for container text input action.
message BotSetContainerTextResponse {
  // Whether the text was successfully set.
  bool success = 1 [(google.api.field_behavior) = REQUIRED];

  // Error message if success is false.
  // Possible errors: "Unsupported container or field ID: <type>/<field_id>"
  optional string error = 2;
}

// Request to change the selected hotbar slot.
message BotSetHotbarSlotRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot to change hotbar selection for.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // Hotbar slot index to select.
  // Range: 0-8 (corresponding to slots 1-9 in the UI).
  int32 slot = 3 [(google.api.field_behavior) = REQUIRED];
}

// Response for hotbar slot selection.
message BotSetHotbarSlotResponse {
  // Whether the hotbar slot was successfully changed.
  bool success = 1 [(google.api.field_behavior) = REQUIRED];

  // Error message if success is false.
  // Possible errors: "Hotbar slot must be between 0 and 8, got: <slot>",
  //                  "Player is not available"
  optional string error = 2;
}

// ============================================================================
// Movement Control Messages
// Control bot movement (WASD, jump, sneak, sprint)
// ============================================================================

// Request to update the bot's movement state.
// Each field is optional to allow partial updates - only specified fields are changed.
// The movement state persists until explicitly changed or reset.
message BotSetMovementStateRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot to control.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // W key state - move forward.
  // true = pressing W, false = not pressing W
  optional bool forward = 3;

  // S key state - move backward.
  // true = pressing S, false = not pressing S
  optional bool backward = 4;

  // A key state - strafe left.
  // true = pressing A, false = not pressing A
  optional bool left = 5;

  // D key state - strafe right.
  // true = pressing D, false = not pressing D
  optional bool right = 6;

  // Space key state - jump.
  // true = pressing Space, false = not pressing Space
  // When held, the bot will jump repeatedly.
  optional bool jump = 7;

  // Shift key state - sneak/crouch.
  // true = sneaking, false = not sneaking
  // While sneaking: slower movement, won't fall off edges, crouching animation.
  optional bool sneak = 8;

  // Ctrl key state - sprint.
  // true = sprinting, false = not sprinting
  // Requires forward movement and sufficient hunger (food level >= 6).
  optional bool sprint = 9;
}

// Response for movement state update.
message BotSetMovementStateResponse {
  // Whether the movement state was successfully updated.
  bool success = 1 [(google.api.field_behavior) = REQUIRED];

  // Error message if success is false.
  optional string error = 2;
}

// Request to reset all movement to stopped state.
// Equivalent to releasing all movement keys (WASD, Space, Shift, Ctrl).
message BotResetMovementRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot to stop movement for.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Response for movement reset.
message BotResetMovementResponse {
  // Whether the movement was successfully reset.
  bool success = 1 [(google.api.field_behavior) = REQUIRED];

  // Error message if success is false.
  optional string error = 2;
}

// ============================================================================
// Rotation Control Messages
// Control bot view direction (yaw/pitch)
// ============================================================================

// Request to set the bot's view rotation (look direction).
// Used to aim the bot's view at specific targets or directions.
message BotSetRotationRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot to rotate.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // Horizontal rotation angle in degrees (yaw).
  // Range: -180 to 180 (values outside this range are normalized).
  // Direction mapping: 0 = South, 90 = West, -90 = East, +/-180 = North.
  float yaw = 3 [(google.api.field_behavior) = REQUIRED];

  // Vertical rotation angle in degrees (pitch).
  // Range: -90 to 90 (values outside this range are clamped).
  // Direction mapping: -90 = looking up, 0 = horizon, 90 = looking down.
  float pitch = 4 [(google.api.field_behavior) = REQUIRED];
}

// Response for rotation update.
message BotSetRotationResponse {
  // Whether the rotation was successfully set.
  bool success = 1 [(google.api.field_behavior) = REQUIRED];

  // Error message if success is false.
  // Possible errors: "Player is not available"
  optional string error = 2;
}

// ============================================================================
// Server Dialog Messages (Minecraft 1.21.6+)
// Dialogs are server-sent UI screens that can display information and gather input.
// They were introduced in Minecraft 1.21.6 as a way for servers to create custom UIs.
// ============================================================================

// Dialog types matching Minecraft's dialog registry.
// Determines the overall structure and behavior of the dialog.
enum DialogType {
  // Unknown or unrecognized dialog type.
  DIALOG_TYPE_UNSPECIFIED = 0;

  // Notice dialog with a single action button in the footer.
  // Used for informational messages that require acknowledgment.
  DIALOG_TYPE_NOTICE = 1;

  // Confirmation dialog with Yes/No buttons in the footer.
  // Used for binary choices.
  DIALOG_TYPE_CONFIRMATION = 2;

  // Multi-action dialog with a scrollable list of action buttons.
  // Used for menus with multiple options.
  DIALOG_TYPE_MULTI_ACTION = 3;

  // Server links dialog displaying clickable links.
  // Used to show server website, Discord, etc.
  DIALOG_TYPE_SERVER_LINKS = 4;

  // Dialog list with buttons linking to other dialogs.
  // Used for hierarchical menu navigation.
  DIALOG_TYPE_DIALOG_LIST = 5;
}

// Body element that can appear in a dialog.
// Dialogs can contain a mix of text and item displays.
message DialogBodyElement {
  oneof element {
    // Plain text message element.
    DialogPlainMessage plain_message = 1;

    // Item display element (shows a Minecraft item).
    DialogItem item = 2;
  }
}

// Plain text message in a dialog body.
message DialogPlainMessage {
  // Text content, typically a JSON text component string.
  // May contain formatting, colors, click events, etc.
  string contents = 1 [(google.api.field_behavior) = REQUIRED];

  // Display width in pixels.
  // Range: 1-1024, default: 200.
  int32 width = 2 [(google.api.field_behavior) = REQUIRED];
}

// Item display element in a dialog body.
message DialogItem {
  // Minecraft item identifier.
  // Example: "minecraft:diamond", "minecraft:netherite_sword"
  string item_id = 1 [(google.api.field_behavior) = REQUIRED];

  // Item count/stack size to display.
  int32 count = 2 [(google.api.field_behavior) = REQUIRED];

  // Optional description text below the item.
  optional string description = 3;

  // Whether to show the item frame decoration around the item.
  // Default: true.
  bool show_decoration = 4 [(google.api.field_behavior) = REQUIRED];

  // Whether to show the item tooltip on hover.
  // Default: true.
  bool show_tooltip = 5 [(google.api.field_behavior) = REQUIRED];

  // Display width in pixels.
  // Range: 1-256, default: 16.
  int32 width = 6 [(google.api.field_behavior) = REQUIRED];

  // Display height in pixels.
  // Range: 1-256, default: 16.
  int32 height = 7 [(google.api.field_behavior) = REQUIRED];
}

// Input control that can appear in a dialog.
// Allows dialogs to gather user input before submission.
message DialogInput {
  oneof input {
    // Text input field (single or multi-line).
    DialogTextInput text = 1;

    // Boolean checkbox input.
    DialogBooleanInput boolean = 2;

    // Dropdown/combo box for selecting one option.
    DialogSingleOptionInput single_option = 3;

    // Slider for selecting a number in a range.
    DialogNumberRangeInput number_range = 4;
  }
}

// Text input field in a dialog.
message DialogTextInput {
  // Identifier used when submitting the dialog.
  string key = 1 [(google.api.field_behavior) = REQUIRED];

  // Display label for the field.
  string label = 2 [(google.api.field_behavior) = REQUIRED];

  // Display width in pixels.
  // Range: 1-1024, default: 200.
  int32 width = 3 [(google.api.field_behavior) = REQUIRED];

  // Whether to show the label.
  // Default: true.
  bool label_visible = 4 [(google.api.field_behavior) = REQUIRED];

  // Initial/default value.
  string initial = 5 [(google.api.field_behavior) = REQUIRED];

  // Maximum character length.
  // Default: 32.
  int32 max_length = 6 [(google.api.field_behavior) = REQUIRED];

  // Whether to allow multi-line input.
  bool multiline = 7 [(google.api.field_behavior) = REQUIRED];

  // Maximum lines for multiline input.
  int32 multiline_max_lines = 8 [(google.api.field_behavior) = REQUIRED];

  // Display height for multiline input in pixels.
  int32 multiline_height = 9 [(google.api.field_behavior) = REQUIRED];
}

// Boolean checkbox input in a dialog.
message DialogBooleanInput {
  // Identifier used when submitting the dialog.
  string key = 1 [(google.api.field_behavior) = REQUIRED];

  // Display label for the checkbox.
  string label = 2 [(google.api.field_behavior) = REQUIRED];

  // Initial checked state.
  // Default: false.
  bool initial = 3 [(google.api.field_behavior) = REQUIRED];

  // String value to submit when checked.
  // Default: "true".
  string on_true = 4 [(google.api.field_behavior) = REQUIRED];

  // String value to submit when unchecked.
  // Default: "false".
  string on_false = 5 [(google.api.field_behavior) = REQUIRED];
}

// Single option selection input (dropdown/combo box) in a dialog.
message DialogSingleOptionInput {
  // Identifier used when submitting the dialog.
  string key = 1 [(google.api.field_behavior) = REQUIRED];

  // Display label for the dropdown.
  string label = 2 [(google.api.field_behavior) = REQUIRED];

  // Whether to show the label.
  // Default: true.
  bool label_visible = 3 [(google.api.field_behavior) = REQUIRED];

  // Display width in pixels.
  // Range: 1-1024, default: 200.
  int32 width = 4 [(google.api.field_behavior) = REQUIRED];

  // Available options to choose from.
  repeated DialogOption options = 5;

  // ID of the initially selected option.
  string initial_option_id = 6 [(google.api.field_behavior) = REQUIRED];
}

// An option in a single-option input.
message DialogOption {
  // Option identifier (submitted value).
  string id = 1 [(google.api.field_behavior) = REQUIRED];

  // Display text for the option.
  // If empty, the id is used for display.
  string display = 2 [(google.api.field_behavior) = REQUIRED];
}

// Number range slider input in a dialog.
message DialogNumberRangeInput {
  // Identifier used when submitting the dialog.
  string key = 1 [(google.api.field_behavior) = REQUIRED];

  // Display label for the slider.
  string label = 2 [(google.api.field_behavior) = REQUIRED];

  // Translation key for formatting the label with the current value.
  string label_format = 3 [(google.api.field_behavior) = REQUIRED];

  // Display width in pixels.
  // Range: 1-1024, default: 200.
  int32 width = 4 [(google.api.field_behavior) = REQUIRED];

  // Minimum value (left side of slider).
  double start = 5 [(google.api.field_behavior) = REQUIRED];

  // Maximum value (right side of slider).
  double end = 6 [(google.api.field_behavior) = REQUIRED];

  // Step size for discrete values.
  // If 0, continuous values are allowed.
  double step = 7 [(google.api.field_behavior) = REQUIRED];

  // Initial/default value.
  double initial = 8 [(google.api.field_behavior) = REQUIRED];
}

// Action that can be triggered by a dialog button.
message DialogAction {
  oneof action {
    // Open a URL in the browser.
    DialogOpenUrlAction open_url = 1;

    // Run a chat command.
    DialogRunCommandAction run_command = 2;

    // Suggest a command (put in chat box without sending).
    DialogSuggestCommandAction suggest_command = 3;

    // Copy text to clipboard.
    DialogCopyToClipboardAction copy_to_clipboard = 4;

    // Show another dialog.
    DialogShowDialogAction show_dialog = 5;

    // Custom server-defined action.
    DialogCustomAction custom = 6;

    // Run a command with input placeholders.
    DialogDynamicRunCommandAction dynamic_run_command = 7;

    // Custom action with additional data.
    DialogDynamicCustomAction dynamic_custom = 8;
  }
}

// Action to open a URL in the user's browser.
message DialogOpenUrlAction {
  // The URL to open.
  string url = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uri"];
}

// Action to execute a chat command.
message DialogRunCommandAction {
  // The command to run (including the leading /).
  string command = 1 [(google.api.field_behavior) = REQUIRED];
}

// Action to suggest a command (place in chat input).
message DialogSuggestCommandAction {
  // The command to suggest.
  string command = 1 [(google.api.field_behavior) = REQUIRED];
}

// Action to copy text to the clipboard.
message DialogCopyToClipboardAction {
  // The text to copy.
  string value = 1 [(google.api.field_behavior) = REQUIRED];
}

// Action to display another dialog.
message DialogShowDialogAction {
  // Dialog ID to show (references a registered dialog).
  string dialog_id = 1 [(google.api.field_behavior) = REQUIRED];
}

// Custom server-defined action.
message DialogCustomAction {
  // Action identifier known to the server.
  string id = 1 [(google.api.field_behavior) = REQUIRED];

  // Optional JSON payload for the action.
  string payload = 2;
}

// Dynamic command action with input value substitution.
message DialogDynamicRunCommandAction {
  // Command template with placeholders for input values.
  // Placeholders are replaced with user input before execution.
  string template = 1 [(google.api.field_behavior) = REQUIRED];
}

// Dynamic custom action with additional NBT data.
message DialogDynamicCustomAction {
  // Custom action identifier.
  string id = 1 [(google.api.field_behavior) = REQUIRED];

  // Additional NBT data as JSON string.
  string additions = 2 [(google.api.field_behavior) = REQUIRED];
}

// Button that can appear in a dialog.
message DialogButton {
  // Button text (may be a JSON text component).
  string label = 1 [(google.api.field_behavior) = REQUIRED];

  // Optional tooltip shown on hover.
  optional string tooltip = 2;

  // Button width in pixels.
  // Range: 1-1024, default: 150.
  int32 width = 3 [(google.api.field_behavior) = REQUIRED];

  // Action to perform when clicked.
  optional DialogAction action = 4;
}

// Behavior after a dialog action is triggered.
enum DialogAfterAction {
  // Unspecified, use dialog default.
  DIALOG_AFTER_ACTION_UNSPECIFIED = 0;

  // Close the dialog after the action.
  // This is the default behavior.
  DIALOG_AFTER_ACTION_CLOSE = 1;

  // Keep the dialog open after the action.
  DIALOG_AFTER_ACTION_NONE = 2;

  // Show a waiting/loading screen while action processes.
  DIALOG_AFTER_ACTION_WAIT_FOR_RESPONSE = 3;
}

// Complete server dialog definition.
// Contains all information needed to render and interact with a dialog.
message ServerDialog {
  // Dialog identifier from the server.
  // Format: namespaced identifier (e.g., "myserver:welcome_dialog").
  string id = 1 [(google.api.field_behavior) = REQUIRED];

  // The type of dialog, determining overall structure.
  DialogType type = 2 [(google.api.field_behavior) = REQUIRED];

  // Dialog title (may be a JSON text component).
  string title = 3 [(google.api.field_behavior) = REQUIRED];

  // Button label when shown as an external/minimized button.
  optional string external_title = 4;

  // Body elements (text and items) displayed in the dialog.
  repeated DialogBodyElement body = 5;

  // Input controls for gathering user data.
  repeated DialogInput inputs = 6;

  // Whether ESC key can close the dialog.
  // Default: true.
  bool can_close_with_escape = 7 [(google.api.field_behavior) = REQUIRED];

  // Whether to pause the game in singleplayer.
  // Default: true. No effect in multiplayer.
  bool pause = 8 [(google.api.field_behavior) = REQUIRED];

  // Behavior after an action button is clicked.
  DialogAfterAction after_action = 9 [(google.api.field_behavior) = REQUIRED];

  // For NOTICE type: the single action button.
  optional DialogButton action = 10;

  // For CONFIRMATION type: the "yes" button.
  optional DialogButton yes = 11;

  // For CONFIRMATION type: the "no" button.
  optional DialogButton no = 12;

  // For MULTI_ACTION type: list of action buttons.
  repeated DialogButton actions = 13;

  // For MULTI_ACTION and DIALOG_LIST: number of button columns.
  // Default: 2.
  int32 columns = 14 [(google.api.field_behavior) = REQUIRED];

  // For MULTI_ACTION, SERVER_LINKS, DIALOG_LIST: exit/close button.
  optional DialogButton exit_action = 15;

  // For SERVER_LINKS and DIALOG_LIST: button width.
  // Default: 150.
  int32 button_width = 16 [(google.api.field_behavior) = REQUIRED];
}

// Request to get the currently displayed dialog (if any).
message BotGetDialogRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot to get dialog for.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Response containing the current dialog state.
message BotGetDialogResponse {
  // The currently displayed dialog.
  // Absent if no dialog is being shown.
  optional ServerDialog dialog = 1;
}

// Request to submit a dialog with input values.
// Used for dialogs that have input fields requiring user data.
message BotSubmitDialogRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot submitting the dialog.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // Map of input key to value for each input field.
  // Keys must match the `key` field of DialogInput elements.
  map<string, string> input_values = 3 [(google.api.field_behavior) = REQUIRED];
}

// Response for dialog submission.
message BotSubmitDialogResponse {
  // Whether the submission was processed successfully.
  bool success = 1 [(google.api.field_behavior) = REQUIRED];

  // Error message if success is false.
  // Possible errors: "No dialog is currently displayed"
  optional string error = 2;
}

// Request to click a button in the current dialog.
message BotClickDialogButtonRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot clicking the button.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // Index of the button to click.
  // For NOTICE: 0 for the action button
  // For CONFIRMATION: 0 for yes, 1 for no
  // For MULTI_ACTION: index into the actions list
  // Special value: -1 to close/escape the dialog
  int32 button_index = 3 [(google.api.field_behavior) = REQUIRED];
}

// Response for dialog button click.
message BotClickDialogButtonResponse {
  // Whether the button click was processed successfully.
  bool success = 1 [(google.api.field_behavior) = REQUIRED];

  // Error message if success is false.
  // Possible errors: "No dialog is currently displayed"
  optional string error = 2;
}

// Request to close/dismiss the current dialog.
message BotCloseDialogRequest {
  // The UUID of the SoulFire instance containing the bot.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];

  // The profile UUID of the bot to close dialog for.
  string bot_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Response for dialog close.
message BotCloseDialogResponse {
  // Whether the dialog was successfully closed.
  bool success = 1 [(google.api.field_behavior) = REQUIRED];

  // Error message if success is false.
  optional string error = 2;
}

// ============================================================================
// BotService Definition
// ============================================================================

// Service for managing and controlling Minecraft bots.
// Provides comprehensive APIs for bot monitoring, movement control,
// inventory management, and interaction with server dialogs.
//
// Permissions:
// - READ_BOT_INFO: Required for GetBotList, GetBotInfo, GetInventoryState,
//                  RenderBotPov, GetDialog
// - CONTROL_BOTS: Required to start, stop, or restart bots
// - UPDATE_BOT_CONFIG: Required for persistent per-bot setting changes
// - CONTROL_BOT_ACTIONS: Required for live movement, inventory, and dialog actions
//
// Error Handling:
// - NOT_FOUND: Instance or bot does not exist
// - FAILED_PRECONDITION: Bot is not online or required game state is unavailable
// - INTERNAL: Unexpected server errors (check server logs for details)
service BotService {
  // Persists whether the selected bots should be running or stopped.
  // The request returns after the desired state is durable. Connections are
  // reconciled asynchronously and can be observed through WatchBotStatuses.
  rpc SetBotsDesiredState (SetBotsDesiredStateRequest) returns (SetBotsDesiredStateResponse) {
    option (google.api.http) = {
      post: "/v1/instances/{instance_id}/bots:setDesiredState"
      body: "*"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Set bot desired state"
      permissions: "CONTROL_BOTS"
      scope: "instance.bot"
      execution: "Returns after desired state is persisted"
      side_effects: "Starts or stops the selected bots asynchronously."
    };
  }

  // Forces the selected bots to establish fresh connections.
  rpc RestartBots (RestartBotsRequest) returns (RestartBotsResponse) {
    option (google.api.http) = {
      post: "/v1/instances/{instance_id}/bots:restart"
      body: "*"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Restart bots"
      permissions: "CONTROL_BOTS"
      scope: "instance.bot"
      side_effects: "Persists running intent and reconnects the selected bots."
    };
  }

  // Streams an initial snapshot followed by desired and runtime state changes.
  rpc WatchBotStatuses (WatchBotStatusesRequest) returns (stream WatchBotStatusesResponse) {
    option (soulfire.v1.api_method) = {
      display_name: "Watch bot statuses"
      permissions: "READ_BOT_INFO"
      scope: "instance"
      execution: "Server streaming"
    };
  }

  // Returns a list of all bots configured in the specified instance.
  // Includes both online and offline bots with their current status.
  // For online bots, includes live state (position, health, etc.) but NOT full inventory data.
  // Requires READ_BOT_INFO permission.
  rpc GetBotList (BotListRequest) returns (BotListResponse) {
    option (google.api.http) = { get: "/v1/instances/{instance_id}/bots" };
    option (soulfire.v1.api_method) = {
      display_name: "List bots"
      permissions: "READ_BOT_INFO"
      scope: "instance"
    };
  }

  // Returns detailed information about a specific bot.
  // Includes full live state with complete inventory data when the bot is online.
  // Requires READ_BOT_INFO permission.
  rpc GetBotInfo (BotInfoRequest) returns (BotInfoResponse) {
    option (google.api.http) = { get: "/v1/instances/{instance_id}/bots/{bot_id}" };
    option (soulfire.v1.api_method) = {
      display_name: "Get bot"
      permissions: "READ_BOT_INFO"
      scope: "instance.bot"
    };
  }

  // Updates a single configuration entry for a specific bot.
  // The configuration is persisted to the database immediately.
  // Requires UPDATE_BOT_CONFIG permission.
  rpc UpdateBotConfigEntry (BotUpdateConfigEntryRequest) returns (BotUpdateConfigEntryResponse) {
    option (google.api.http) = {
      patch: "/v1/instances/{instance_id}/bots/{bot_id}/config/namespaces/{namespace}/entries/{key}"
      body: "*"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Update bot config entry"
      permissions: "UPDATE_BOT_CONFIG"
      scope: "instance.bot"
      side_effects: "Persists the new setting value immediately."
    };
  }

  // Renders the bot's point-of-view as a PNG image using software rendering.
  // The bot must be online with a valid player and level.
  // Uses the bot's current render distance setting.
  // Requires READ_BOT_INFO permission.
  rpc RenderBotPov (BotRenderPovRequest) returns (BotRenderPovResponse) {
    option (google.api.http) = { get: "/v1/instances/{instance_id}/bots/{bot_id}/pov:render" };
    option (soulfire.v1.api_method) = {
      display_name: "Render bot POV"
      permissions: "READ_BOT_INFO"
      scope: "instance.bot"
      preconditions: "Bot must be online."
      preconditions: "Player and level state must be available."
      execution: "Immediate"
    };
  }

  // Streams rendered POV frames. Slow consumers receive the newest available
  // frame and an explicit dropped-frame count instead of an unbounded backlog.
  rpc WatchBotPov (BotWatchPovRequest) returns (stream BotPovFrame) {
    option (google.api.http) = { get: "/v1/instances/{instance_id}/bots/{bot_id}/pov:watch" };
    option (soulfire.v1.api_method) = {
      display_name: "Watch bot POV"
      permissions: "READ_BOT_INFO"
      scope: "instance.bot"
      preconditions: "Bot must be online."
      preconditions: "Player and level state must be available."
      execution: "Streaming"
    };
  }

  // Samples loaded terrain and optional entity overlays for top-down viewers.
  rpc GetBotWorldMap (BotWorldMapRequest) returns (BotWorldMapResponse) {
    option (google.api.http) = { get: "/v1/instances/{instance_id}/bots/{bot_id}/world-map" };
    option (soulfire.v1.api_method) = {
      display_name: "Get bot world map"
      permissions: "READ_BOT_INFO"
      scope: "instance.bot"
      preconditions: "Bot must be online."
      preconditions: "Player and level state must be available."
      execution: "Immediate"
    };
  }

  // Performs a click action on an inventory/container slot.
  // Supports various click types (left, right, shift, drop, swap, middle).
  // The bot must be online with a valid player and gameMode.
  // Requires CONTROL_BOT_ACTIONS permission.
  rpc ClickInventorySlot (BotInventoryClickRequest) returns (BotInventoryClickResponse) {
    option (google.api.http) = {
      post: "/v1/instances/{instance_id}/bots/{bot_id}/inventory/slots/{slot}:click"
      body: "*"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Click inventory slot"
      permissions: "CONTROL_BOT_ACTIONS"
      scope: "instance.bot"
      preconditions: "Bot must be online."
      preconditions: "Player and game mode state must be available."
      execution: "Immediate"
      side_effects: "Clicks the open container or player inventory."
    };
  }

  // Returns the current state of the bot's open container/inventory.
  // Includes layout information, slot contents, and carried item.
  // The bot must be online with a valid player.
  // Requires READ_BOT_INFO permission.
  rpc GetInventoryState (BotInventoryStateRequest) returns (BotInventoryStateResponse) {
    option (google.api.http) = { get: "/v1/instances/{instance_id}/bots/{bot_id}/inventory" };
    option (soulfire.v1.api_method) = {
      display_name: "Get inventory state"
      permissions: "READ_BOT_INFO"
      scope: "instance.bot"
      preconditions: "Bot should be online for live container data."
    };
  }

  // Closes the currently open container (chest, crafting table, etc.).
  // Returns to the player inventory view.
  // The bot must be online with a valid player.
  // Requires CONTROL_BOT_ACTIONS permission.
  rpc CloseContainer (BotCloseContainerRequest) returns (BotCloseContainerResponse) {
    option (google.api.http) = { post: "/v1/instances/{instance_id}/bots/{bot_id}/container:close" };
    option (soulfire.v1.api_method) = {
      display_name: "Close container"
      permissions: "CONTROL_BOT_ACTIONS"
      scope: "instance.bot"
      preconditions: "Bot must be online."
      preconditions: "Player state must be available."
      execution: "Immediate"
      side_effects: "Closes the current container view."
    };
  }

  // Opens the player's inventory screen.
  // Sends a packet to the server to display the inventory UI.
  // The bot must be online with a valid player.
  // Requires CONTROL_BOT_ACTIONS permission.
  rpc OpenInventory (BotOpenInventoryRequest) returns (BotOpenInventoryResponse) {
    option (google.api.http) = { post: "/v1/instances/{instance_id}/bots/{bot_id}/inventory:open" };
    option (soulfire.v1.api_method) = {
      display_name: "Open inventory"
      permissions: "CONTROL_BOT_ACTIONS"
      scope: "instance.bot"
      preconditions: "Bot must be online."
      preconditions: "Player state must be available."
      execution: "Immediate"
      side_effects: "Opens the player inventory screen."
    };
  }

  // Simulates a mouse click in the game world.
  // Left click: attack entity or start breaking block.
  // Right click: use item or interact with entity/block.
  // The bot must be online with valid player, level, and gameMode.
  // Requires CONTROL_BOT_ACTIONS permission.
  rpc MouseClick (BotMouseClickRequest) returns (BotMouseClickResponse) {
    option (google.api.http) = {
      post: "/v1/instances/{instance_id}/bots/{bot_id}/mouse:click"
      body: "*"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Mouse click"
      permissions: "CONTROL_BOT_ACTIONS"
      scope: "instance.bot"
      preconditions: "Bot must be online."
      preconditions: "Player, level, and game mode state must be available."
      execution: "Immediate"
      side_effects: "Triggers a world interaction using the selected mouse button."
    };
  }

  // Clicks a container-specific action button.
  // Used for: stonecutter recipes, enchanting, loom patterns, villager trades,
  // beacon effects, crafter slot toggles, lectern page navigation.
  // The action is queued and executed on the next game tick.
  // Requires CONTROL_BOT_ACTIONS permission.
  rpc ClickContainerButton (BotContainerButtonClickRequest) returns (BotContainerButtonClickResponse) {
    option (google.api.http) = {
      post: "/v1/instances/{instance_id}/bots/{bot_id}/container/buttons/{button_id}:click"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Click container button"
      permissions: "CONTROL_BOT_ACTIONS"
      scope: "instance.bot"
      execution: "Queued for the next game tick"
      side_effects: "Activates the selected container-specific action button."
    };
  }

  // Sets text in a container's text input field.
  // Currently only supports anvil item renaming (field_id: "item_name").
  // The action is queued and executed on the next game tick.
  // Requires CONTROL_BOT_ACTIONS permission.
  rpc SetContainerText (BotSetContainerTextRequest) returns (BotSetContainerTextResponse) {
    option (google.api.http) = {
      post: "/v1/instances/{instance_id}/bots/{bot_id}/container/text-inputs/{field_id}:set"
      body: "*"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Set container text"
      permissions: "CONTROL_BOT_ACTIONS"
      scope: "instance.bot"
      execution: "Queued for the next game tick"
      side_effects: "Updates the selected container text input."
    };
  }

  // Changes the selected hotbar slot.
  // Valid slot range: 0-8 (corresponding to slots 1-9 in the UI).
  // The action is queued and executed on the next game tick.
  // Requires CONTROL_BOT_ACTIONS permission.
  rpc SetHotbarSlot (BotSetHotbarSlotRequest) returns (BotSetHotbarSlotResponse) {
    option (google.api.http) = {
      post: "/v1/instances/{instance_id}/bots/{bot_id}/hotbar/{slot}:select"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Select hotbar slot"
      permissions: "CONTROL_BOT_ACTIONS"
      scope: "instance.bot"
      execution: "Queued for the next game tick"
      side_effects: "Changes the selected hotbar slot."
    };
  }

  // Updates the bot's movement state (WASD, jump, sneak, sprint).
  // Only specified fields are changed; omitted fields retain their current value.
  // Movement persists until explicitly changed or reset.
  // The action is queued and executed on the next game tick.
  // Requires CONTROL_BOT_ACTIONS permission.
  rpc SetMovementState (BotSetMovementStateRequest) returns (BotSetMovementStateResponse) {
    option (google.api.http) = {
      post: "/v1/instances/{instance_id}/bots/{bot_id}/movement:set"
      body: "*"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Set movement state"
      permissions: "CONTROL_BOT_ACTIONS"
      scope: "instance.bot"
      execution: "Queued for the next game tick"
      side_effects: "Updates the persisted movement control state."
    };
  }

  // Resets all movement to stopped state.
  // Equivalent to releasing all movement keys.
  // The action is queued and executed on the next game tick.
  // Requires CONTROL_BOT_ACTIONS permission.
  rpc ResetMovement (BotResetMovementRequest) returns (BotResetMovementResponse) {
    option (google.api.http) = { post: "/v1/instances/{instance_id}/bots/{bot_id}/movement:reset" };
    option (soulfire.v1.api_method) = {
      display_name: "Reset movement"
      permissions: "CONTROL_BOT_ACTIONS"
      scope: "instance.bot"
      execution: "Queued for the next game tick"
      side_effects: "Clears all movement inputs."
    };
  }

  // Sets the bot's view rotation (look direction).
  // Yaw is normalized to -180 to 180, pitch is clamped to -90 to 90.
  // The action is queued and executed on the next game tick.
  // Requires CONTROL_BOT_ACTIONS permission.
  rpc SetRotation (BotSetRotationRequest) returns (BotSetRotationResponse) {
    option (google.api.http) = {
      post: "/v1/instances/{instance_id}/bots/{bot_id}/rotation:set"
      body: "*"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Set bot rotation"
      permissions: "CONTROL_BOT_ACTIONS"
      scope: "instance.bot"
      execution: "Queued for the next game tick"
      side_effects: "Updates the bot's look direction."
    };
  }

  // Returns the currently displayed server dialog (Minecraft 1.21.6+).
  // Dialogs are server-sent UI screens for custom interactions.
  // Returns empty response if no dialog is being shown.
  // Does not require the bot to be online (but dialog will be absent).
  // Requires READ_BOT_INFO permission.
  rpc GetDialog (BotGetDialogRequest) returns (BotGetDialogResponse) {
    option (google.api.http) = { get: "/v1/instances/{instance_id}/bots/{bot_id}/dialog" };
    option (soulfire.v1.api_method) = {
      display_name: "Get dialog"
      permissions: "READ_BOT_INFO"
      scope: "instance.bot"
    };
  }

  // Submits a dialog with input values.
  // Used for dialogs containing text fields, checkboxes, dropdowns, or sliders.
  // The dialog is closed after submission.
  // Note: Full dialog response packets are not yet implemented; this clears local state.
  // Requires CONTROL_BOT_ACTIONS permission.
  rpc SubmitDialog (BotSubmitDialogRequest) returns (BotSubmitDialogResponse) {
    option (google.api.http) = {
      post: "/v1/instances/{instance_id}/bots/{bot_id}/dialog:submit"
      body: "*"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Submit dialog"
      permissions: "CONTROL_BOT_ACTIONS"
      scope: "instance.bot"
      execution: "Immediate"
      side_effects: "Submits the current dialog and clears the local dialog state."
    };
  }

  // Clicks a button in the current dialog.
  // Button index depends on dialog type (see BotClickDialogButtonRequest).
  // The dialog is typically closed after the button click.
  // Note: Full dialog button packets are not yet implemented; this clears local state.
  // Requires CONTROL_BOT_ACTIONS permission.
  rpc ClickDialogButton (BotClickDialogButtonRequest) returns (BotClickDialogButtonResponse) {
    option (google.api.http) = {
      post: "/v1/instances/{instance_id}/bots/{bot_id}/dialog/buttons/{button_index}:click"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Click dialog button"
      permissions: "CONTROL_BOT_ACTIONS"
      scope: "instance.bot"
      execution: "Immediate"
      side_effects: "Invokes a dialog action and usually clears the local dialog state."
    };
  }

  // Closes/dismisses the current dialog.
  // Clears the local dialog state.
  // Requires CONTROL_BOT_ACTIONS permission.
  rpc CloseDialog (BotCloseDialogRequest) returns (BotCloseDialogResponse) {
    option (google.api.http) = { post: "/v1/instances/{instance_id}/bots/{bot_id}/dialog:close" };
    option (soulfire.v1.api_method) = {
      display_name: "Close dialog"
      permissions: "CONTROL_BOT_ACTIONS"
      scope: "instance.bot"
      execution: "Immediate"
      side_effects: "Closes the current dialog and clears the local dialog state."
    };
  }
}
