<?php

declare(strict_types=1);

namespace Drupal\flowdrop\Plugin\ExpressionEvaluator;

use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop\Attribute\ExpressionEvaluator;
use Symfony\Component\PropertyAccess\Exception\AccessException;
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;

/**
 * PropertyPath expression evaluator using Symfony PropertyAccessor.
 *
 * Supports bracket notation ([user][address][city]) and dot notation
 * (user.address.city) for extracting values from nested data structures.
 *
 * @internal
 *   This class is not part of the public API and may change without notice.
 */
#[ExpressionEvaluator(
  id: "property_path",
  label: new TranslatableMarkup("Property Path"),
  description: "Extract values using Symfony PropertyAccessor syntax (e.g., [user][address][city])",
)]
class PropertyPathEvaluator extends PluginBase implements ExpressionEvaluatorInterface {

  /**
   * The property accessor instance.
   */
  protected PropertyAccessorInterface $propertyAccessor;

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
  }

  /**
   * {@inheritdoc}
   */
  public function evaluate(string $expression, mixed $context): mixed {
    if ($expression === "") {
      return $context;
    }

    if (!is_array($context) && !is_object($context)) {
      return NULL;
    }

    $normalizedPath = $this->normalizePropertyPath($expression);

    try {
      if ($this->propertyAccessor->isReadable($context, $normalizedPath)) {
        return $this->propertyAccessor->getValue($context, $normalizedPath);
      }
    }
    catch (AccessException | UnexpectedTypeException) {
      // Path not accessible.
    }

    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function validate(string $expression): bool {
    if ($expression === "") {
      return TRUE;
    }

    // Basic syntax validation: must be bracket or dot notation.
    return (bool) preg_match('/^(\[[\w]+\])+$|^[\w]+(\.[\w]+)*$/', $expression);
  }

  /**
   * {@inheritdoc}
   */
  public function getSyntaxHint(): string {
    return "[user][address][city] or user.address.city";
  }

  /**
   * Normalize a property path to Symfony PropertyAccess format.
   *
   * Converts dot notation (user.name) to bracket notation ([user][name]).
   *
   * @param string $path
   *   The property path.
   *
   * @return string
   *   The normalized path.
   */
  protected function normalizePropertyPath(string $path): string {
    if (str_starts_with($path, "[")) {
      return $path;
    }

    $parts = explode(".", $path);
    return implode("", array_map(
      static fn(string $part): string => "[{$part}]",
      $parts
    ));
  }

}
