<?php

declare(strict_types=1);

namespace Drupal\native_observability_spans\Service;

use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Keeps track of the active span stack for the current HTTP request.
 */
final class ActiveSpanContextStack {

  /**
   * Request attribute key used to store the span stack.
   */
  private const ATTRIBUTE_KEY = 'native_observability.active_span_stack';

  /**
   * Creates a new active span context stack instance.
   */
  public function __construct(
    private readonly RequestStack $requestStack,
  ) {
  }

  /**
   * Pushes a new span identifier on the active stack.
   */
  public function push(string $spanId): void {
    $request = $this->requestStack->getCurrentRequest();
    if (!$request) {
      return;
    }

    $stack = $request->attributes->get(self::ATTRIBUTE_KEY, []);
    if (!is_array($stack)) {
      $stack = [];
    }

    $stack[] = $spanId;
    $request->attributes->set(self::ATTRIBUTE_KEY, $stack);
  }

  /**
   * Pops the latest active span identifier from the stack.
   */
  public function pop(?string $expectedSpanId = NULL): ?string {
    $request = $this->requestStack->getCurrentRequest();
    if (!$request) {
      return NULL;
    }

    $stack = $request->attributes->get(self::ATTRIBUTE_KEY, []);
    if (!is_array($stack) || $stack === []) {
      return NULL;
    }

    $last = array_pop($stack);

    if ($expectedSpanId !== NULL && $last !== $expectedSpanId) {
      $stack = array_values(array_filter(
        $stack,
        static fn($value): bool => $value !== $expectedSpanId
      ));
    }

    $request->attributes->set(self::ATTRIBUTE_KEY, $stack);

    return is_string($last) ? $last : NULL;
  }

  /**
   * Returns the current active span identifier.
   */
  public function getCurrentSpanId(): ?string {
    $request = $this->requestStack->getCurrentRequest();
    if (!$request) {
      return NULL;
    }

    $stack = $request->attributes->get(self::ATTRIBUTE_KEY, []);
    if (!is_array($stack) || $stack === []) {
      return NULL;
    }

    $current = end($stack);

    return is_string($current) ? $current : NULL;
  }

}
