<?php

namespace Drupal\acquia_dam\Form;

use Drupal\acquia_dam\ImageStyleHelper;
use Drupal\acquia_dam\Plugin\media\Source\Asset;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\image\Entity\ImageStyle;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Alters the media edit form to add focal point element on DAM images.
 */
final class FocalPointMediaEditFormAlter implements ContainerInjectionInterface {

  use StringTranslationTrait;

  /**
   * Acquia DAM image helper service.
   *
   * @var \Drupal\acquia_dam\ImageStyleHelper
   */
  protected $imageStyleHelper;

  /**
   * Constructs a new FocalPointMediaEditFormAlter object.
   *
   * @param \Drupal\acquia_dam\ImageStyleHelper $image_style_helper
   *   Acquia DAM image helper service.
   */
  public function __construct(ImageStyleHelper $image_style_helper) {
    $this->imageStyleHelper = $image_style_helper;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    $instance = new self(
      $container->get('acquia_dam.image_style_support')
    );
    $instance->setStringTranslation($container->get('string_translation'));
    return $instance;
  }

  /**
   * Alters the media edit form to add focal point element.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function formAlter(array &$form, FormStateInterface $form_state): void {
    $media = $form_state->getFormObject()->getEntity();
    $source = $media->getSource();

    // Early exit if this is an Acquia DAM: Image asset.
    if (!$source instanceof Asset || $source->getDerivativeId() !== 'image') {
      return;
    }

    // Get image properties and thumbnail.
    $image_properties = $source->getMetadata($media, 'image_properties');
    $thumbnail_uri = $source->getMetadata($media, 'thumbnail_uri');

    // Early exit if image properties or thumbnail URI is empty.
    if (empty($image_properties) || empty($thumbnail_uri)) {
      return;
    }

    // Get all allowed image styles with focal point effects.
    $allowed_styles = $this->imageStyleHelper->getAllowedImageStyles();
    $focal_point_styles = [];

    foreach ($allowed_styles as $image_style) {
      $crop_type = $this->imageStyleHelper->getCropTypeOfFocalPointEffect($image_style->id());
      if ($crop_type) {
        $focal_point_styles[$image_style->id()] = [
          'label' => $image_style->label(),
          'crop_type' => $crop_type,
        ];
      }
    }

    if (empty($focal_point_styles)) {
      return;
    }

    // Create a fieldset to group all focal point fields.
    $form['focal_points'] = [
      '#type' => 'details',
      '#title' => $this->t('Focal Point Settings'),
      '#description' => $this->t('Set focal points for different image styles. The focal point determines which part of the image stays in focus when cropped to different sizes.'),
      '#open' => TRUE,
      '#weight' => 50,
      '#tree' => TRUE,
    ];

    // Get existing crops for this media.
    $existing_crops = $this->imageStyleHelper->getCrops($media);
    $crops_by_style = [];

    // Build a map of image style ID to its existing crop entity.
    // The Crop entity does not store the image style ID directly; instead, it
    // stores the URI of the derivative (styled) image, which encodes the style
    // name as a path segment (e.g. "acquia-dam://styles/my_style/...").
    // We therefore parse the style name out of the crop's URI, as there is no
    // direct API on the Crop entity to retrieve the associated image style ID.
    foreach ($existing_crops as $crop) {
      $uri = $crop->get('uri')->first()->getValue();
      // Match the image style name from a derivative URI path segment.
      // Derivative URIs follow the pattern ".../styles/{style_name}/...", so
      // capturing the first path segment after "styles/" gives us the style ID.
      if (preg_match('/styles\/([\w-]+)/', $uri['value'], $matches)) {
        $crops_by_style[$matches[1]] = $crop;
      }
    }

    // Add focal point fields for each style.
    $image_styles_map = [];

    foreach ($focal_point_styles as $style_id => $style_info) {
      $default_position = '50,50';
      $crop_id = NULL;

      // If a crop exists, use its position.
      if (isset($crops_by_style[$style_id])) {
        $crop = $crops_by_style[$style_id];
        $position = $crop->position();
        $relative_position = $this->imageStyleHelper->absoluteToRelative(
          $position['x'],
          $position['y'],
          $image_properties['width'],
          $image_properties['height']
        );
        $default_position = $relative_position['x'] . ',' . $relative_position['y'];
        $crop_id = $crop->id();
      }

      $form['focal_points'][$style_id] = [
        '#type' => 'acquia_dam_focal_point',
        '#tree' => TRUE,
        '#title' => $this->t('@label', ['@label' => $style_info['label']]),
        '#description' => $crop_id
          ? $this->t('Current position: @pos', ['@pos' => $default_position])
          : $this->t('No focal point set. Default will be center (50,50).'),
        '#description_display' => 'after',
        '#position' => $default_position,
        '#thumbnail_uri' => $thumbnail_uri,
        '#style_name' => 'media_library',
      ];

      // Store the mapping: style_id => crop_id (or NULL for new crops).
      $image_styles_map[$style_id] = [
        'crop_id' => $crop_id,
        'crop_type' => $style_info['crop_type'],
      ];
    }

    $form_state->set('image_styles_map', $image_styles_map);
    $form['actions']['submit']['#submit'][] = [self::class, 'updateCrop'];
  }

  /**
   * Handler that persists focal point crop data after the media form is saved.
   *
   * When a user edits a DAM image media entity and adjusts the focal point for
   * one or more image styles, this handler ensures those selections are
   * actually saved as crop entities.
   * Without this, the focal point UI changes would be discarded on
   * form submission. It also flushes the relevant image style so the
   * updated crop position takes effect immediately in the rendered output.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   */
  public static function updateCrop(array &$form, FormStateInterface $form_state): void {
    $storage = $form_state->getStorage();
    $media = $form_state->getFormObject()->getEntity();
    /** @var \Drupal\acquia_dam\ImageStyleHelper $image_style_helper */
    $image_style_helper = \Drupal::service('acquia_dam.image_style_support');

    if (!isset($storage['image_styles_map'])) {
      return;
    }

    foreach ($storage['image_styles_map'] as $image_style_id => $crop_info) {
      // Drupal FormState tree-aware lookup
      $new_position = $form_state->getValue(['focal_points', $image_style_id, 'focal_point']);
      // Skip if no position value was submitted.
      // This happen if the user didn't interact with the focal point field
      // for this style.
      if ($new_position === NULL || $new_position === '') {
        continue;
      }

      [$x, $y] = array_map('intval', explode(',', $new_position, 2));

      if (!empty($crop_info['crop_id'])) {
        // Update existing crop.
        $image_style_helper->updateCrop($x, $y, $media, $crop_info['crop_id']);
      } else {
        // Create new crop.
        $image_style_helper->saveCropEntity($x, $y, $media, $image_style_id);
      }

      // Flush the image style after updating/creating crop.
      if ($image_style = ImageStyle::load($image_style_id)) {
        $asset_id = $media->get('acquia_dam_asset_id')->asset_id;
        $version_id = $media->get('acquia_dam_asset_id')->version_id;
        $original_uri = "acquia-dam://$asset_id/$version_id";
        $image_style->flush($original_uri);
      }
    }
  }

}
