<?php

declare(strict_types=1);

namespace Drupal\flowdrop_memory\Plugin\FlowDropNodeProcessor;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop\Attribute\FlowDropNodeProcessor;
use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\flowdrop\DTO\ValidationError;
use Drupal\flowdrop\DTO\ValidationResult;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\AbstractFlowDropNodeProcessor;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\ExecutionContextAwareInterface;
use Drupal\flowdrop_memory\Service\MemoryManager;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Context-aware memory read node processor.
 *
 * Reads a value from the configured memory backend. The scope ID is
 * automatically derived from the current execution context:
 * - scope=workflow  → workflow ID
 * - scope=pipeline  → pipeline ID
 * - scope=execution → execution ID
 * - scope=session   → session_id from execution metadata
 * - scope=global    → no sub-scope (empty string)
 *
 * The resolved scope_id is exposed as an output for downstream debugging.
 *
 * @see \Drupal\flowdrop_memory\Plugin\FlowDropNodeProcessor\MemoryReadRaw
 * @see \Drupal\flowdrop_memory\Service\MemoryManager
 */
#[FlowDropNodeProcessor(
  id: 'memory_read',
  label: new TranslatableMarkup('Memory Read'),
  description: 'Read a value from scoped memory. Scope ID is automatically derived from the execution context.',
  version: '1.0.0',
)]
class MemoryRead extends AbstractFlowDropNodeProcessor implements ExecutionContextAwareInterface {

  use ScopeResolutionTrait;

  /**
   * {@inheritdoc}
   */
  public function __construct(
    array $configuration,
    string $plugin_id,
    mixed $plugin_definition,
    private readonly MemoryManager $memoryManager,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('flowdrop_memory.manager'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function validateParams(array $params): ValidationResult {
    $errors = [];
    if (empty($params['key'])) {
      $errors[] = new ValidationError('key', 'Memory key is required.');
    }
    return empty($errors) ? ValidationResult::success() : ValidationResult::failure($errors);
  }

  /**
   * {@inheritdoc}
   */
  public function process(ParameterBagInterface $params): array {
    $scope = $params->getString('scope', 'global');
    $key = $params->getString('key');
    $default = $params->get('default_value');
    $backend = $params->getString('backend', 'static');

    $resolvedScopeId = $this->resolveScopeId($scope);

    $value = $this->memoryManager->get($scope, $resolvedScopeId, $key, $default, $backend);
    $found = $this->memoryManager->has($scope, $resolvedScopeId, $key, $backend);

    return [
      'value'            => $value,
      'found'            => $found,
      'key'              => $key,
      'scope'            => $scope,
      'resolved_scope_id' => $resolvedScopeId,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    return [
      'type' => 'object',
      'properties' => [
        'scope' => [
          'type'        => 'string',
          'title'       => 'Scope',
          'description' => 'The memory scope. Common values: global, workflow, pipeline, execution, session.',
          'enum'        => ['global', 'workflow', 'pipeline', 'execution', 'session'],
          'default'     => 'global',
        ],
        'key' => [
          'type'        => 'string',
          'title'       => 'Key',
          'description' => 'The memory key to read.',
        ],
        'default_value' => [
          'type'        => 'mixed',
          'title'       => 'Default Value',
          'description' => 'Value to return if the key does not exist.',
          'default'     => NULL,
        ],
        'backend' => [
          'type'        => 'string',
          'title'       => 'Memory Backend',
          'description' => 'The storage backend to use.',
          'enum'        => ['static', 'cached', 'entity'],
          'default'     => 'static',
        ],
        'loop_back' => [
          'type'                => 'any',
          'description'         => 'Loopback trigger — connect the loop body output here to re-read memory each iteration. The value is ignored.',
          'is_loopback'         => TRUE,
          'loopback_metadata'   => [
            'edge_style'           => 'dotted',
            'skip_cycle_detection' => TRUE,
            'label'                => 'Loop Back',
          ],
        ],
      ],
      'required' => ['key', 'backend'],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    return [
      'type' => 'object',
      'properties' => [
        'value' => [
          'type'        => 'mixed',
          'description' => 'The retrieved value, or the default value if not found.',
        ],
        'found' => [
          'type'        => 'boolean',
          'description' => 'Whether the key existed in memory.',
        ],
        'key' => [
          'type'        => 'string',
          'description' => 'The key that was read.',
        ],
        'scope' => [
          'type'        => 'string',
          'description' => 'The scope used for the read.',
        ],
        'resolved_scope_id' => [
          'type'        => 'string',
          'description' => 'The scope ID that was actually used (after auto-resolution).',
        ],
      ],
    ];
  }

}
