<?php

declare(strict_types=1);

namespace Drupal\flowdrop\DTO\NodeMetadata;

/**
 * Value object representing a node port (input or output).
 *
 * Ports are connection points on nodes in the workflow editor.
 * Each port has a type (input/output), data type, and metadata
 * describing how it can be connected to other nodes.
 *
 * @see \Drupal\flowdrop_node_type\Controller\Api\NodesController::transformSchemaToPorts()
 *
 * @api
 */
final class NodePort {

  /**
   * Port type constant for input ports.
   */
  public const TYPE_INPUT = "input";

  /**
   * Port type constant for output ports.
   */
  public const TYPE_OUTPUT = "output";

  /**
   * Constructs a NodePort instance.
   *
   * @param string $id
   *   The port identifier (e.g., "input_value", "output", "trigger").
   * @param string $name
   *   The human-readable port name.
   * @param string $type
   *   The port type: "input" or "output".
   * @param string $dataType
   *   The data type: "string", "number", "boolean", "json", "trigger", etc.
   * @param bool $required
   *   Whether the port connection is required.
   * @param string $description
   *   The port description.
   * @param mixed $defaultValue
   *   The default value for this port (optional).
   */
  public function __construct(
    public readonly string $id,
    public readonly string $name,
    public readonly string $type,
    public readonly string $dataType,
    public readonly bool $required = FALSE,
    public readonly string $description = "",
    public readonly mixed $defaultValue = NULL,
  ) {}

  /**
   * Creates an input port instance.
   *
   * @param string $id
   *   The port identifier.
   * @param string $name
   *   The human-readable port name.
   * @param string $dataType
   *   The data type.
   * @param bool $required
   *   Whether the port connection is required.
   * @param string $description
   *   The port description.
   * @param mixed $defaultValue
   *   The default value for this port.
   *
   * @return self
   *   A new input port instance.
   */
  public static function input(
    string $id,
    string $name,
    string $dataType,
    bool $required = FALSE,
    string $description = "",
    mixed $defaultValue = NULL,
  ): self {
    return new self(
      id: $id,
      name: $name,
      type: self::TYPE_INPUT,
      dataType: $dataType,
      required: $required,
      description: $description,
      defaultValue: $defaultValue,
    );
  }

  /**
   * Creates an output port instance.
   *
   * @param string $id
   *   The port identifier.
   * @param string $name
   *   The human-readable port name.
   * @param string $dataType
   *   The data type.
   * @param string $description
   *   The port description.
   *
   * @return self
   *   A new output port instance.
   */
  public static function output(
    string $id,
    string $name,
    string $dataType,
    string $description = "",
  ): self {
    return new self(
      id: $id,
      name: $name,
      type: self::TYPE_OUTPUT,
      dataType: $dataType,
      required: FALSE,
      description: $description,
      defaultValue: NULL,
    );
  }

  /**
   * Creates an instance from an array.
   *
   * @param array<string, mixed> $data
   *   The array data containing port properties.
   *   Expected keys: id, name, type, dataType, required, description,
   *   defaultValue.
   *
   * @return self
   *   A new NodePort instance.
   */
  public static function fromArray(array $data): self {
    return new self(
      id: (string) ($data["id"] ?? ""),
      name: (string) ($data["name"] ?? $data["id"] ?? ""),
      type: (string) ($data["type"] ?? self::TYPE_INPUT),
      dataType: (string) ($data["dataType"] ?? "string"),
      required: (bool) ($data["required"] ?? FALSE),
      description: (string) ($data["description"] ?? ""),
      defaultValue: $data["defaultValue"] ?? NULL,
    );
  }

  /**
   * Converts the port to an array suitable for JSON serialization.
   *
   * @return array<string, mixed>
   *   The array representation matching the API response format.
   */
  public function toArray(): array {
    $result = [
      "id" => $this->id,
      "name" => $this->name,
      "type" => $this->type,
      "dataType" => $this->dataType,
      "required" => $this->required,
      "description" => $this->description,
    ];

    // Only include defaultValue if it's set.
    if ($this->defaultValue !== NULL) {
      $result["defaultValue"] = $this->defaultValue;
    }

    return $result;
  }

  /**
   * Checks if this is an input port.
   *
   * @return bool
   *   TRUE if this is an input port.
   */
  public function isInput(): bool {
    return $this->type === self::TYPE_INPUT;
  }

  /**
   * Checks if this is an output port.
   *
   * @return bool
   *   TRUE if this is an output port.
   */
  public function isOutput(): bool {
    return $this->type === self::TYPE_OUTPUT;
  }

  /**
   * Checks if this port has a default value.
   *
   * @return bool
   *   TRUE if a default value is set.
   */
  public function hasDefaultValue(): bool {
    return $this->defaultValue !== NULL;
  }

}
