<?php

declare(strict_types=1);

namespace Drupal\flowdrop_node_type\Form\Trait;

use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\BranchingNodeProcessorInterface;

/**
 * Provides plugin introspection helpers for the node type form.
 *
 * Extracts plugin lookup, grouping, and schema retrieval methods
 * that are used during both plugin selection and configuration steps.
 *
 * Requirements for using this trait:
 * - The class must have a $nodeProcessorPluginManager property
 *   of type FlowDropNodeProcessorPluginManager
 * - The class must implement messenger() returning MessengerInterface
 * - The class must implement t() for translation
 *
 * @internal This trait is not part of the public API and may change without notice.
 */
trait NodeTypePluginHelperTrait {

  /**
   * Get available FlowDropNode plugins.
   *
   * @return array<string, string>
   *   Array of plugin IDs mapped to their labels.
   */
  protected function getAvailablePlugins(): array {
    $plugins = [];

    try {
      $definitions = $this->nodeProcessorPluginManager->getDefinitions();

      foreach ($definitions as $pluginId => $definition) {
        $label = $definition["label"];
        $plugins[$pluginId] = (string) $label;
      }

      asort($plugins);
    }
    catch (\Exception $e) {
      $this->messenger()->addError($this->t("Error loading plugins: @error", ["@error" => $e->getMessage()]));
    }

    return $plugins;
  }

  /**
   * Get plugins grouped by their provider module.
   *
   * Returns plugins organized by their providing module with full definition
   * data including label, description, and version.
   *
   * @return array<string, array<string, array<string, mixed>>>
   *   Nested array where first level keys are provider names and second level
   *   keys are plugin IDs with their full definition data.
   */
  protected function getPluginsGroupedByProvider(): array {
    $grouped = [];

    try {
      $definitions = $this->nodeProcessorPluginManager->getDefinitions();

      foreach ($definitions as $pluginId => $definition) {
        // Extract provider from the namespaced plugin ID.
        $provider = $this->nodeProcessorPluginManager->getProviderFromNamespacedId($pluginId);
        if ($provider === NULL) {
          $provider = $definition["provider"] ?? "unknown";
        }

        // Format the provider name for display.
        // Converts snake_case to Title Case.
        $providerDisplay = $this->formatProviderName($provider);

        // Extract label and description, casting to string if needed.
        $label = $definition["label"];
        $labelStr = (string) $label;

        $description = $definition["description"] ?? "";
        $descriptionStr = (string) $description;

        // Group by provider.
        if (!isset($grouped[$providerDisplay])) {
          $grouped[$providerDisplay] = [
            "provider_id" => $provider,
            "plugins" => [],
          ];
        }

        $grouped[$providerDisplay]["plugins"][$pluginId] = [
          "id" => $pluginId,
          "label" => $labelStr,
          "description" => $descriptionStr,
          "version" => $definition["version"] ?? "",
          "original_id" => $definition["original_id"] ?? $pluginId,
          "tags" => $this->nodeProcessorPluginManager->getCapabilityTags($pluginId),
        ];
      }

      // Sort providers alphabetically.
      ksort($grouped);

      // Sort plugins within each provider alphabetically by label.
      foreach ($grouped as $providerDisplay => &$providerData) {
        uasort($providerData["plugins"], function (array $a, array $b): int {
          return strcasecmp($a["label"], $b["label"]);
        });
      }
      // Unset reference to avoid PHP foreach reference bug.
      unset($providerData);
    }
    catch (\Exception $e) {
      $this->messenger()->addError($this->t("Error loading plugins: @error", ["@error" => $e->getMessage()]));
    }

    return $grouped;
  }

  /**
   * Check whether a plugin is a gateway (branching) node processor.
   *
   * @param string $pluginId
   *   The plugin ID.
   *
   * @return bool
   *   TRUE if the plugin implements BranchingNodeProcessorInterface.
   */
  protected function isGatewayPlugin(string $pluginId): bool {
    $definition = $this->getPluginDefinition($pluginId);
    $class = $definition["class"] ?? NULL;

    return is_string($class)
      && is_subclass_of($class, BranchingNodeProcessorInterface::class);
  }

  /**
   * Transform grouped plugins to the format expected by the SDC component.
   *
   * @param array<string, array<string, mixed>> $groupedPlugins
   *   Plugins grouped by provider from getPluginsGroupedByProvider().
   * @param array<string, array<int, array{label: string, url: string}>> $usageByPlugin
   *   Optional map of plugin ID to the node types already using it.
   *
   * @return array<int, array<string, mixed>>
   *   Array of plugin groups formatted for the SDC component.
   */
  protected function transformPluginsForComponent(array $groupedPlugins, array $usageByPlugin = []): array {
    $pluginGroups = [];

    foreach ($groupedPlugins as $providerDisplay => $providerData) {
      $plugins = [];

      foreach ($providerData["plugins"] as $pluginId => $pluginInfo) {
        $plugins[] = [
          "id" => $pluginId,
          "label" => $pluginInfo["label"],
          "description" => $pluginInfo["description"],
          "tags" => $pluginInfo["tags"] ?? [],
          "usages" => $usageByPlugin[$pluginId] ?? [],
        ];
      }

      $pluginGroups[] = [
        "provider_name" => $providerDisplay,
        "provider_id" => $providerData["provider_id"],
        "plugins" => $plugins,
      ];
    }

    return $pluginGroups;
  }

  /**
   * Format a provider module name for display.
   *
   * Converts snake_case module names to human-readable titles.
   * For example: "flowdrop_node_processor" becomes "Flowdrop Node Processor".
   *
   * @param string $provider
   *   The provider module name.
   *
   * @return string
   *   The formatted display name.
   */
  protected function formatProviderName(string $provider): string {
    // Replace underscores with spaces and capitalize each word.
    $formatted = str_replace("_", " ", $provider);
    return ucwords($formatted);
  }

  /**
   * Get the parameter schema from a plugin.
   *
   * @param string $pluginId
   *   The plugin ID.
   *
   * @return array<string, mixed>
   *   The plugin's parameter schema.
   */
  protected function getPluginParameterSchema(string $pluginId): array {
    try {
      $plugin = $this->nodeProcessorPluginManager->createInstance($pluginId);
      return $plugin->getParameterSchema();
    }
    catch (\Exception $e) {
      $this->messenger()->addError($this->t("Error loading parameter schema: @error", ["@error" => $e->getMessage()]));
      return [];
    }
  }

  /**
   * Get the output schema from a plugin.
   *
   * @param string $pluginId
   *   The plugin ID.
   *
   * @return array<string, mixed>
   *   The plugin's output schema.
   */
  protected function getPluginOutputSchema(string $pluginId): array {
    try {
      $plugin = $this->nodeProcessorPluginManager->createInstance($pluginId);
      return $plugin->getOutputSchema();
    }
    catch (\Exception $e) {
      $this->messenger()->addError($this->t("Error loading output schema: @error", ["@error" => $e->getMessage()]));
      return [];
    }
  }

  /**
   * Get the full plugin definition.
   *
   * @param string $pluginId
   *   The plugin ID.
   *
   * @return array<string, mixed>|null
   *   The plugin definition, or NULL if not found.
   */
  protected function getPluginDefinition(string $pluginId): ?array {
    if (empty($pluginId)) {
      return NULL;
    }

    try {
      $definition = $this->nodeProcessorPluginManager->getDefinition($pluginId, FALSE);
      return is_array($definition) ? $definition : NULL;
    }
    catch (\Exception $e) {
      return NULL;
    }
  }

  /**
   * Get the version of a plugin from its definition.
   *
   * @param string $pluginId
   *   The plugin ID.
   *
   * @return string
   *   The plugin version, or empty string if not found.
   */
  protected function getPluginVersion(string $pluginId): string {
    $definition = $this->getPluginDefinition($pluginId);
    if ($definition !== NULL && isset($definition["version"])) {
      return (string) $definition["version"];
    }
    return "";
  }

}
