<?php

declare(strict_types=1);

namespace Drupal\flowdrop_node_type\Entity;

use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\Attribute\ConfigEntityType;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop_node_type\FlowDropNodeTypeInterface;
use Drupal\flowdrop_node_type\FlowDropNodeTypeListBuilder;
use Drupal\flowdrop_node_type\Form\FlowDropNodeTypeForm;

/**
 * Defines the flowdrop node type entity type.
 *
 * @internal
 *   This class is not part of the public API and may change without notice.
 */
#[ConfigEntityType(
  id: 'flowdrop_node_type',
  label: new TranslatableMarkup('FlowDrop Node Type'),
  label_collection: new TranslatableMarkup('FlowDrop Node Types'),
  label_singular: new TranslatableMarkup('FlowDrop Node Type'),
  label_plural: new TranslatableMarkup('FlowDrop Node Types'),
  config_prefix: 'flowdrop_node_type',
  entity_keys: [
    'id' => 'id',
    'label' => 'label',
    'uuid' => 'uuid',
  ],
  handlers: [
    'list_builder' => FlowDropNodeTypeListBuilder::class,
    'route_provider' => [
      'html' => AdminHtmlRouteProvider::class,
    ],
    'form' => [
      'add' => FlowDropNodeTypeForm::class,
      'edit' => FlowDropNodeTypeForm::class,
      'delete' => EntityDeleteForm::class,
    ],
  ],
  links: [
    'collection' => '/admin/flowdrop/config/node-types',
    'add-form' => '/admin/flowdrop/config/node-types/add',
    'edit-form' => '/admin/flowdrop/config/node-types/{flowdrop_node_type}',
    'delete-form' => '/admin/flowdrop/config/node-types/{flowdrop_node_type}/delete',
  ],
  admin_permission: 'administer flowdrop_node_type',
  label_count: [
    'singular' => '@count flowdrop node type',
    'plural' => '@count flowdrop node types',
  ],
  config_export: [
    'id',
    'label',
    'uuid',
    'description',
    'category',
    'icon',
    'plugin_version',
    'enabled',
    'parameters',
    'outputs',
    'tags',
    'executor_plugin',
    'visual_type',
    'supported_visual_types',
    'unified_input_exposed',
    'unified_output_exposed',
    'tool_exposed',
  ],
)]
final class FlowDropNodeType extends ConfigEntityBase implements FlowDropNodeTypeInterface {

  /**
   * The node type ID.
   */
  protected string $id;

  /**
   * The node type label.
   */
  protected string $label;

  /**
   * The node type description.
   */
  protected string $description = '';

  /**
   * The node category.
   */
  protected string $category = 'processing';

  /**
   * The node icon.
   */
  protected string $icon = 'mdi:cog';

  /**
   * The plugin version stored when this config was saved.
   *
   * This is automatically captured from the selected executor plugin's version
   * attribute and is used to detect if the plugin has been updated since this
   * config was last saved.
   */
  protected string $plugin_version = '';

  /**
   * Whether the node type is enabled.
   */
  protected bool $enabled = TRUE;

  /**
   * The unified parameter configuration.
   *
   * Structure:
   * [
   *   'paramName' => [
   *     'configurable' => true,
   *     'connectable' => false,
   *     'required' => false,
   *     'default' => 'value',
   *   ],
   * ]
   *
   * @var array<string, array<string, mixed>>
   */
  protected array $parameters = [];

  /**
   * The output configuration.
   *
   * Structure:
   * [
   *   'outputName' => [
   *     'exposed' => true,
   *   ],
   * ]
   *
   * @var array<string, array<string, mixed>>
   */
  protected array $outputs = [];

  /**
   * The node tags.
   *
   * @var array<string>
   */
  protected array $tags = [];

  /**
   * The executor plugin ID.
   */
  protected string $executor_plugin = '';

  /**
   * The visual type for rendering in the workflow editor.
   *
   * This determines how the node appears visually (e.g., "default", "gateway").
   * Site builders can choose the appropriate visual representation.
   */
  protected string $visual_type = 'default';

  /**
   * The supported visual types for this node.
   *
   * When multiple types are supported, users can change the visual type
   * per node instance in the workflow editor.
   *
   * @var array<string>
   */
  protected array $supported_visual_types = ['default'];

  /**
   * Whether the unified input port is exposed.
   *
   * When enabled, adds an "input" port (reserved name) that accepts all
   * connectable parameters as a single JSON object.
   */
  protected bool $unified_input_exposed = FALSE;

  /**
   * Whether the unified output port is exposed.
   *
   * When enabled, adds an "output" port (reserved name) that provides all
   * exposed outputs as a single JSON object.
   */
  protected bool $unified_output_exposed = FALSE;

  /**
   * Whether instances of this node type can be wired as tools.
   *
   * When enabled, a "tool" output port (reserved name) is exposed so the node
   * can supply itself as a callable tool to tool-aware consumers.
   */
  protected bool $tool_exposed = FALSE;

  /**
   * {@inheritdoc}
   *
   * Adds a module dependency for the provider of the executor plugin, so
   * uninstalling that module triggers Drupal's config dependency system to
   * delete the node types (and, transitively, the workflows) that use it.
   *
   * The provider is the first segment of the plugin id. This is not a fragile
   * convention: FlowDropNodeProcessorPluginManager *constructs* every plugin id
   * as "{provider}:{original_id}" (derivatives included), so the prefix is
   * always the real provider — there are no bare ids and the prefix cannot
   * diverge from the provider. String parsing is therefore both correct and
   * deliberately preferred over a plugin-manager lookup, which would be
   * unreliable during the module install/uninstall transitions when this runs.
   */
  public function calculateDependencies(): static {
    parent::calculateDependencies();

    if ($this->executor_plugin !== '' && str_contains($this->executor_plugin, ':')) {
      $parts = explode(':', $this->executor_plugin, 2);
      $provider = $parts[0];
      if ($provider !== '') {
        $this->addDependency('module', $provider);
      }
    }

    return $this;
  }

  /**
   * Get the label.
   *
   * @return string
   *   The label.
   */
  public function getLabel(): string {
    return $this->label;
  }

  /**
   * Set the label.
   *
   * @param string $label
   *   The label.
   *
   * @return static
   *   The node entity.
   */
  public function setLabel(string $label): static {
    $this->label = $label;
    return $this;
  }

  /**
   * Get the description.
   *
   * @return string
   *   The description.
   */
  public function getDescription(): string {
    return $this->description;
  }

  /**
   * Set the description.
   *
   * @param string $description
   *   The description.
   *
   * @return static
   *   The node entity.
   */
  public function setDescription(string $description): static {
    $this->description = $description;
    return $this;
  }

  /**
   * Get the category.
   *
   * @return string
   *   The category.
   */
  public function getCategory(): string {
    return $this->category;
  }

  /**
   * Set the category.
   *
   * @param string $category
   *   The category.
   *
   * @return static
   *   The node entity.
   */
  public function setCategory(string $category): static {
    $this->category = $category;
    return $this;
  }

  /**
   * Get the icon.
   *
   * @return string
   *   The icon.
   */
  public function getIcon(): string {
    return $this->icon;
  }

  /**
   * Set the icon.
   *
   * @param string $icon
   *   The icon.
   *
   * @return static
   *   The node entity.
   */
  public function setIcon(string $icon): static {
    $this->icon = $icon;
    return $this;
  }

  /**
   * Get the plugin version stored when this config was saved.
   *
   * @return string
   *   The plugin version.
   */
  public function getPluginVersion(): string {
    return $this->plugin_version;
  }

  /**
   * Set the plugin version.
   *
   * This should be set automatically from the selected executor plugin's
   * version, not manually by users.
   *
   * @param string $plugin_version
   *   The plugin version.
   *
   * @return static
   *   The node entity.
   */
  public function setPluginVersion(string $plugin_version): static {
    $this->plugin_version = $plugin_version;
    return $this;
  }

  /**
   * Check if the node type is enabled.
   *
   * @return bool
   *   TRUE if enabled.
   */
  public function isEnabled(): bool {
    return $this->enabled;
  }

  /**
   * Set the enabled status.
   *
   * @param bool $enabled
   *   The enabled status.
   *
   * @return static
   *   The node entity.
   */
  public function setEnabled(bool $enabled): static {
    $this->enabled = $enabled;
    return $this;
  }

  /**
   * Get the unified parameter configuration.
   *
   * @return array<string, array<string, mixed>>
   *   The parameter configuration.
   */
  public function getParameters(): array {
    return $this->parameters;
  }

  /**
   * Set the unified parameter configuration.
   *
   * @param array<string, array<string, mixed>> $parameters
   *   The parameter configuration.
   *
   * @return static
   *   The node entity.
   */
  public function setParameters(array $parameters): static {
    $this->parameters = $parameters;
    return $this;
  }

  /**
   * Get configuration for a specific parameter.
   *
   * @param string $name
   *   The parameter name.
   *
   * @return array<string, mixed>|null
   *   The parameter configuration, or NULL if not found.
   */
  public function getParameter(string $name): ?array {
    return $this->parameters[$name] ?? NULL;
  }

  /**
   * Set configuration for a specific parameter.
   *
   * @param string $name
   *   The parameter name.
   * @param array<string, mixed> $config
   *   The parameter configuration.
   *
   * @return static
   *   The node entity.
   */
  public function setParameter(string $name, array $config): static {
    $this->parameters[$name] = $config;
    return $this;
  }

  /**
   * Get the output configuration.
   *
   * @return array<string, array<string, mixed>>
   *   The output configuration.
   */
  public function getOutputs(): array {
    return $this->outputs;
  }

  /**
   * Set the output configuration.
   *
   * @param array<string, array<string, mixed>> $outputs
   *   The output configuration.
   *
   * @return static
   *   The node entity.
   */
  public function setOutputs(array $outputs): static {
    $this->outputs = $outputs;
    return $this;
  }

  /**
   * Get configuration for a specific output.
   *
   * @param string $name
   *   The output name.
   *
   * @return array<string, mixed>|null
   *   The output configuration, or NULL if not found.
   */
  public function getOutput(string $name): ?array {
    return $this->outputs[$name] ?? NULL;
  }

  /**
   * Set configuration for a specific output.
   *
   * @param string $name
   *   The output name.
   * @param array<string, mixed> $config
   *   The output configuration.
   *
   * @return static
   *   The node entity.
   */
  public function setOutput(string $name, array $config): static {
    $this->outputs[$name] = $config;
    return $this;
  }

  /**
   * Get the tags.
   *
   * @return array
   *   The tags.
   */
  public function getTags(): array {
    return $this->tags;
  }

  /**
   * Set the tags.
   *
   * @param array $tags
   *   The tags.
   *
   * @return static
   *   The node entity.
   */
  public function setTags(array $tags): static {
    $this->tags = $tags;
    return $this;
  }

  /**
   * Get the executor plugin ID.
   *
   * @return string
   *   The executor plugin ID.
   */
  public function getExecutorPlugin(): string {
    return $this->executor_plugin;
  }

  /**
   * Set the executor plugin ID.
   *
   * @param string $executor_plugin
   *   The executor plugin ID.
   *
   * @return static
   *   The node entity.
   */
  public function setExecutorPlugin(string $executor_plugin): static {
    $this->executor_plugin = $executor_plugin;
    return $this;
  }

  /**
   * Get the visual type for this node.
   *
   * @return string
   *   The visual type (e.g., "default", "gateway", "simple", "square").
   */
  public function getVisualType(): string {
    return $this->visual_type;
  }

  /**
   * Set the visual type for this node.
   *
   * @param string $visual_type
   *   The visual type.
   *
   * @return static
   *   The node entity.
   */
  public function setVisualType(string $visual_type): static {
    $this->visual_type = $visual_type;
    return $this;
  }

  /**
   * Get the supported visual types for this node.
   *
   * @return array<string>
   *   Array of supported visual type keys.
   */
  public function getSupportedVisualTypes(): array {
    return $this->supported_visual_types;
  }

  /**
   * Set the supported visual types for this node.
   *
   * @param array<string> $supported_visual_types
   *   Array of supported visual type keys.
   *
   * @return static
   *   The node entity.
   */
  public function setSupportedVisualTypes(array $supported_visual_types): static {
    $this->supported_visual_types = $supported_visual_types;
    return $this;
  }

  /**
   * Check if the unified input port is exposed.
   *
   * @return bool
   *   TRUE if the unified input port should be shown.
   */
  public function isUnifiedInputExposed(): bool {
    return $this->unified_input_exposed;
  }

  /**
   * Set the unified input port visibility.
   *
   * @param bool $exposed
   *   TRUE to show the unified input port.
   *
   * @return static
   *   The node entity.
   */
  public function setUnifiedInputExposed(bool $exposed): static {
    $this->unified_input_exposed = $exposed;
    return $this;
  }

  /**
   * Check if the unified output port is exposed.
   *
   * @return bool
   *   TRUE if the unified output port should be shown.
   */
  public function isUnifiedOutputExposed(): bool {
    return $this->unified_output_exposed;
  }

  /**
   * Set the unified output port visibility.
   *
   * @param bool $exposed
   *   TRUE to show the unified output port.
   *
   * @return static
   *   The node entity.
   */
  public function setUnifiedOutputExposed(bool $exposed): static {
    $this->unified_output_exposed = $exposed;
    return $this;
  }

  /**
   * Check if this node type is exposed as a tool.
   *
   * @return bool
   *   TRUE if instances can be wired as tools.
   */
  public function isToolExposed(): bool {
    return $this->tool_exposed;
  }

  /**
   * Set whether this node type is exposed as a tool.
   *
   * @param bool $exposed
   *   TRUE to expose a tool output port.
   *
   * @return static
   *   The node entity.
   */
  public function setToolExposed(bool $exposed): static {
    $this->tool_exposed = $exposed;
    return $this;
  }

  /**
   * Transform singular category to plural form for API compatibility.
   *
   * @param string $category
   *   The singular category.
   *
   * @return string
   *   The plural category.
   */
  protected function transformCategoryToPlural(string $category): string {
    $category_mapping = [
      'input' => 'inputs',
      'output' => 'outputs',
      'model' => 'models',
      'prompt' => 'prompts',
      'processing' => 'processing',
      'logic' => 'logic',
      'data' => 'data',
      'helper' => 'helpers',
      'tool' => 'tools',
      'vectorstore' => 'vectorstores',
      'embedding' => 'embeddings',
      'memory' => 'memories',
      'agent' => 'agents',
      'bundle' => 'bundles',
    ];

    return $category_mapping[$category] ?? $category;
  }

  /**
   * Convert to node definition format.
   *
   * Produces a node definition array for API responses using the
   * Unified Parameter System.
   *
   * Note: Color is not included here as it should be inherited from the
   * node's category. The calling code is responsible for looking up the
   * category color if needed.
   *
   * @return array<string, mixed>
   *   The node definition array.
   */
  public function toNodeDefinition(): array {
    return [
      "id" => $this->id,
      "name" => $this->label,
      "type" => $this->visual_type,
      "supportedTypes" => $this->supported_visual_types,
      "plugin_version" => $this->plugin_version,
      "description" => $this->description,
      "category" => $this->transformCategoryToPlural($this->category),
      "icon" => $this->icon,
      "tags" => $this->tags,
      "enabled" => $this->enabled,
      "parameters" => $this->parameters,
      "outputs" => $this->outputs,
      "executor_plugin" => $this->executor_plugin,
    ];
  }

}
