syntax = "proto3";

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

package soulfire.v1;

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";

// The type of connection between nodes in the visual script editor.
// Determines how data and control flow between nodes.
enum EdgeType {
  // Execution edge that controls the order of node execution.
  // When a node completes, execution flows through its outgoing execution edges.
  EDGE_TYPE_EXECUTION = 0;
  // Data edge that passes values between nodes.
  // Data edges transfer output values from one node's output port to another node's input port.
  EDGE_TYPE_DATA = 1;
}

// The data type of a node port.
// Used for validation and UI rendering of connections.
enum PortType {
  // Any type - accepts all values. Used for generic nodes.
  PORT_TYPE_ANY = 0;
  // Numeric value (integer or floating point).
  PORT_TYPE_NUMBER = 1;
  // Text string value.
  PORT_TYPE_STRING = 2;
  // Boolean true/false value.
  PORT_TYPE_BOOLEAN = 3;
  // 3D vector with x, y, z components.
  PORT_TYPE_VECTOR3 = 4;
  // Reference to a bot connection.
  PORT_TYPE_BOT = 5;
  // List/array of values.
  PORT_TYPE_LIST = 6;
  // Execution flow port (not data).
  PORT_TYPE_EXEC = 7;
  // Block type identifier.
  PORT_TYPE_BLOCK = 8;
  // Entity reference.
  PORT_TYPE_ENTITY = 9;
  // Item stack reference.
  PORT_TYPE_ITEM = 10;
  // Key-value map of entries.
  PORT_TYPE_MAP = 11;
  // Set of unique values with stable insertion order.
  PORT_TYPE_SET = 12;
  // Any Java-like collection type. LIST and SET are compatible with COLLECTION.
  PORT_TYPE_COLLECTION = 13;
}

// Log levels for script execution logging.
// Maps to standard logging severity levels for filtering and display.
enum LogLevel {
  // Debug level for verbose diagnostic information.
  // Typically only shown when troubleshooting scripts.
  LOG_LEVEL_DEBUG = 0;
  // Informational messages about script execution progress.
  // Normal operational messages.
  LOG_LEVEL_INFO = 1;
  // Warning level for potentially problematic situations.
  // Script continues execution but something unexpected occurred.
  LOG_LEVEL_WARN = 2;
  // Error level for failures that may affect script execution.
  // Indicates a problem that needs attention.
  LOG_LEVEL_ERROR = 3;
}

// Represents the 2D position of a node in the visual script editor canvas.
// Used for rendering the node graph and preserving user layout preferences.
message Position {
  // The horizontal position in canvas coordinates.
  // Positive values extend to the right.
  double x = 1 [(google.api.field_behavior) = REQUIRED];
  // The vertical position in canvas coordinates.
  // Positive values extend downward.
  double y = 2 [(google.api.field_behavior) = REQUIRED];
}

// A recursive type descriptor for generic/parameterized port types.
// Supports simple types (NUMBER, STRING), parameterized types (List<Bot>, Map<String, Number>),
// and type variables (T, K, V) that are resolved based on connections.
message TypeDescriptor {
  oneof kind {
    // A simple, non-parameterized type (e.g., NUMBER, STRING, BOT).
    PortType simple = 1;
    // A parameterized type like List<T> or Map<K, V>.
    ParameterizedType parameterized = 2;
    // A type variable name (e.g., "T", "K", "V") that gets resolved from connections.
    // Type variables are scoped per node: each node instance has its own bindings.
    string type_variable = 3;
  }
}

// A parameterized type with a base type and type arguments.
// For example, List<Bot> has base=LIST and params=[Simple(BOT)].
message ParameterizedType {
  // The container type (e.g., LIST, MAP).
  PortType base = 1 [(google.api.field_behavior) = REQUIRED];
  // The type arguments (e.g., [BOT] for List<Bot>, [STRING, NUMBER] for Map<String, Number>).
  repeated TypeDescriptor params = 2;
}

// Definition of a port (input or output) on a node type.
// Describes the port's type, constraints, and display properties.
message PortDefinition {
  // The identifier for this port.
  // Used in edge connections (source_handle/target_handle).
  // Examples: "value", "target", "bot", "exec_in", "exec_out".
  string id = 1 [(google.api.field_behavior) = REQUIRED];
  // Human-readable name displayed in the UI.
  // Examples: "Target Position", "Bot", "Message".
  string display_name = 2 [(google.api.field_behavior) = REQUIRED];
  // The data type this port accepts or produces.
  PortType port_type = 3 [(google.api.field_behavior) = REQUIRED];
  // Whether this input is required for the node to execute.
  // Only applicable for input ports.
  bool required = 4 [(google.api.field_behavior) = REQUIRED];
  // Default value for this port when not connected (JSON-encoded).
  // Only applicable for input ports.
  optional string default_value = 5;
  // Optional description explaining what this port is for.
  string description = 6;
  // For list ports, the type of elements in the list.
  // Only applicable when port_type is PORT_TYPE_LIST.
  optional PortType element_type = 7;
  // Whether this input accepts multiple connections (Blender-style multi-input).
  // When true, all connected values are collected into a list.
  // Visually rendered as a pill/ellipsis shaped socket.
  bool multi_input = 8 [(google.api.field_behavior) = REQUIRED];
  // For polymorphic ports, the list of accepted types.
  // If non-empty, this port can accept any of these types and will
  // adapt its behavior based on the connected type.
  repeated PortType accepted_types = 9;
  // For dynamic output ports, the ID of the input port to inherit type from.
  // Used for polymorphic nodes where output type depends on input type.
  string infer_type_from = 10 [(google.api.field_behavior) = REQUIRED];
  // Optional generic type descriptor for parameterized types.
  // When present, provides richer type information than the flat port_type field.
  // For example, List<Bot> or a type variable T that resolves from connections.
  optional TypeDescriptor type_descriptor = 11;
}

// Complete definition of a node type.
// Contains all metadata needed to render the node in an editor
// and validate its connections without hardcoded client knowledge.
message NodeTypeDefinition {
  // The unique type identifier for this node.
  // Examples: "trigger.on_tick", "action.pathfind", "math.add".
  string type = 1 [(google.api.field_behavior) = REQUIRED];
  // Human-readable name displayed in the UI.
  // Examples: "On Tick", "Pathfind To", "Add".
  string display_name = 2 [(google.api.field_behavior) = REQUIRED];
  // Description of what this node does.
  // Displayed in tooltips and node palettes.
  string description = 3 [(google.api.field_behavior) = REQUIRED];
  // Category ID for organizing nodes in the palette.
  // References CategoryDefinition.id from GetNodeTypesResponse.
  // Examples: "triggers", "actions", "math", "logic", "flow".
  string category = 4 [(google.api.field_behavior) = REQUIRED];
  // Whether this is a trigger node (entry point for execution).
  // Trigger nodes have no execution input and start script flows.
  bool is_trigger = 5 [(google.api.field_behavior) = REQUIRED];
  // The input ports for this node type.
  // Includes both data inputs and execution inputs.
  repeated PortDefinition inputs = 6;
  // The output ports for this node type.
  // Includes both data outputs and execution outputs.
  repeated PortDefinition outputs = 7;
  // Optional icon identifier for rendering.
  // Can be an icon name or emoji.
  string icon = 8;
  // Optional color hint for the node (hex color code).
  // Used for visual distinction between node categories.
  string color = 9;
  // Keywords for searching/filtering nodes in the palette.
  repeated string keywords = 10;
  // Whether this node is deprecated and should be avoided.
  bool deprecated = 11 [(google.api.field_behavior) = REQUIRED];
  // If deprecated, message explaining what to use instead.
  string deprecation_message = 12;
  // Whether this is a layout node (reroute, frame, etc.).
  // Layout nodes have special minimal rendering and don't execute logic.
  bool is_layout_node = 13 [(google.api.field_behavior) = REQUIRED];
  // Whether this node can be muted (bypassed during execution).
  // When muted, inputs pass through to outputs unchanged.
  bool supports_muting = 14 [(google.api.field_behavior) = REQUIRED];
  // Whether this node supports inline preview of its output.
  bool supports_preview = 15 [(google.api.field_behavior) = REQUIRED];
  // Whether this node is expensive (slow, may block).
  // Used for visual warnings in tick-path analysis.
  bool is_expensive = 16 [(google.api.field_behavior) = REQUIRED];
}

// Represents a single node in the visual script graph.
// Nodes are the building blocks of scripts, each performing a specific action
// or computation when executed.
message ScriptNode {
  // Unique identifier for this node within the script.
  // Used to reference this node in edges and during execution tracking.
  // Format: UUID string.
  string id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The type identifier for this node, determining its behavior and available ports.
  // Examples: "trigger.on_chat", "action.send_message", "control.delay", "logic.condition".
  string type = 2 [(google.api.field_behavior) = REQUIRED];
  // The visual position of this node on the editor canvas.
  // Persisted to maintain the user's layout when reopening the script.
  Position position = 3 [(google.api.field_behavior) = REQUIRED];
  // Configuration data for this node instance.
  // Contains node-specific settings and parameter values.
  // The structure depends on the node type.
  map<string, google.protobuf.Value> data = 4;
  // Whether this node is muted (bypassed during execution).
  // When muted, the node passes inputs directly to outputs without processing.
  // Visually shown as grayed out with pass-through indicator.
  bool muted = 5 [(google.api.field_behavior) = REQUIRED];
  // Whether this node is collapsed (showing only header).
  // Collapsed nodes still function normally but take less visual space.
  bool collapsed = 6 [(google.api.field_behavior) = REQUIRED];
  // For frame nodes: the width of the frame.
  optional double width = 7;
  // For frame nodes: the height of the frame.
  optional double height = 8;
  // For frame nodes: IDs of nodes contained within this frame.
  // Updated automatically based on node positions.
  repeated string contained_nodes = 9;
  // Optional label override for this node instance.
  // If set, displayed instead of the node type's default label.
  string label = 10;
  // For reroute nodes: the resolved port type based on connections.
  // Dynamically updated when connections change.
  optional PortType resolved_type = 11;
  // Parent frame node ID, if this node is inside a frame.
  string parent_frame_id = 12 [(google.api.field_behavior) = REQUIRED];
}

// Represents a connection between two nodes in the visual script graph.
// Edges define how execution flows and how data passes between nodes.
message ScriptEdge {
  // Unique identifier for this edge within the script.
  // Format: UUID string.
  string id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The ID of the source node where this edge originates.
  // Must reference a valid node ID in the script's nodes list.
  string source = 2 [(google.api.field_behavior) = REQUIRED];
  // The handle/port identifier on the source node.
  // Identifies which output port of the source node this edge connects from.
  // Examples: "exec_out", "value", "true", "false".
  string source_handle = 3 [(google.api.field_behavior) = REQUIRED];
  // The ID of the target node where this edge terminates.
  // Must reference a valid node ID in the script's nodes list.
  string target = 4 [(google.api.field_behavior) = REQUIRED];
  // The handle/port identifier on the target node.
  // Identifies which input port of the target node this edge connects to.
  // Examples: "exec_in", "message", "condition".
  string target_handle = 5 [(google.api.field_behavior) = REQUIRED];
  // The type of this edge, determining whether it carries execution flow or data.
  EdgeType edge_type = 6 [(google.api.field_behavior) = REQUIRED];
  // Explicit order for edges targeting the same multi-input port.
  // Lower values are materialized first. Ignored for non-multi-input ports.
  optional int32 order = 7;
}

// Complete data structure representing a visual script.
// Contains all nodes, edges, and metadata needed to render and execute the script.
message ScriptData {
  // Unique identifier for this script.
  // Format: UUID string.
  string id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // Human-readable name for the script.
  // Displayed in the UI and used for identification.
  string name = 2 [(google.api.field_behavior) = REQUIRED];
  // Optional description explaining what the script does.
  // Displayed in script listings and detail views.
  string description = 3;
  // The list of nodes that make up this script's graph.
  // Nodes are executed based on execution edge connections.
  repeated ScriptNode nodes = 4;
  // The list of edges connecting nodes in this script.
  // Defines both execution flow and data transfer between nodes.
  repeated ScriptEdge edges = 5;
  // Reserved field 6 was scope (removed - all scripts run at instance level)
  reserved 6;
  // The instance this script belongs to.
  // Scripts are always associated with a specific instance.
  // Format: UUID string.
  string instance_id = 7 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // Whether this script is paused (not running).
  // Scripts run by default. When paused, they won't be started on server startup
  // or when created/updated.
  bool paused = 8 [(google.api.field_behavior) = REQUIRED];
  // Optional resource quotas for this script.
  ScriptQuotas quotas = 9;
}

// Resource quotas for limiting script execution.
// All fields are optional; unset fields use server defaults.
message ScriptQuotas {
  // Maximum number of node executions per trigger invocation.
  optional int64 max_execution_count = 1;
  // Maximum wall-clock time for a single trigger execution in milliseconds.
  optional int64 max_execution_time_ms = 2;
  // Maximum number of concurrent trigger invocations.
  optional int32 max_concurrent_triggers = 3;
  // Maximum number of entries in the script's state store.
  optional int64 max_state_store_entries = 4;
  // When true, disables per-node and data edge timeouts entirely.
  bool disable_timeouts = 5 [(google.api.field_behavior) = REQUIRED];
}

// Summary information about a script for listing purposes.
// Contains metadata without the full node graph to reduce payload size.
message ScriptInfo {
  // Unique identifier for this script.
  // Format: UUID string.
  string id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // Human-readable name for the script.
  string name = 2 [(google.api.field_behavior) = REQUIRED];
  // Optional description of what the script does.
  string description = 3;
  // Reserved field 4 was scope (removed - all scripts run at instance level)
  reserved 4;
  // The instance this script belongs to.
  // Format: UUID string.
  string instance_id = 5 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // Timestamp when the script was first created.
  google.protobuf.Timestamp created_at = 6 [(google.api.field_behavior) = REQUIRED];
  // Timestamp when the script was last modified.
  google.protobuf.Timestamp updated_at = 7 [(google.api.field_behavior) = REQUIRED];
  // Whether this script is paused (not running).
  bool paused = 8 [(google.api.field_behavior) = REQUIRED];
}

// Runtime status information for a script.
// Provides information about current activation state and statistics.
message ScriptStatus {
  // The script this status applies to.
  // Format: UUID string.
  string script_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // Whether the script is currently active (listening for triggers).
  bool is_active = 2 [(google.api.field_behavior) = REQUIRED];
  // The ID of the currently executing node, if any.
  // Only present when a node is actively executing.
  optional string active_node_id = 3;
  // The total number of times this script has been activated.
  // Incremented each time ActivateScript is called.
  int64 activation_count = 4 [(google.api.field_behavior) = REQUIRED];
}

// A single log entry generated during script execution.
// Provides detailed information about script operations for debugging and monitoring.
message ScriptLogEntry {
  // The script that generated this log entry.
  // Format: UUID string.
  string script_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The node that generated this log entry, if applicable.
  // May be empty for script-level log entries.
  string node_id = 2 [(google.api.field_behavior) = REQUIRED];
  // The severity level of this log entry.
  LogLevel level = 3 [(google.api.field_behavior) = REQUIRED];
  // The log message content.
  string message = 4 [(google.api.field_behavior) = REQUIRED];
  // When this log entry was created.
  google.protobuf.Timestamp timestamp = 5 [(google.api.field_behavior) = REQUIRED];
}

// Event emitted when a node begins execution.
message NodeStarted {
  // The ID of the node that started executing.
  string node_id = 1 [(google.api.field_behavior) = REQUIRED];
  // When the node started execution.
  google.protobuf.Timestamp timestamp = 2 [(google.api.field_behavior) = REQUIRED];
}

// Event emitted when a node completes execution successfully.
message NodeCompleted {
  // The ID of the node that completed execution.
  string node_id = 1 [(google.api.field_behavior) = REQUIRED];
  // The output values produced by the node.
  // Keys are output port names, values are the data produced.
  map<string, google.protobuf.Value> outputs = 2;
  // When the node completed execution.
  google.protobuf.Timestamp timestamp = 3 [(google.api.field_behavior) = REQUIRED];
  // Execution time in nanoseconds for profiling.
  int64 execution_time_nanos = 4 [(google.api.field_behavior) = REQUIRED];
}

// Execution statistics emitted when a trigger chain completes.
message ExecutionStats {
  // Total number of node executions in this chain.
  int64 node_count = 1 [(google.api.field_behavior) = REQUIRED];
  // Maximum allowed node executions.
  int64 max_count = 2 [(google.api.field_behavior) = REQUIRED];
}

// Event emitted when a node encounters an error during execution.
message NodeError {
  // The ID of the node that encountered the error.
  string node_id = 1 [(google.api.field_behavior) = REQUIRED];
  // A description of what went wrong.
  string error_message = 2 [(google.api.field_behavior) = REQUIRED];
  // When the error occurred.
  google.protobuf.Timestamp timestamp = 3 [(google.api.field_behavior) = REQUIRED];
}

// Event emitted when a script logs a message (e.g., from Print node).
message ScriptLog {
  // The ID of the node that produced the log (if applicable).
  optional string node_id = 1;
  // The log level (debug, info, warn, error).
  string level = 2 [(google.api.field_behavior) = REQUIRED];
  // The log message content.
  string message = 3 [(google.api.field_behavior) = REQUIRED];
  // When the log was produced.
  google.protobuf.Timestamp timestamp = 4 [(google.api.field_behavior) = REQUIRED];
}

// Event emitted when a trigger fires and begins executing a node chain.
// Note: Scripts are reactive - this indicates a trigger event occurred,
// not that the script "started running" in the traditional sense.
message ScriptStarted {
  // The ID of the script containing the triggered chain.
  string script_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // When the trigger fired and execution began.
  google.protobuf.Timestamp timestamp = 2 [(google.api.field_behavior) = REQUIRED];
}

// Event emitted when a triggered node chain finishes executing.
// Note: This indicates one trigger chain completed, not that the script
// is done - the script remains active and will respond to future triggers.
message ScriptCompleted {
  // The ID of the script that completed the chain.
  string script_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // Whether the chain completed successfully without errors.
  bool success = 2 [(google.api.field_behavior) = REQUIRED];
  // When the chain completed execution.
  google.protobuf.Timestamp timestamp = 3 [(google.api.field_behavior) = REQUIRED];
}

// Severity level for validation diagnostics.
enum DiagnosticSeverity {
  // Error that prevents script execution.
  DIAGNOSTIC_ERROR = 0;
  // Warning that does not prevent execution but may indicate issues.
  DIAGNOSTIC_WARNING = 1;
}

// A structured validation diagnostic with node/edge context and severity.
message ValidationDiagnostic {
  // The node related to this diagnostic, or empty for graph-level issues.
  string node_id = 1 [(google.api.field_behavior) = REQUIRED];
  // A composite edge key related to this diagnostic, or empty.
  string edge_id = 2 [(google.api.field_behavior) = REQUIRED];
  // Human-readable description of the issue.
  string message = 3 [(google.api.field_behavior) = REQUIRED];
  // Severity level of this diagnostic.
  DiagnosticSeverity severity = 4 [(google.api.field_behavior) = REQUIRED];
}

// Union type for all script execution events.
// Streamed to clients during script activation to provide real-time feedback.
// Events represent individual trigger chain executions, not script lifecycle.
message ScriptEvent {
  // The specific event that occurred.
  oneof event {
    // A node started executing.
    NodeStarted node_started = 1;
    // A node completed execution successfully.
    NodeCompleted node_completed = 2;
    // A node encountered an error.
    NodeError node_error = 3;
    // A trigger chain finished executing.
    ScriptCompleted script_completed = 4;
    // A trigger fired and began executing a chain.
    ScriptStarted script_started = 5;
    // A script logged a message.
    ScriptLog script_log = 6;
    // Execution statistics for a completed trigger chain.
    ExecutionStats execution_stats = 7;
  }
}

// Request to create a new script.
message CreateScriptRequest {
  // The instance to create the script in.
  // Format: UUID string.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The human-readable name for the script.
  string name = 2 [(google.api.field_behavior) = REQUIRED];
  // Optional description of what the script does.
  string description = 3;
  // Reserved field 4 was scope (removed - all scripts run at instance level)
  reserved 4;
  // Initial nodes for the script graph.
  // Can be empty to create a blank script.
  repeated ScriptNode nodes = 5 [(google.api.field_behavior) = REQUIRED];
  // Initial edges for the script graph.
  // Can be empty to create a blank script.
  repeated ScriptEdge edges = 6 [(google.api.field_behavior) = REQUIRED];
  // Whether the script should be created in paused state.
  // If false (default), the script starts running immediately after creation.
  bool paused = 7 [(google.api.field_behavior) = REQUIRED];
  // Optional resource quotas for this script.
  ScriptQuotas quotas = 8;
}

// Response after successfully creating a script.
message CreateScriptResponse {
  // The complete script data including the generated ID.
  ScriptData script = 1 [(google.api.field_behavior) = REQUIRED];
  // Validation diagnostics (warnings) from building the script graph.
  repeated ValidationDiagnostic diagnostics = 2 [(google.api.field_behavior) = REQUIRED];
}

// Request to retrieve a specific script by ID.
message GetScriptRequest {
  // The instance containing the script.
  // Format: UUID string.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The ID of the script to retrieve.
  // Format: UUID string.
  string script_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Response containing the requested script.
message GetScriptResponse {
  // The complete script data including all nodes and edges.
  ScriptData script = 1 [(google.api.field_behavior) = REQUIRED];
}

// Request to update an existing script.
message UpdateScriptRequest {
  // The instance containing the script.
  // Format: UUID string.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The ID of the script to update.
  // Format: UUID string.
  string script_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // Updated name for the script.
  // If not set, the name remains unchanged.
  optional string name = 3;
  // Updated description for the script.
  // If not set, the description remains unchanged.
  optional string description = 4;
  // Reserved field 5 was scope (removed - all scripts run at instance level)
  reserved 5;
  // Updated list of nodes.
  // If provided, replaces the entire node list.
  // If not provided, nodes remain unchanged.
  repeated ScriptNode nodes = 6;
  // Updated list of edges.
  // If provided, replaces the entire edge list.
  // If not provided, edges remain unchanged.
  repeated ScriptEdge edges = 7;
  // Whether the nodes field should be updated (allows setting to empty list).
  bool update_nodes = 8 [(google.api.field_behavior) = REQUIRED];
  // Whether the edges field should be updated (allows setting to empty list).
  bool update_edges = 9 [(google.api.field_behavior) = REQUIRED];
  // Updated paused setting.
  // If not set, the paused setting remains unchanged.
  // If set to false and script was paused, the script will be restarted.
  optional bool paused = 10;
  // Updated resource quotas for this script.
  ScriptQuotas quotas = 11 [(google.api.field_behavior) = REQUIRED];
  // Whether the quotas field should be updated (allows clearing quotas).
  bool update_quotas = 12 [(google.api.field_behavior) = REQUIRED];
}

// Response after successfully updating a script.
message UpdateScriptResponse {
  // The updated script data.
  ScriptData script = 1 [(google.api.field_behavior) = REQUIRED];
  // Validation diagnostics (warnings) from building the script graph.
  repeated ValidationDiagnostic diagnostics = 2 [(google.api.field_behavior) = REQUIRED];
}

// Request to delete a script.
message DeleteScriptRequest {
  // The instance containing the script.
  // Format: UUID string.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The ID of the script to delete.
  // Format: UUID string.
  string script_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Response after successfully deleting a script.
// Empty response indicates success.
message DeleteScriptResponse {
}

// Request to list all scripts in an instance.
message ListScriptsRequest {
  // The instance to list scripts from.
  // Format: UUID string.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Response containing the list of scripts.
message ListScriptsResponse {
  // Summary information for each script in the instance.
  // Does not include full node/edge data to reduce payload size.
  repeated ScriptInfo scripts = 1 [(google.api.field_behavior) = REQUIRED];
}

// Request to activate a script (register its event listeners).
message ActivateScriptRequest {
  // The instance containing the script.
  // Format: UUID string.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The ID of the script to activate.
  // Format: UUID string.
  string script_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Request to deactivate an active script.
message DeactivateScriptRequest {
  // The instance containing the script.
  // Format: UUID string.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The ID of the script to deactivate.
  // Format: UUID string.
  string script_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Response after successfully deactivating a script.
// Empty response indicates success.
message DeactivateScriptResponse {
}

// Request to get the execution status of a script.
message GetScriptStatusRequest {
  // The instance containing the script.
  // Format: UUID string.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The ID of the script to check.
  // Format: UUID string.
  string script_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
}

// Response containing the script's execution status.
message GetScriptStatusResponse {
  // The current status of the script.
  ScriptStatus status = 1 [(google.api.field_behavior) = REQUIRED];
}

// Request to subscribe to log entries from a script.
message SubscribeScriptLogsRequest {
  // The instance containing the script.
  // Format: UUID string.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The ID of the script to receive logs from.
  // Format: UUID string.
  string script_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // Minimum log level to receive.
  // Only log entries at this level or higher severity will be streamed.
  LogLevel min_level = 3 [(google.api.field_behavior) = REQUIRED];
}

// Request to get all available node types.
// This enables clients to render nodes without hardcoded knowledge.
message GetNodeTypesRequest {
  // Optional filter by category ID (references CategoryDefinition.id).
  // Examples: "triggers", "actions", "math".
  // If empty, returns all node types.
  optional string category = 1;
  // Whether to include deprecated node types.
  // Default is false.
  bool include_deprecated = 2 [(google.api.field_behavior) = REQUIRED];
}

// Definition of a node category for organizing the node palette.
message CategoryDefinition {
  // The unique identifier for this category.
  // Examples: "triggers", "actions", "math", "logic".
  string id = 1 [(google.api.field_behavior) = REQUIRED];
  // Human-readable name displayed in the UI.
  // Examples: "Triggers", "Actions", "Math", "Logic".
  string display_name = 2 [(google.api.field_behavior) = REQUIRED];
  // Icon identifier for the category.
  // Examples: "zap", "play", "calculator", "git-branch".
  string icon = 3 [(google.api.field_behavior) = REQUIRED];
  // Optional description of this category.
  string description = 4;
  // Sort order for displaying categories (lower = earlier).
  int32 sort_order = 5 [(google.api.field_behavior) = REQUIRED];
}

// Response containing all available node types.
message GetNodeTypesResponse {
  // List of all node type definitions.
  // Clients can use this to render any node and validate connections.
  repeated NodeTypeDefinition node_types = 1 [(google.api.field_behavior) = REQUIRED];
  // List of all categories with their metadata.
  // Useful for building category filters and palette sections in the UI.
  repeated CategoryDefinition categories = 2 [(google.api.field_behavior) = REQUIRED];
  // Metadata for each port type including colors and compatibility.
  // Clients should use this instead of hardcoded port type info.
  repeated PortTypeMetadata port_type_metadata = 3 [(google.api.field_behavior) = REQUIRED];
}

// Shape of the port handle in the visual editor.
enum HandleShape {
  // Circular handle, default for data ports.
  HANDLE_SHAPE_CIRCLE = 0;
  // Square handle, typically used for execution/flow ports.
  HANDLE_SHAPE_SQUARE = 1;
  // Diamond-shaped handle, for special port types.
  HANDLE_SHAPE_DIAMOND = 2;
}

// Visual style for edges connected to this port type.
enum EdgeStyle {
  // Default bezier curve style.
  EDGE_STYLE_DEFAULT = 0;
  // Animated flow style with moving dashes, typically for execution edges.
  EDGE_STYLE_ANIMATED = 1;
  // Dashed line style.
  EDGE_STYLE_DASHED = 2;
}

// Metadata for a port type, enabling data-driven port rendering.
message PortTypeMetadata {
  // The port type this metadata applies to.
  PortType port_type = 1 [(google.api.field_behavior) = REQUIRED];
  // Display color for this port type (hex color code, e.g., "#22c55e").
  string color = 2 [(google.api.field_behavior) = REQUIRED];
  // Human-readable name for this port type.
  string display_name = 3 [(google.api.field_behavior) = REQUIRED];
  // List of port types that can be implicitly converted to this type.
  // Used for connection validation. Empty means only exact matches allowed.
  repeated PortType compatible_from = 4;
  // Shape of the handle for this port type in the visual editor.
  HandleShape handle_shape = 5 [(google.api.field_behavior) = REQUIRED];
  // Visual style for edges of this port type.
  EdgeStyle edge_style = 6 [(google.api.field_behavior) = REQUIRED];
}

// Request to get Minecraft registry data for autocomplete and validation.
message GetRegistryDataRequest {
  // Optional: specific registry to fetch. If empty, returns all registries.
  // Valid values: "blocks", "entities", "items", "biomes"
  optional string registry = 1;
}

// A single registry entry (block, entity, item, or biome).
message RegistryEntry {
  // The namespaced identifier (e.g., "minecraft:diamond_ore").
  string id = 1 [(google.api.field_behavior) = REQUIRED];
  // Human-readable display name.
  string display_name = 2 [(google.api.field_behavior) = REQUIRED];
  // Optional icon or texture identifier.
  string icon = 3;
  // Optional category/group for organizing in UI.
  string category = 4;
}

// Request to validate a script graph without saving it.
message ValidateScriptRequest {
  // The instance context for validation.
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The nodes to validate.
  repeated ScriptNode nodes = 2 [(google.api.field_behavior) = REQUIRED];
  // The edges to validate.
  repeated ScriptEdge edges = 3 [(google.api.field_behavior) = REQUIRED];
}

// Response containing validation diagnostics.
message ValidateScriptResponse {
  // All diagnostics (errors and warnings) for the script graph.
  repeated ValidationDiagnostic diagnostics = 1 [(google.api.field_behavior) = REQUIRED];
}

// Request to dry-run a script from a specific trigger node with mock inputs.
message DryRunScriptRequest {
  // The instance ID for context (may be used for registry data).
  string instance_id = 1 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The ID of the saved script to dry-run.
  string script_id = 2 [(google.api.field_behavior) = REQUIRED, (soulfire.v1.api_field).format = "uuid"];
  // The trigger node ID to start execution from.
  string trigger_node_id = 3 [(google.api.field_behavior) = REQUIRED];
  // Mock input values for the trigger (port ID -> value).
  map<string, google.protobuf.Value> mock_inputs = 4;
}

// Response containing Minecraft registry data.
message GetRegistryDataResponse {
  // Block types (e.g., "minecraft:stone", "minecraft:diamond_ore").
  repeated RegistryEntry blocks = 1 [(google.api.field_behavior) = REQUIRED];
  // Entity types (e.g., "minecraft:zombie", "minecraft:player").
  repeated RegistryEntry entities = 2 [(google.api.field_behavior) = REQUIRED];
  // Item types (e.g., "minecraft:diamond", "minecraft:stick").
  repeated RegistryEntry items = 3 [(google.api.field_behavior) = REQUIRED];
  // Biome types (e.g., "minecraft:plains", "minecraft:forest").
  repeated RegistryEntry biomes = 4 [(google.api.field_behavior) = REQUIRED];
}

// ScriptService provides management and execution capabilities for visual node-based
// automation scripts within SoulFire.
//
// The visual scripting system allows users to create automation workflows by connecting
// nodes in a graph. Each node represents an action, condition, or event trigger.
// Nodes are connected by edges that define execution flow and data transfer.
//
// SCRIPT LIFECYCLE:
// 1. Create a script using CreateScript with initial nodes/edges or empty graph
// 2. Edit the script using UpdateScript to modify the node graph
// 3. Activate the script using ActivateScript to register event listeners
// 4. Monitor execution via SubscribeScriptEvents or GetScriptStatus
// 5. Deactivate using DeactivateScript when done
// 6. Delete unused scripts with DeleteScript
//
// Scripts are reactive state machines - they don't "run" but rather listen for
// trigger events and execute node chains in response. Activation registers the
// listeners, deactivation removes them and cancels any pending async operations.
//
// PERMISSIONS: Script operations require appropriate instance permissions.
// The specific permissions are TBD but will likely include:
// - READ_SCRIPT: View script definitions and status
// - UPDATE_SCRIPT: Create, modify, and delete scripts
// - EXECUTE_SCRIPT: Activate and deactivate scripts
//
// LOGGING: Script execution logs can be streamed via SubscribeScriptLogs or
// filtered using InstanceScriptLogScope in the LogsService (see logs.proto).
service ScriptService {
  // Creates a new script in the specified instance.
  // The script can be created with initial nodes and edges, or as a blank script
  // to be edited later.
  // Returns: The complete script data including the generated ID
  // Errors: NOT_FOUND if instance does not exist
  // Errors: PERMISSION_DENIED if user lacks script creation permission
  // Errors: INVALID_ARGUMENT if required fields are missing or invalid
  rpc CreateScript (CreateScriptRequest) returns (CreateScriptResponse) {
    option (google.api.http) = {
      post: "/v1/instances/{instance_id}/scripts"
      body: "*"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Create script"
      permissions: "UPDATE_SCRIPT"
      scope: "instance.script"
      side_effects: "Creates and stores a new script graph."
    };
  }

  // Retrieves a specific script by its ID.
  // Returns the complete script data including all nodes and edges.
  // Errors: NOT_FOUND if instance or script does not exist
  // Errors: PERMISSION_DENIED if user lacks script read permission
  rpc GetScript (GetScriptRequest) returns (GetScriptResponse) {
    option (google.api.http) = { get: "/v1/instances/{instance_id}/scripts/{script_id}" };
    option (soulfire.v1.api_method) = {
      display_name: "Get script"
      permissions: "READ_SCRIPT"
      scope: "instance.script"
    };
  }

  // Updates an existing script's metadata and/or node graph.
  // Only specified fields are updated; others remain unchanged.
  // Use update_nodes/update_edges flags to explicitly set empty lists.
  // Errors: NOT_FOUND if instance or script does not exist
  // Errors: PERMISSION_DENIED if user lacks script update permission
  // Errors: INVALID_ARGUMENT if update data is invalid
  rpc UpdateScript (UpdateScriptRequest) returns (UpdateScriptResponse) {
    option (google.api.http) = {
      patch: "/v1/instances/{instance_id}/scripts/{script_id}"
      body: "*"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Update script"
      permissions: "UPDATE_SCRIPT"
      scope: "instance.script"
      side_effects: "Persists script metadata and graph updates."
    };
  }

  // Permanently deletes a script.
  // If the script is currently active, it will be deactivated first.
  // This operation cannot be undone.
  // Errors: NOT_FOUND if instance or script does not exist
  // Errors: PERMISSION_DENIED if user lacks script delete permission
  rpc DeleteScript (DeleteScriptRequest) returns (DeleteScriptResponse) {
    option (google.api.http) = { delete: "/v1/instances/{instance_id}/scripts/{script_id}" };
    option (soulfire.v1.api_method) = {
      display_name: "Delete script"
      permissions: "UPDATE_SCRIPT"
      scope: "instance.script"
      side_effects: "Deletes the target script."
    };
  }

  // Lists all scripts in the specified instance.
  // Returns summary information (ScriptInfo) without full node/edge data
  // to reduce response size.
  // Errors: NOT_FOUND if instance does not exist
  // Errors: PERMISSION_DENIED if user lacks script read permission
  rpc ListScripts (ListScriptsRequest) returns (ListScriptsResponse) {
    option (google.api.http) = { get: "/v1/instances/{instance_id}/scripts" };
    option (soulfire.v1.api_method) = {
      display_name: "List scripts"
      permissions: "READ_SCRIPT"
      scope: "instance.script"
    };
  }

  // Activates a script by registering its trigger event listeners.
  // The script will begin responding to trigger events (tick, chat, etc.).
  // Returns a stream of execution events as triggers fire and nodes execute.
  // The stream remains open until the script is deactivated.
  // Errors: NOT_FOUND if instance or script does not exist
  // Errors: PERMISSION_DENIED if user lacks script execution permission
  // Errors: FAILED_PRECONDITION if script is already active
  // Errors: INVALID_ARGUMENT if script graph is invalid (e.g., no trigger nodes)
  rpc ActivateScript (ActivateScriptRequest) returns (stream ScriptEvent) {
    option (soulfire.v1.api_method) = {
      display_name: "Activate script"
      permissions: "EXECUTE_SCRIPT"
      scope: "instance.script"
      execution: "Server streaming"
      side_effects: "Activates the script and streams runtime events."
    };
  }

  // Deactivates an active script.
  // Unregisters all event listeners and cancels any pending async operations.
  // Errors: NOT_FOUND if instance or script does not exist
  // Errors: PERMISSION_DENIED if user lacks script execution permission
  // Errors: FAILED_PRECONDITION if script is not active
  rpc DeactivateScript (DeactivateScriptRequest) returns (DeactivateScriptResponse) {
    option (google.api.http) = { post: "/v1/instances/{instance_id}/scripts/{script_id}:deactivate" };
    option (soulfire.v1.api_method) = {
      display_name: "Deactivate script"
      permissions: "EXECUTE_SCRIPT"
      scope: "instance.script"
      side_effects: "Stops the active script and unregisters listeners."
    };
  }

  // Gets the current status of a script.
  // Returns whether the script is active, which node is executing, and activation count.
  // Errors: NOT_FOUND if instance or script does not exist
  // Errors: PERMISSION_DENIED if user lacks script read permission
  rpc GetScriptStatus (GetScriptStatusRequest) returns (GetScriptStatusResponse) {
    option (google.api.http) = { get: "/v1/instances/{instance_id}/scripts/{script_id}/status" };
    option (soulfire.v1.api_method) = {
      display_name: "Get script status"
      permissions: "READ_SCRIPT"
      scope: "instance.script"
    };
  }

  // Subscribes to log entries generated during script execution.
  // Returns a stream of log entries at or above the specified minimum level.
  // The stream remains open until cancelled by the client.
  // Errors: NOT_FOUND if instance or script does not exist
  // Errors: PERMISSION_DENIED if user lacks script log subscription permission
  rpc SubscribeScriptLogs (SubscribeScriptLogsRequest) returns (stream ScriptLogEntry) {
    option (soulfire.v1.api_method) = {
      display_name: "Subscribe to script logs"
      permissions: "READ_SCRIPT"
      scope: "instance.script"
      execution: "Server streaming"
      side_effects: "Opens a live stream of script log entries."
    };
  }

  // Gets all available node types with their metadata.
  // This enables clients to render the node editor without hardcoded knowledge
  // of specific node types. The response includes port definitions, categories,
  // display names, and all other information needed to render and validate nodes.
  // This endpoint is cacheable - node types only change between server versions.
  rpc GetNodeTypes (GetNodeTypesRequest) returns (GetNodeTypesResponse) {
    option (google.api.http) = { get: "/v1/scripts/node-types" };
    option (soulfire.v1.api_method) = {
      display_name: "Get node types"
      permissions: "READ_SCRIPT"
      scope: "script.catalog"
    };
  }

  // Gets Minecraft registry data for autocomplete and validation.
  // Returns blocks, entities, items, and biomes that can be used in scripts.
  // This endpoint is cacheable - registry data only changes between server versions.
  rpc GetRegistryData (GetRegistryDataRequest) returns (GetRegistryDataResponse) {
    option (google.api.http) = { get: "/v1/scripts/registry" };
    option (soulfire.v1.api_method) = {
      display_name: "Get script registry data"
      permissions: "READ_SCRIPT"
      scope: "script.catalog"
    };
  }

  // Validates a script graph without saving it.
  // Returns all diagnostics (errors and warnings) for the given nodes and edges.
  // Useful for live validation in the editor without requiring a save.
  rpc ValidateScript (ValidateScriptRequest) returns (ValidateScriptResponse) {
    option (google.api.http) = {
      post: "/v1/instances/{instance_id}/scripts:validate"
      body: "*"
    };
    option (soulfire.v1.api_method) = {
      display_name: "Validate script"
      permissions: "UPDATE_SCRIPT"
      scope: "instance.script"
    };
  }

  // Dry-runs a script from a specified trigger with mock inputs.
  // Executes the graph without requiring an active instance.
  // Returns execution events as a stream for the editor to display.
  rpc DryRunScript (DryRunScriptRequest) returns (stream ScriptEvent) {
    option (soulfire.v1.api_method) = {
      display_name: "Dry-run script"
      permissions: "EXECUTE_SCRIPT"
      scope: "instance.script"
      execution: "Server streaming"
      side_effects: "Streams runtime events for a dry-run execution."
    };
  }
}
