<?php

declare(strict_types=1);

namespace Drupal\flowdrop_orchestration\DTO\Snapshot;

/**
 * Represents a chat message in the state graph.
 *
 * Messages are the primary data structure for conversational workflows,
 * including ReAct-style agent loops and multi-turn chat systems.
 *
 * Supported roles:
 * - "system": System/instruction messages.
 * - "user": User input messages.
 * - "assistant": AI/assistant responses.
 * - "tool": Tool/function call results.
 *
 * Example usage:
 * @code
 * $systemMsg = new Message("system", "You are a helpful assistant.");
 * $userMsg = new Message("user", "What is the weather?");
 * $assistantMsg = new Message("assistant", "Let me check...", [
 *   "tool_calls" => [["name" => "get_weather", "args" => ["location" => "NYC"]]],
 * ]);
 * $toolMsg = new Message("tool", "72°F, sunny", [
 *   "tool_call_id" => "call_123",
 * ]);
 * @endcode
 *
 * @internal
 */
final class Message implements \JsonSerializable {

  /**
   * Constructs a new Message.
   *
   * @param string $role
   *   The message role (system, user, assistant, tool).
   * @param string $content
   *   The message content.
   * @param array<string, mixed> $metadata
   *   Additional metadata (tool calls, tool_call_id, etc.).
   * @param int|null $timestamp
   *   Unix timestamp when the message was created.
   */
  public function __construct(
    public readonly string $role,
    public readonly string $content,
    public readonly array $metadata = [],
    public readonly ?int $timestamp = NULL,
  ) {}

  /**
   * Creates a system message.
   *
   * @param string $content
   *   The system instruction content.
   *
   * @return self
   *   A new Message with role "system".
   */
  public static function system(string $content): self {
    return new self("system", $content, [], time());
  }

  /**
   * Creates a user message.
   *
   * @param string $content
   *   The user message content.
   *
   * @return self
   *   A new Message with role "user".
   */
  public static function user(string $content): self {
    return new self("user", $content, [], time());
  }

  /**
   * Creates an assistant message.
   *
   * @param string $content
   *   The assistant response content.
   * @param array<string, mixed> $metadata
   *   Optional metadata (e.g., tool_calls).
   *
   * @return self
   *   A new Message with role "assistant".
   */
  public static function assistant(string $content, array $metadata = []): self {
    return new self("assistant", $content, $metadata, time());
  }

  /**
   * Creates a tool result message.
   *
   * @param string $content
   *   The tool result content.
   * @param string $toolCallId
   *   The ID of the tool call this is a response to.
   *
   * @return self
   *   A new Message with role "tool".
   */
  public static function tool(string $content, string $toolCallId): self {
    return new self("tool", $content, ["tool_call_id" => $toolCallId], time());
  }

  /**
   * Gets the tool calls from this message (if any).
   *
   * @return array<array{name: string, args: array<string, mixed>}>
   *   Array of tool calls or empty array.
   */
  public function getToolCalls(): array {
    return $this->metadata["tool_calls"] ?? [];
  }

  /**
   * Checks if this message has tool calls.
   *
   * @return bool
   *   TRUE if message has tool calls.
   */
  public function hasToolCalls(): bool {
    return !empty($this->metadata["tool_calls"]);
  }

  /**
   * Gets the tool call ID (for tool role messages).
   *
   * @return string|null
   *   The tool call ID or NULL.
   */
  public function getToolCallId(): ?string {
    $toolCallId = $this->metadata["tool_call_id"] ?? NULL;
    return $toolCallId !== NULL ? (string) $toolCallId : NULL;
  }

  /**
   * Creates a Message from an array.
   *
   * @param array<string, mixed> $data
   *   The message data.
   *
   * @return self
   *   A new Message instance.
   */
  public static function fromArray(array $data): self {
    return new self(
      role: (string) ($data["role"] ?? "user"),
      content: (string) ($data["content"] ?? ""),
      metadata: $data["metadata"] ?? [],
      timestamp: isset($data["timestamp"]) ? (int) $data["timestamp"] : NULL,
    );
  }

  /**
   * Converts the message to an array.
   *
   * @return array<string, mixed>
   *   The message as an array.
   */
  public function toArray(): array {
    return [
      "role" => $this->role,
      "content" => $this->content,
      "metadata" => $this->metadata,
      "timestamp" => $this->timestamp,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function jsonSerialize(): mixed {
    return $this->toArray();
  }

}
