<?php

declare(strict_types=1);

namespace Drupal\sobki_assets\Form;

use Drupal\Component\Utility\Environment;
use Drupal\Core\Cache\CacheCollectorInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\ByteSizeMarkup;
use Drupal\sobki_assets\Service\ArchiveExtractorInterface;
use Drupal\sobki_assets\Service\DestinationDirectoryPurgerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Form to upload assets.
 */
class AssetsUploadForm extends FormBase {

  public const string DESTINATION_DIRECTORY = 'public://sobki_assets';

  /**
   * The entity type manager service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected EntityTypeManagerInterface $entityTypeManager;

  /**
   * The file system service.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected FileSystemInterface $fileSystem;

  /**
   * THe library discovery service.
   *
   * @var \Drupal\Core\Cache\CacheCollectorInterface
   */
  protected CacheCollectorInterface $libraryDiscovery;

  /**
   * Destination directory purger service.
   *
   * @var \Drupal\sobki_assets\Service\DestinationDirectoryPurgerInterface
   */
  protected DestinationDirectoryPurgerInterface $destinationDirectoryPurger;

  /**
   * Archive extractor service.
   *
   * @var \Drupal\sobki_assets\Service\ArchiveExtractorInterface
   */
  protected ArchiveExtractorInterface $archiveExtractor;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): static {
    /** @var static $instance */
    $instance = parent::create($container);
    $instance->entityTypeManager = $container->get('entity_type.manager');
    $instance->fileSystem = $container->get('file_system');
    $instance->libraryDiscovery = $container->get('library.discovery');
    $instance->destinationDirectoryPurger = $container->get('sobki_assets.destination_directory_purger');
    $instance->archiveExtractor = $container->get('sobki_assets.archive_extractor');
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId(): string {
    return 'sobki_assets.assets_upload';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state): array {
    $size = Environment::getUploadMaxSize();
    $formatted_size = ByteSizeMarkup::create($size);
    $directory = static::DESTINATION_DIRECTORY;

    if (!$this->fileSystem->prepareDirectory($directory)) {
      if (!$this->fileSystem->mkdir($directory)) {
        $this->messenger()->addError($this->t('Impossible to create the assets directory.'));
        return $form;
      }
    }

    $form['archive'] = [
      '#type' => 'managed_file',
      '#title' => $this->t('Upload assets'),
      '#upload_location' => static::DESTINATION_DIRECTORY,
      '#required' => TRUE,
      '#description' => $this->t("Upload your assets compressed in a .zip format, all at the root of the archive.<br>Total upload size cannot exceed server's @size max limit", [
        '@size' => $formatted_size,
      ]),
      '#upload_validators' => [
        'FileExtension' => ['extensions' => 'zip'],
        'FileSizeLimit' => ['fileLimit' => $size],
      ],
    ];

    /** @var object{"filename": string}[] $existingFiles */
    $existingFiles = $this->fileSystem->scanDirectory($directory, '/.*/');
    if (!empty($existingFiles)) {
      $files = [];
      foreach ($existingFiles as $file) {
        $files[] = $file->filename;
      }
      $form['uploaded_files'] = [
        '#theme' => 'item_list',
        '#title' => $this->formatPlural(\count($existingFiles), 'Uploaded asset', 'Uploaded assets'),
        '#items' => $files,
      ];
    }

    $form['actions'] = [
      '#type' => 'actions',
      'submit' => [
        '#type' => 'submit',
        '#value' => $this->t('Upload'),
      ],
    ];

    if (!empty($existingFiles)) {
      $form['actions']['delete'] = [
        '#type' => 'submit',
        '#value' => $this->t('Delete'),
        '#submit' => ['::deleteFormSubmit'],
        '#limit_validation_errors' => [],
      ];
    }

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    /** @var string|null $fid */
    [$fid] = \is_array($form_state->getValue('archive')) ? $form_state->getValue('archive') : [NULL];
    $this->destinationDirectoryPurger->purge($fid);

    if ($fid == NULL) {
      $this->messenger()->addError($this->t('Failed to load the archive.'));
      return;
    }

    $archive = $this->entityTypeManager->getStorage('file')->load($fid);
    if ($archive == NULL) {
      $this->messenger()->addError($this->t('Failed to load the archive.'));
      return;
    }

    $archive_uri = $archive->getFileUri();
    if ($archive_uri == NULL) {
      $this->messenger()->addError($this->t('Failed to get the archive URI.'));
      return;
    }

    $archive_path = $this->fileSystem->realpath($archive_uri);
    if (!$archive_path) {
      $this->messenger()->addError($this->t('Failed to get the archive path.'));
      return;
    }

    $destination = $this->fileSystem->realpath(static::DESTINATION_DIRECTORY);
    if (!$destination) {
      $this->messenger()->addError($this->t('Failed to get the destination path.'));
      return;
    }

    $this->archiveExtractor->extract(
      $archive_path,
      $destination,
      fn (array $filenames) => $this->messenger()->addStatus($this->formatPlural(
        \count($filenames),
        'File uploaded and extracted successfully.',
        'Files uploaded and extracted successfully.'
      )),
      fn () => $this->messenger()->addError($this->t('Failed to extract the archive.')),
    );
    $this->libraryDiscovery->clear();
  }

  /**
   * Delete form handler.
   */
  public function deleteFormSubmit(): void {
    $this->destinationDirectoryPurger->purge();
    $this->destinationDirectoryPurger->removeDestinationDirectory();
    $this->libraryDiscovery->clear();
    $this->messenger()->addStatus($this->t('Files have been deleted successfully.'));
  }

}
