<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Hook;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Drupal\ffmpeg_media\Cdn\CdnManagerInterface;

/**
 * File URL hook implementations for CDN URL rewriting.
 */
final class FileUrlHooks {

  /**
   * Cached CDN enabled state (NULL = not yet checked).
   */
  private ?bool $cdnEnabled = NULL;

  /**
   * Cached configured output directory URI prefix.
   */
  private ?string $outputDirectoryPrefix = NULL;

  /**
   * Cached web path prefixes corresponding to output directory.
   *
   * @var array<int, string>
   */
  private array $outputWebPrefixes = [];

  /**
   * Constructs a FileUrlHooks object.
   *
   * @param \Drupal\ffmpeg_media\Cdn\CdnManagerInterface $cdnManager
   *   CDN manager.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   Config factory.
   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $streamWrapperManager
   *   Stream wrapper manager for resolving file paths.
   */
  public function __construct(
    private readonly CdnManagerInterface $cdnManager,
    private readonly ConfigFactoryInterface $configFactory,
    private readonly StreamWrapperManagerInterface $streamWrapperManager,
  ) {}

  /**
   * Implements hook_file_url_alter().
   *
   * Rewrites local file URLs to CDN URLs when CDN URL rewriting is enabled.
   * Only affects files under the configured FFmpeg output directory.
   *
   * @param string $uri
   *   The file URI to potentially rewrite.
   */
  #[Hook('file_url_alter')]
  public function fileUrlAlter(string &$uri): void {
    if (!$this->isManagedOutputUri($uri)) {
      return;
    }

    // Cache the CDN enabled check for the request lifetime.
    if ($this->cdnEnabled === NULL) {
      $this->cdnEnabled = $this->cdnManager->isEnabled();
    }

    if (!$this->cdnEnabled) {
      return;
    }

    $integration = $this->cdnManager->getActiveIntegration();
    if ($integration === NULL) {
      return;
    }

    $rewritten = $integration->rewriteUrl($uri);
    if ($rewritten !== $uri) {
      $uri = $rewritten;
    }
  }

  /**
   * Determines if a URI belongs to configured FFmpeg output storage.
   *
   * @param string $uri
   *   URI/URL to check.
   *
   * @return bool
   *   TRUE when the URI should be considered for CDN rewrite.
   */
  private function isManagedOutputUri(string $uri): bool {
    if ($this->outputDirectoryPrefix === NULL) {
      $this->initializeOutputDirectoryPrefixes();
    }

    if ($this->outputDirectoryPrefix === NULL) {
      return FALSE;
    }
    $prefix = $this->outputDirectoryPrefix;
    if ($uri === $prefix || str_starts_with($uri, $prefix . '/')) {
      return TRUE;
    }

    $path = parse_url($uri, PHP_URL_PATH);
    if (!is_string($path)) {
      return FALSE;
    }

    foreach ($this->outputWebPrefixes as $webPrefix) {
      if ($path === $webPrefix || str_starts_with($path, $webPrefix . '/')) {
        return TRUE;
      }
    }

    return FALSE;
  }

  /**
   * Initializes output directory URI/web path prefixes for request cache.
   */
  private function initializeOutputDirectoryPrefixes(): void {
    $settings = $this->configFactory->get('ffmpeg_media.settings');
    $outputDirectory = (string) ($settings->get('output_directory') ?? 'public://ffmpeg_media');
    $outputDirectory = rtrim($outputDirectory, '/');
    if ($outputDirectory === '') {
      $outputDirectory = 'public://ffmpeg_media';
    }

    $this->outputDirectoryPrefix = $outputDirectory;
    $this->outputWebPrefixes = $this->buildWebPrefixes($outputDirectory);
  }

  /**
   * Builds web path prefixes that correspond to a stream wrapper URI prefix.
   *
   * @param string $outputDirectory
   *   Configured output directory URI.
   *
   * @return array<int, string>
   *   Web path prefixes.
   */
  private function buildWebPrefixes(string $outputDirectory): array {
    // Extract scheme from stream wrapper URI (e.g., 'public' from 'public://').
    $schemeSeparator = strpos($outputDirectory, '://');
    if ($schemeSeparator !== FALSE) {
      $scheme = substr($outputDirectory, 0, $schemeSeparator);
      $wrapper = $this->streamWrapperManager->getViaScheme($scheme);
      if ($wrapper !== FALSE) {
        $wrapper->setUri($outputDirectory);
        $externalUrl = $wrapper->getExternalUrl();
        $path = parse_url($externalUrl, PHP_URL_PATH);
        if (is_string($path) && $path !== '') {
          return [rtrim($path, '/')];
        }
      }
    }

    if (str_starts_with($outputDirectory, '/')) {
      return [rtrim($outputDirectory, '/')];
    }

    return [];
  }

}
