<?php

declare(strict_types=1);

namespace Drupal\flowdrop_orchestration\Service\Orchestrator;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\flowdrop_orchestration\Plugin\Orchestrator\OrchestratorPluginInterface;
use Psr\Log\LoggerInterface;

/**
 * Helper service for orchestrator-related operations.
 *
 * Centralizes common orchestrator interaction patterns including:
 * - Instance creation with fallback handling
 * - Default orchestrator resolution
 * - Form options building
 * - Orchestrator type validation and detection.
 *
 * This service reduces code duplication across modules and ensures consistent
 * behavior when working with orchestrators.
 *
 * @see \Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorHelperInterface
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class OrchestratorHelper implements OrchestratorHelperInterface {

  /**
   * The orchestrator plugin manager.
   *
   * @var \Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorPluginManager
   */
  protected OrchestratorPluginManager $pluginManager;

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected ConfigFactoryInterface $configFactory;

  /**
   * The logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected LoggerInterface $logger;

  /**
   * Constructs a new OrchestratorHelper.
   *
   * @param \Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorPluginManager $plugin_manager
   *   The orchestrator plugin manager.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger.
   */
  public function __construct(
    OrchestratorPluginManager $plugin_manager,
    ConfigFactoryInterface $config_factory,
    LoggerInterface $logger,
  ) {
    $this->pluginManager = $plugin_manager;
    $this->configFactory = $config_factory;
    $this->logger = $logger;
  }

  /**
   * {@inheritdoc}
   */
  public function getInstance(string $type, ?string $fallback = NULL): OrchestratorPluginInterface {
    $fallback = $fallback ?? self::ASYNCHRONOUS;

    // Handle "default" type by resolving to actual default.
    if ($type === "default") {
      $type = $this->resolveDefault();
    }

    // Validate the orchestrator type exists.
    if (!$this->pluginManager->hasDefinition($type)) {
      $this->logger->warning("Orchestrator type '@type' not available, falling back to '@fallback'.", [
        "@type" => $type,
        "@fallback" => $fallback,
      ]);
      $type = $fallback;

      // If even the fallback doesn't exist, this is a critical error.
      if (!$this->pluginManager->hasDefinition($type)) {
        throw new \RuntimeException(sprintf(
          "Neither orchestrator type '%s' nor fallback '%s' is available.",
          $type,
          $fallback
        ));
      }
    }

    return $this->pluginManager->createInstance($type);
  }

  /**
   * {@inheritdoc}
   */
  public function getInstanceFromSettings(
    array $settings,
    ?string $defaultType = NULL,
  ): OrchestratorPluginInterface {
    $defaultType = $defaultType ?? self::ASYNCHRONOUS;
    $type = $settings["type"] ?? $defaultType;

    return $this->getInstance($type, $defaultType);
  }

  /**
   * {@inheritdoc}
   */
  public function resolveDefault(?string $eventType = NULL): string {
    // Check for presave events - these must be synchronous.
    if ($eventType !== NULL && str_contains($eventType, ".presave")) {
      return self::SYNCHRONOUS;
    }

    // Try to get from trigger settings first.
    $triggerSettings = $this->configFactory->get("flowdrop_trigger.settings");
    $default = $triggerSettings->get("default_orchestrator");

    if ($default !== NULL && $this->validate($default)) {
      return $default;
    }

    // Fall back to general FlowDrop settings.
    $flowdropSettings = $this->configFactory->get("flowdrop.settings");
    $default = $flowdropSettings->get("execution.default_orchestrator");

    if ($default !== NULL && $this->validate($default)) {
      return $default;
    }

    // Final fallback to asynchronous.
    return self::ASYNCHRONOUS;
  }

  /**
   * {@inheritdoc}
   */
  public function validate(string $type): bool {
    // Handle "default" as valid - it will be resolved later.
    if ($type === "default") {
      return TRUE;
    }

    return $this->pluginManager->hasDefinition($type);
  }

  /**
   * {@inheritdoc}
   */
  public function getFormOptions(bool $includeDefault = FALSE): array {
    $options = [];

    // Optionally include "default" option at the start.
    if ($includeDefault) {
      $options["default"] = "Default (based on event)";
    }

    // Get all available orchestrator definitions.
    $definitions = $this->pluginManager->getDefinitions();

    foreach ($definitions as $id => $definition) {
      $label = $definition["label"] ?? ucfirst(str_replace("_", " ", $id));

      // Handle TranslatableMarkup objects.
      if ($label instanceof \Stringable) {
        $label = (string) $label;
      }

      $options[$id] = $label;
    }

    return $options;
  }

  /**
   * {@inheritdoc}
   */
  public function hasCapability(string $type, string $capability): bool {
    if (!$this->pluginManager->hasDefinition($type)) {
      return FALSE;
    }
    $definition = $this->pluginManager->getDefinition($type);
    $capabilities = $definition["capabilities"] ?? [];
    return in_array($capability, $capabilities, TRUE);
  }

  /**
   * {@inheritdoc}
   */
  public function isStateGraph(string $type): bool {
    return $this->hasCapability($type, "stateful");
  }

  /**
   * {@inheritdoc}
   */
  public function isSynchronous(string $type): bool {
    return $this->hasCapability($type, "synchronous_execution")
      && !$this->hasCapability($type, "stateful");
  }

}
