<?php

declare(strict_types=1);

namespace Drupal\sobki_background_image\Element;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Attribute\FormElement;
use Drupal\Core\Render\Element\FormElementBase;

/**
 * Provides a form element to provide a background image.
 *
 * Properties:
 * - #default_value: (optional) Structured as an array with keys:
 *   - media: (optional) the media UUID.
 *   - image_style: (optional) the image style.
 *   - position_x: (optional) the horizontal position.
 *   - position_y: (optional) the vertical position.
 *   - attachment: (optional) the attachment.
 *   - repeat: (optional) the repeat setting.
 *   - size: (optional) the size.
 *
 * Usage example:
 *
 * @code
 * $form['background_image'] = [
 *   '#type' => 'sobki_background_image',
 *   '#title' => $this->>t('Background Image'),
 *   '#default_value' => [],
 *   '#media_bundles' => ['image'],
 * ];
 *
 * @endcode
 */
#[FormElement('sobki_background_image')]
class BackgroundImage extends FormElementBase {

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = static::class;
    return [
      '#input' => TRUE,
      '#multiple' => FALSE,
      '#default_value' => [],
      '#media_bundles' => [],
      '#process' => [
        [$class, 'buildForm'],
        [$class, 'processGroup'],
      ],
      '#element_validate' => [
        [$class, 'validate'],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
    if (\is_array($input) && isset($input['wrapper']) && !empty($input['wrapper'])) {
      $value = $input['wrapper'];
      $value['media'] = static::getMediaUuidFromId(static::getMediaId($value['media']));
    }
    else {
      $value = $element['#default_value'] ?? [];
    }
    return \array_filter($value);
  }

  /**
   * Build the form element.
   *
   * @param array $element
   *   The form element to process.
   * @param \Drupal\Core\Form\FormStateInterface $formState
   *   The current state of the form.
   * @param array $completeForm
   *   The complete form structure.
   *
   * @return array
   *   The form element.
   */
  public static function buildForm(array &$element, FormStateInterface $formState, array &$completeForm): array {
    if (empty($element['#media_bundles'])) {
      $element['error'] = [
        '#theme' => 'status_messages',
        '#message_list' => [
          'error' => [
            \t('At least one media type needs to be allowed for background image.'),
          ],
        ],
      ];
      return $element;
    }

    $settings = $element['#value'];

    $element['wrapper'] = [
      '#type' => 'details',
      '#title' => $element['#title'] ?? '',
      '#description' => $element['#description'] ?? '',
      '#states' => $element['#states'] ?? [],
    ];

    $element['wrapper']['media'] = [
      '#type' => 'media_library',
      '#title' => \t('Media'),
      '#allowed_bundles' => $element['#media_bundles'],
      '#default_value' => $settings['media'] ? static::getMediaIdFromUuid((string) $settings['media']) : NULL,
    ];

    $imageStyles = static::getImageStyles();
    if (empty($imageStyles)) {
      $element['wrapper']['image_style'] = [
        '#type' => 'value',
        '#value' => '',
      ];
    }
    else {
      $element['wrapper']['image_style'] = [
        '#type' => 'select',
        '#title' => \t('Image style'),
        '#options' => $imageStyles,
        '#default_value' => $settings['image_style'] ?? NULL,
        '#empty_value' => '',
        '#empty_option' => \t('None (original image)'),
      ];
    }

    $element['wrapper']['position_x'] = [
      '#type' => 'select',
      '#title' => \t('Horizontal position'),
      '#description' => \t('The horizontal alignment of the background image. [<a href=":url">CSS property: background-position-x</a>]', [
        ':url' => 'https://developer.mozilla.org/en-US/docs/Web/CSS/background-position-x',
      ]),
      '#default_value' => $settings['position_x'] ?? '',
      '#empty_value' => '',
      '#options' => [
        'left' => \t('Left'),
        'center' => \t('Center'),
        'right' => \t('Right'),
        'initial' => \t('Initial'),
        'inherit' => \t('Inherit'),
      ],
    ];
    $element['wrapper']['position_y'] = [
      '#type' => 'select',
      '#title' => \t('Vertical position'),
      '#description' => \t('The vertical alignment of the background image. [<a href=":url">CSS property: background-position-y</a>]', [
        ':url' => 'https://developer.mozilla.org/en-US/docs/Web/CSS/background-position-y',
      ]),
      '#default_value' => $settings['position_y'] ?? '',
      '#empty_value' => '',
      '#options' => [
        'top' => \t('Top'),
        'center' => \t('Center'),
        'bottom' => \t('Bottom'),
        'initial' => \t('Initial'),
        'inherit' => \t('Inherit'),
      ],
    ];

    $element['wrapper']['attachment'] = [
      '#type' => 'select',
      '#title' => \t('Attachment'),
      '#description' => \t('The attachment setting for the background image. [<a href=":url">CSS property: background-attachment</a>]', [
        ':url' => 'https://developer.mozilla.org/en-US/docs/Web/CSS/background-attachment',
      ]),
      '#default_value' => $settings['attachment'] ?? '',
      '#empty_value' => '',
      '#options' => [
        'scroll' => \t('Scroll'),
        'fixed' => \t('Fixed'),
        'local' => \t('Local'),
        'initial' => \t('Initial'),
        'inherit' => \t('Inherit'),
      ],
    ];

    $element['wrapper']['repeat'] = [
      '#type' => 'select',
      '#title' => \t('Repeat'),
      '#description' => \t('Define the repeat settings for the background image. [<a href=":url">CSS property: background-repeat</a>]', [
        ':url' => 'https://developer.mozilla.org/en-US/docs/Web/CSS/background-repeat',
      ]),
      '#default_value' => $settings['repeat'] ?? '',
      '#empty_value' => '',
      '#options' => [
        'no-repeat' => \t('No Repeat'),
        'repeat' => \t('Repeat'),
        'repeat-x' => \t('Repeat horizontally'),
        'repeat-y' => \t('Repeat vertically'),
        'round' => \t('Round'),
        'space' => \t('Space'),
        'initial' => \t('initial'),
        'inherit' => \t('inherit'),
      ],
    ];

    $element['wrapper']['size'] = [
      '#type' => 'select',
      '#title' => \t('Size'),
      '#description' => \t('The size of the background (NOTE: CSS3 only. Useful for responsive designs). [<a href=":url">CSS property: background-size</a>]', [
        ':url' => 'https://developer.mozilla.org/en-US/docs/Web/CSS/background-size',
      ]),
      '#default_value' => $settings['size'] ?? '',
      '#empty_value' => '',
      '#options' => [
        'auto' => \t('auto'),
        'cover' => \t('cover'),
        'contain' => \t('contain'),
        'initial' => \t('initial'),
        'inherit' => \t('inherit'),
      ],
    ];

    return $element;
  }

  /**
   * Form element validation handler.
   *
   * Cleanup value structure.
   */
  public static function validate(array &$element, FormStateInterface $form_state, array &$complete_form): void {
    $form_state->setValueForElement($element, $element['#value']);
  }

  /**
   * Wraps the entity type manager.
   *
   * @return \Drupal\Core\Entity\EntityTypeManagerInterface
   *   The entity type manager.
   */
  protected static function entityTypeManager(): EntityTypeManagerInterface {
    return \Drupal::service('entity_type.manager');
  }

  /**
   * The referenced media ID.
   *
   * @param mixed $value
   *   The provided value from media library element.
   *
   * @return string
   *   The media ID. Empty string if not found.
   */
  protected static function getMediaId($value): string {
    if (\is_array($value) && !empty($value['media_library_selection'])) {
      $value = $value['media_library_selection'];
    }
    elseif (!\is_string($value)) {
      $value = '';
    }
    return $value;
  }

  /**
   * The media ID.
   *
   * @param string $uuid
   *   The media UUID.
   *
   * @return string
   *   The media ID. Empty string if not found.
   */
  protected static function getMediaIdFromUuid(string $uuid): string {
    if (empty($uuid)) {
      return '';
    }

    $mediaStorage = static::entityTypeManager()->getStorage('media');
    /** @var \Drupal\media\MediaInterface[] $medias */
    $medias = $mediaStorage->loadByProperties(['uuid' => $uuid]);
    if (empty($medias)) {
      return '';
    }

    $media = \array_shift($medias);
    return (string) $media->id();
  }

  /**
   * The media UUID.
   *
   * @param string $id
   *   The media ID.
   *
   * @return string
   *   The media UUID. Empty string if not found.
   */
  protected static function getMediaUuidFromId(string $id): string {
    if (empty($id)) {
      return '';
    }

    $mediaStorage = static::entityTypeManager()->getStorage('media');
    $media = $mediaStorage->load($id);
    if ($media === NULL) {
      return '';
    }

    return (string) $media->uuid();
  }

  /**
   * Get the image styles.
   *
   * @return array
   *   The image styles.
   */
  protected static function getImageStyles(): array {
    $imageStyles = [];
    foreach (static::entityTypeManager()->getStorage('image_style')->loadMultiple() as $style) {
      $imageStyles[$style->id()] = $style->label();
    }
    return $imageStyles;
  }

}
