<?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;

/**
 * Conversation buffer node processor.
 *
 * Appends a single message {role, content, timestamp} to a stored
 * conversation history list and writes it back atomically. Scope ID is
 * auto-resolved from the execution context.
 *
 * Covers the LangChain ConversationBufferMemory and
 * ConversationBufferWindowMemory patterns in a single node.
 *
 * Set max_messages > 0 to enable a sliding window (window memory).
 * The oldest messages are discarded first.
 *
 * @see \Drupal\flowdrop_memory\Plugin\FlowDropNodeProcessor\MemoryDelete
 */
#[FlowDropNodeProcessor(
  id: 'conversation_buffer',
  label: new TranslatableMarkup('Conversation Buffer'),
  description: 'Append a message to the conversation buffer. Scope ID is automatically derived from the execution context.',
  version: '1.0.0',
)]
class ConversationBuffer 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['role'])) {
      $errors[] = new ValidationError('role', 'Role is required.');
    }
    if (!array_key_exists('content', $params) || $params['content'] === NULL) {
      $errors[] = new ValidationError('content', 'Content is required.');
    }
    return empty($errors) ? ValidationResult::success() : ValidationResult::failure($errors);
  }

  /**
   * {@inheritdoc}
   */
  public function process(ParameterBagInterface $params): array {
    $scope = $params->getString('scope', 'session');
    $key = $params->getString('key', 'chat_history');
    $role = $params->getString('role');
    $content = $params->get('content');
    $maxMessages = $params->getInt('max_messages', 0);
    $backend = $params->getString('backend', 'static');

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

    $messages = $this->memoryManager->get($scope, $resolvedScopeId, $key, [], $backend);
    if (!is_array($messages)) {
      $messages = [];
    }

    $messages[] = [
      'role'      => $role,
      'content'   => $content,
      'timestamp' => time(),
    ];

    if ($maxMessages > 0 && count($messages) > $maxMessages) {
      $messages = array_slice($messages, -$maxMessages);
    }

    $this->memoryManager->set($scope, $resolvedScopeId, $key, $messages, NULL, $backend);

    return [
      'messages'          => $messages,
      'count'             => count($messages),
      '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.',
          'enum'        => ['global', 'workflow', 'pipeline', 'execution', 'session'],
          'default'     => 'session',
        ],
        'key' => [
          'type'        => 'string',
          'title'       => 'Key',
          'description' => 'Memory key for the conversation buffer.',
          'default'     => 'chat_history',
        ],
        'role' => [
          'type'        => 'string',
          'title'       => 'Role',
          'description' => 'The message role.',
          'enum'        => ['user', 'assistant', 'system'],
        ],
        'content' => [
          'type'        => 'mixed',
          'title'       => 'Content',
          'description' => 'The message content.',
        ],
        'max_messages' => [
          'type'        => 'integer',
          'title'       => 'Max Messages',
          'description' => 'Sliding window size. 0 means unlimited (buffer memory).',
          'default'     => 0,
          'minimum'     => 0,
        ],
        'backend' => [
          'type'        => 'string',
          'title'       => 'Memory Backend',
          'description' => 'The storage backend to use.',
          'enum'        => ['static', 'cached', 'entity'],
          'default'     => 'static',
        ],
      ],
      'required' => ['role', 'content', 'backend'],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    return [
      'type' => 'object',
      'properties' => [
        'messages' => [
          'type'        => 'array',
          'description' => 'Full conversation buffer as [{role, content, timestamp}].',
        ],
        'count' => [
          'type'        => 'integer',
          'description' => 'Number of messages currently in the buffer.',
        ],
        'key' => [
          'type'        => 'string',
          'description' => 'The key used to store the buffer.',
        ],
        'scope' => [
          'type'        => 'string',
          'description' => 'The scope used.',
        ],
        'resolved_scope_id' => [
          'type'        => 'string',
          'description' => 'The scope ID that was actually used (after auto-resolution).',
        ],
      ],
    ];
  }

}
