<?php

declare(strict_types=1);

namespace Drupal\flowdrop_stategraph\Reducer;

/**
 * Reducer that performs deep array merge.
 *
 * This reducer is designed for nested data structures where updates
 * should be merged rather than replaced entirely. It recursively
 * merges arrays while replacing scalar values.
 *
 * Use cases:
 * - Accumulated metadata from multiple nodes.
 * - Configuration objects that are progressively built.
 * - Nested data transforms where partial updates are common.
 *
 * Merge behavior:
 * - Associative arrays are recursively merged by key.
 * - Numeric-keyed arrays are appended (not merged by index).
 * - Scalar values are replaced.
 * - NULL update values preserve the current value.
 *
 * Example:
 * @code
 * $reducer = new MergeReducer();
 *
 * $result = $reducer->reduce(
 *   ["user" => ["name" => "John", "age" => 30]],
 *   ["user" => ["email" => "john@example.com"]]
 * );
 * // Result: ["user" => ["name" => "John", "age" => 30, "email" => "john@example.com"]]
 * @endcode
 *
 * @internal This class is not part of the public API and may change without notice.
 */
final class MergeReducer implements ReducerInterface {

  /**
   * {@inheritdoc}
   */
  public function getId(): string {
    return "merge";
  }

  /**
   * {@inheritdoc}
   */
  public function reduce(mixed $currentValue, mixed $updateValue): mixed {
    // If update value is NULL, preserve current.
    if ($updateValue === NULL) {
      return $currentValue;
    }

    // If current value is not an array, just replace.
    if (!is_array($currentValue)) {
      return $updateValue;
    }

    // If update value is not an array, replace.
    if (!is_array($updateValue)) {
      return $updateValue;
    }

    return $this->deepMerge($currentValue, $updateValue);
  }

  /**
   * {@inheritdoc}
   */
  public function getDefaultValue(): mixed {
    return [];
  }

  /**
   * Performs deep recursive merge of two arrays.
   *
   * @param array<mixed> $base
   *   The base array to merge into.
   * @param array<mixed> $overlay
   *   The overlay array to merge from.
   *
   * @return array<mixed>
   *   The merged result.
   */
  private function deepMerge(array $base, array $overlay): array {
    foreach ($overlay as $key => $value) {
      // If key is numeric, append rather than merge.
      if (is_int($key)) {
        $base[] = $value;
      }
      // If both values are arrays and base has this key, recurse.
      elseif (isset($base[$key]) && is_array($base[$key]) && is_array($value)) {
        $base[$key] = $this->deepMerge($base[$key], $value);
      }
      // Otherwise, overlay replaces base.
      else {
        $base[$key] = $value;
      }
    }

    return $base;
  }

}
