<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Hook;

use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FormatterInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\ffmpeg_media\Service\ProfileManagerInterface;
use Drupal\ffmpeg_media\ValueObject\ConversionProfile;

/**
 * Injects FFmpeg conversion settings into third-party video formatters.
 *
 * Uses Drupal's third-party settings mechanism to add auto_convert,
 * conversion_profile, and format_priority options to any file field
 * formatter that is not natively handled by FFmpeg Media.
 *
 * Format options are dynamically resolved from conversion profiles,
 * validated server-side, and synchronized via JS when the profile
 * selection changes — matching the native formatter behavior.
 *
 * @see \Drupal\ffmpeg_media\Hook\MediaViewAlterHook
 * @see \Drupal\ffmpeg_media\Service\FormatterSettingsResolver
 */
final class ThirdPartyFormatterHook {

  use StringTranslationTrait;

  /**
   * Formatter IDs natively handled by ffmpeg_media.
   *
   * @var array<int, string>
   */
  private const array NATIVE_FORMATTER_IDS = [
    'ffmpeg_media_converted_video',
    'ffmpeg_media_streaming_video',
  ];

  /**
   * Fallback labels for common container formats.
   *
   * @var array<string, string>
   */
  private const array FORMAT_LABELS = [
    'mp4' => 'MP4',
    'webm' => 'WebM',
    'mkv' => 'MKV',
    'flv' => 'FLV',
    'hls' => 'HLS',
    'dash' => 'DASH',
    'jxl' => 'JXL',
  ];

  /**
   * Constructs a ThirdPartyFormatterHook object.
   *
   * @param \Drupal\ffmpeg_media\Service\ProfileManagerInterface $profileManager
   *   Profile manager service.
   */
  public function __construct(
    private readonly ProfileManagerInterface $profileManager,
  ) {}

  /**
   * Implements hook_field_formatter_third_party_settings_form().
   *
   * @param \Drupal\Core\Field\FormatterInterface $plugin
   *   The formatter plugin.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The field definition.
   * @param string $view_mode
   *   The view mode.
   * @param array<string, mixed> $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @return array<string, mixed>
   *   Form elements for third-party settings.
   */
  #[Hook('field_formatter_third_party_settings_form')]
  public function thirdPartySettingsForm(
    FormatterInterface $plugin,
    FieldDefinitionInterface $field_definition,
    string $view_mode,
    array $form,
    FormStateInterface $form_state,
  ): array {
    if (!$this->isApplicable($plugin, $field_definition)) {
      return [];
    }

    $elements = [];

    $elements['compatibility_notice'] = [
      '#type' => 'container',
      '#attributes' => ['class' => ['messages', 'messages--warning'], 'role' => 'alert'],
      'message' => [
        '#markup' => $this->t('FFmpeg Media cannot detect which formats your player supports. Verify that the selected output formats (e.g., MP4, WebM) are playable by <em>@formatter</em>. Streaming formats (HLS, DASH) require JavaScript-based players and may not work with all formatters.', [
          '@formatter' => ((array) $plugin->getPluginDefinition())['label'] ?? $plugin->getPluginId(),
        ]),
      ],
      '#weight' => -100,
    ];

    $elements['auto_convert'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Auto-convert on upload'),
      '#description' => $this->t('Automatically queue video for conversion when media is created or the source file changes.'),
      '#default_value' => $plugin->getThirdPartySetting('ffmpeg_media', 'auto_convert', FALSE),
    ];

    $profiles = $this->profileManager->getAllProfiles();
    $profileOptions = ['' => $this->t('- Default -')];
    foreach ($profiles as $profile) {
      $profileOptions[$profile->id] = $profile->label;
    }

    $elements['conversion_profile'] = [
      '#type' => 'select',
      '#title' => $this->t('Conversion profile'),
      '#description' => $this->t('Select a conversion profile for automatic video conversion.'),
      '#options' => $profileOptions,
      '#default_value' => $plugin->getThirdPartySetting('ffmpeg_media', 'conversion_profile', ''),
      '#attributes' => ['data-ffmpeg-profile-select' => TRUE],
    ];

    // Resolve the current profile for initial rendering.
    $currentProfileId = $plugin->getThirdPartySetting('ffmpeg_media', 'conversion_profile', '')
      ?: $this->profileManager->getDefaultProfileId();

    // Build format options for all profiles so JS can swap them.
    $allProfileFormats = [];
    foreach ($profiles as $profile) {
      $allProfileFormats[$profile->id] = $this->buildFormatOptions($profile->id);
    }
    $defaultProfileId = $this->profileManager->getDefaultProfileId();
    $allProfileFormats[''] = $allProfileFormats[$defaultProfileId] ?? [];

    // Build the union of all formats so all checkboxes are pre-rendered.
    $currentProfileFormats = $this->buildFormatOptions($currentProfileId);
    $allFormats = $currentProfileFormats;
    foreach ($allProfileFormats as $profileFormats) {
      foreach ($profileFormats as $key => $label) {
        if (!isset($allFormats[$key])) {
          $allFormats[$key] = $label;
        }
      }
    }

    // Only keep saved selections that still exist in the profile.
    $savedPriority = $plugin->getThirdPartySetting('ffmpeg_media', 'format_priority', []);
    $defaultValue = array_intersect((array) $savedPriority, array_keys($currentProfileFormats));
    if ($defaultValue === []) {
      $defaultValue = array_keys($currentProfileFormats);
    }

    $elements['format_priority'] = [
      '#type' => 'checkboxes',
      '#title' => $this->t('Enabled formats'),
      '#description' => $this->t('Available formats are determined by the conversion profile. Only enable formats that your player can handle. Most third-party players support MP4 and WebM but not streaming formats like HLS or DASH.'),
      '#options' => $allFormats,
      '#default_value' => $defaultValue,
      '#element_validate' => [[self::class, 'validateFormatPriority']],
      '#attributes' => ['data-ffmpeg-format-priority' => TRUE],
      '#attached' => [
        'library' => ['ffmpeg_media/formatter_settings'],
        'drupalSettings' => [
          'ffmpegFormatter' => [
            'profiles' => $allProfileFormats,
            'defaultProfile' => $defaultProfileId,
          ],
        ],
      ],
    ];

    // Hide checkboxes not in the current profile.
    foreach (array_keys($allFormats) as $format) {
      if (!isset($currentProfileFormats[$format])) {
        $elements['format_priority'][$format] = [
          '#wrapper_attributes' => [
            'style' => 'display: none;',
            'data-ffmpeg-hidden' => 'true',
          ],
        ];
      }
    }

    return $elements;
  }

  /**
   * Validates that selected format_priority values exist in the profile.
   *
   * Static callback to avoid serialization of the hook class (which holds
   * a database connection via ProfileManager). Uses \Drupal::service() as
   * DI is unavailable in element_validate callbacks.
   *
   * @param array<string, mixed> $element
   *   The form element.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param array<string, mixed> $complete_form
   *   The complete form.
   */
  public static function validateFormatPriority(array &$element, FormStateInterface $form_state, array &$complete_form): void {
    $selected = array_filter(
      $form_state->getValue($element['#parents']) ?? [],
    );

    if ($selected === []) {
      return;
    }

    /** @var \Drupal\ffmpeg_media\Service\ProfileManagerInterface $profileManager */
    $profileManager = \Drupal::service(ProfileManagerInterface::class);

    // Navigate to the sibling conversion_profile value.
    $parents = $element['#parents'];
    array_pop($parents);
    $parents[] = 'conversion_profile';
    $profileId = $form_state->getValue($parents) ?: $profileManager->getDefaultProfileId();

    $profileKeys = $profileManager->getEnabledOutputKeys($profileId);
    $unavailable = array_diff(array_keys($selected), $profileKeys);

    if ($unavailable !== []) {
      $form_state->setError($element, t(
        'The following formats are not available in the selected profile: @formats',
        ['@formats' => implode(', ', $unavailable)],
      ));
    }
  }

  /**
   * Implements hook_field_formatter_settings_summary_alter().
   *
   * @param array<int, string|\Drupal\Core\StringTranslation\TranslatableMarkup> &$summary
   *   Summary lines.
   * @param array<string, mixed> $context
   *   Context with 'formatter', 'field_definition', 'view_mode'.
   */
  #[Hook('field_formatter_settings_summary_alter')]
  public function settingsSummaryAlter(array &$summary, array $context): void {
    $formatter = $context['formatter'];
    if (!$formatter instanceof FormatterInterface) {
      return;
    }

    if (!$this->isApplicable($formatter, $context['field_definition'])) {
      return;
    }

    $autoConvert = $formatter->getThirdPartySetting('ffmpeg_media', 'auto_convert', FALSE);
    if (!$autoConvert) {
      return;
    }

    $profileSetting = $formatter->getThirdPartySetting('ffmpeg_media', 'conversion_profile', '');
    $profileId = $profileSetting !== '' ? $profileSetting : $this->profileManager->getDefaultProfileId();
    $profileData = $this->profileManager->getProfile($profileId);
    $profileLabel = $profileData?->label ?? $profileId;

    $formats = array_filter(
      (array) $formatter->getThirdPartySetting('ffmpeg_media', 'format_priority', []),
    );

    $formatLabels = [];
    $formatOptions = $this->buildFormatOptions($profileId);
    foreach (array_values($formats) as $fmt) {
      $formatLabels[] = $formatOptions[$fmt] ?? strtoupper($fmt);
    }

    $summary[] = $this->t('FFmpeg: auto-convert (@profile)', [
      '@profile' => $profileLabel,
    ]);

    if ($formatLabels !== []) {
      $summary[] = $this->t('Formats: @formats', [
        '@formats' => implode(', ', $formatLabels),
      ]);

      // Warn about formats not available in the selected profile.
      $profileKeys = $this->profileManager->getEnabledOutputKeys($profileId);
      $unavailable = array_diff(array_values($formats), $profileKeys);
      if ($unavailable !== []) {
        $summary[] = $this->t('Warning: Formats @formats not available in profile', [
          '@formats' => implode(', ', array_map('strtoupper', $unavailable)),
        ]);
      }
    }
  }

  /**
   * Builds format options from a profile's enabled outputs.
   *
   * Generates labels that include the codec (e.g., "MP4 (AV1)") based
   * on the actual profile configuration.
   *
   * @param string $profileId
   *   The profile ID.
   *
   * @return array<string, string>
   *   Format options keyed by output key.
   */
  #[\NoDiscard]
  private function buildFormatOptions(string $profileId): array {
    $outputs = $this->profileManager->getEnabledOutputs($profileId);
    $keys = $this->profileManager->getEnabledOutputKeys($profileId);
    $options = [];

    foreach ($outputs as $idx => $output) {
      $key = $keys[$idx] ?? $output->container;
      $label = ConversionProfile::labelFromKey($key);

      if (!ConversionProfile::isQualifiedKey($key)) {
        $container = $output->container;
        $codec = strtoupper($output->codec);
        $baseLabel = self::FORMAT_LABELS[$container] ?? strtoupper($container);
        $label = $codec !== '' ? $baseLabel . ' (' . $codec . ')' : $baseLabel;
      }

      $options[$key] = $label;
    }

    return $options;
  }

  /**
   * Checks if the formatter is applicable for FFmpeg third-party settings.
   *
   * @param \Drupal\Core\Field\FormatterInterface $formatter
   *   The formatter plugin.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The field definition.
   *
   * @return bool
   *   TRUE if the formatter is a non-native file field formatter.
   */
  private function isApplicable(FormatterInterface $formatter, FieldDefinitionInterface $field_definition): bool {
    if (in_array($formatter->getPluginId(), self::NATIVE_FORMATTER_IDS, TRUE)) {
      return FALSE;
    }

    return $field_definition->getType() === 'file';
  }

}
