<?php

namespace Drupal\nodehive_visualeditor\Form;

use Drupal\Component\Utility\Html;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\RedirectCommand;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\Core\Url;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Shows a recent content edits.
 */
class RecentEditForm extends FormBase {

  /**
   * The private temp store factory.
   *
   * @var \Drupal\Core\TempStore\PrivateTempStoreFactory
   */
  protected PrivateTempStoreFactory $tempStoreFactory;

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

  /**
   * Language manager interface.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected LanguageManagerInterface $languageManager;

  /**
   * Renderer.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected RendererInterface $renderer;

  /**
   * Constructs a new RecentEditForm.
   *
   * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
   *   Tempstore factory.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   Entity type manager.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   Language manager.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   Renderer.
   */
  public function __construct(
    PrivateTempStoreFactory $temp_store_factory,
    EntityTypeManagerInterface $entity_type_manager,
    LanguageManagerInterface $language_manager,
    RendererInterface $renderer,
  ) {
    $this->tempStoreFactory = $temp_store_factory;
    $this->entityTypeManager = $entity_type_manager;
    $this->languageManager = $language_manager;
    $this->renderer = $renderer;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('tempstore.private'),
      $container->get('entity_type.manager'),
      $container->get('language_manager'),
      $container->get('renderer')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'recent_edit_form';
  }

  /**
   * Build the form with AJAX functionality.
   */
  public function buildForm(array $form, FormStateInterface $form_state, $default_space = NULL) {
    $store = $this->tempStoreFactory->get('nodehive_tempstore');
    if ($default_space) {
      $store->set('nodehive_space', $default_space);
    }
    else {
      $default_space = $store->get('nodehive_space');
    }

    $spaces = $this->entityTypeManager
      ->getStorage("nodehive_space")
      ->loadByProperties(['status' => 1]);

    $space_options = [];
    foreach ($spaces as $space) {
      if ($space->access('edit space')) {
        $space_options[$space->id()] = $space->label();
      }
    }

    $form['space'] = [
      '#type' => 'select',
      '#title' => $this->t('Selected space'),
      '#options' => $space_options,
      '#default_value' => $default_space,
      '#ajax' => [
        'callback' => '::updateResults',
        'wrapper' => 'results-wrapper',
        'event' => 'change',
        'progress' => ['type' => 'throbber', 'message' => NULL],
      ],
    ];

    // Retrieve all content types.
    if ($default_space) {
      $default_space_entity = $this->entityTypeManager
        ->getStorage('nodehive_space')
        ->load($default_space);

      $content_types = $default_space_entity->get('content_types')
        ->referencedEntities();

      // If empty then we allow all.
      if (!$content_types) {
        $content_types = $this->entityTypeManager
          ->getStorage('node_type')
          ->loadMultiple();
      }
    }
    else {
      $content_types = $this->entityTypeManager
        ->getStorage('node_type')
        ->loadMultiple();
    }

    $options = [];
    foreach ($content_types as $content_type) {
      $options[$content_type->id()] = $content_type->label();
    }

    $user_input = $form_state->getUserInput();
    $content_type_default_value = $user_input['content_type'] ?? NULL;
    if (is_null($form_state->getValue('content_type'))) {
      $content_type_default_value = $store->get('nodehive_content_type');
    }
    $form_state->setValue('content_type', $content_type_default_value);

    $form['content_type'] = [
      '#type' => 'select',
      '#title' => $this->t('Content type'),
      '#options' => $options,
      '#empty_option' => $this->t('- All content types -'),
      '#default_value' => $content_type_default_value,
      '#ajax' => [
        'callback' => '::updateResults',
        'wrapper' => 'results-wrapper',
        'event' => 'change',
        'progress' => [
          'type' => 'throbber',
          'message' => NULL,
        ],
      ],
    ];

    // Retrieve all available languages.
    $languages = $this->languageManager->getLanguages();
    $lang_options = [];
    foreach ($languages as $lang_code => $language) {
      $lang_options[$lang_code] = $language->getName();
    }

    // Determine the default language for the form.
    $initial_language = $form_state->getValue('language');
    if (!$initial_language) {
      $initial_language = $store->get('nodehive_language');
      if (!$initial_language) {
        $initial_language = $this->languageManager->getCurrentLanguage()->getId();
      }

      $form_state->setValue('language', $initial_language);
    }

    // Add a select element for languages.
    $form['language'] = [
      '#type' => 'select',
      '#title' => $this->t('Language'),
      '#options' => $lang_options,
      '#default_value' => $initial_language,
      '#ajax' => [
        'callback' => '::updateResults',
        'wrapper' => 'results-wrapper',
        'event' => 'change',
        'progress' => [
          'type' => 'throbber',
          'message' => NULL,
        ],
      ],
    ];

    $default_search_value = $form_state->getUserInput()['search'] ?? '';
    $form['search'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Search by title'),
      '#default_value' => $default_search_value,
      '#maxlength' => 64,
      '#size' => 64,
      '#ajax' => [
        'callback' => '::updateResults',
        'event' => 'change',
        'wrapper' => 'results-wrapper',
        'refocus-blur' => FALSE,
        'progress' => [
          'type' => 'throbber',
          'message' => NULL,
        ],
      ],
    ];

    $form['results'] = [
      '#type' => 'markup',
      '#markup' => '<div id="results-wrapper">' . $this->getTableMarkup($form_state) . '</div>',
    ];

    $form['#attributes']['class'] = [
      'visualeditor-recent-edit-form',
    ];

    $form['#attached']['library'][] = "nodehive_visualeditor/recent_edit_form";
    $form['#attached']['library'][] = "nodehive_visualeditor/content_browser";
    $form['#attached']['library'][] = "core/drupal.dropbutton";

    return $form;
  }

  /**
   * AJAX callback to update the table of results.
   *
   * @param array $form
   *   Render array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Current form state.
   */
  public function updateResults(array &$form, FormStateInterface $form_state) {
    $selected_space = $form_state->getValue('space');
    $selected_content_type = $form_state->getValue('content_type');
    $selected_language = $form_state->getValue('language');
    $selected_search = $form_state->getValue('search');

    $store = $this->tempStoreFactory->get('nodehive_tempstore');
    $current_selected_space = $store->get('nodehive_space');
    $store->set('nodehive_space', $selected_space);
    $store->set('nodehive_content_type', $selected_content_type);
    $store->set('nodehive_language', $selected_language);
    $store->set('nodehive_search', $selected_search);

    $response = new AjaxResponse();

    if ($selected_space != $current_selected_space) {
      $url = Url::fromRoute(
        'nodehive_visualeditor.visual_editor_single',
        [
          'nodehive_space' => $selected_space,
        ]
      )->toString();

      $response->addCommand(new RedirectCommand($url));
    }
    else {
      $response->addCommand(
        new HtmlCommand(
          '#results-wrapper',
          $this->getTableMarkup($form_state)
        )
      );
    }

    return $response;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
  }

  /**
   * Generates the markup for the results table.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Current form state.
   */
  private function getTableMarkup(FormStateInterface $form_state) {
    $title = $form_state->getValue('search');
    $content_type = $form_state->getValue('content_type');
    $language = $form_state->getValue('language');

    // Load the space id from the tempstore.
    $store = $this->tempStoreFactory->get('nodehive_tempstore');
    $space_id = $store->get('nodehive_space');
    $space_url = $space_id ? $this->entityTypeManager->getStorage('nodehive_space')->load($space_id)->space_url[0]->uri : 'https://' . $_SERVER['HTTP_HOST'];

    // Load nodes.
    $nodes = $this->getNodes($title, $content_type, $space_id, $language);
    if (empty($nodes)) {
      return $this->t('No results found.');
    }

    $header = [$this->t('Content (@nodes) found', ["@nodes" => count($nodes)])];
    $rows = [];

    /** @var \Drupal\node\NodeInterface $node */
    foreach ($nodes as $node) {
      $rows[] = [
        '#markup' => $this->getTableRowMarkup($node, $language, $space_url),
      ];
    }

    $table = [
      '#type' => 'table',
      '#header' => $header,
      '#rows' => $rows,
      '#empty' => $this->t('No recent edits found.'),
      '#allowed_tags' => ['a', 'br', 'span', 's', 'ul', 'li', 's'],
      '#attributes' => [
        'class' => [
          'content-browser',
        ],
      ],
    ];

    return $this->renderer->renderRoot($table);
  }

  /**
   * Generates the markup for the table row.
   *
   * @param \Drupal\Node\NodeInterface $node
   *   Node entity.
   * @param string $language
   *   Langcode.
   * @param string $space_url
   *   Space url.
   */
  private function getTableRowMarkup(NodeInterface $node, string $language, string $space_url) {
    if ($language) {
      $node = $node->getTranslation($language);
    }

    // Retrieve the workflow state.
    $workflow_state = $node->get('status')->value;
    if ($workflow_state) {
      $workflow_state_label = $this->t('Published');
    }
    else {
      $workflow_state_label = $this->t('Unpublished');
    }

    $node_url = Url::fromRoute(
      'entity.node.canonical',
      [
        'node' => $node->id(),
      ],
      [
        'absolute' => FALSE,
        'language' => $this->languageManager->getLanguage($language),
      ]
    )->toString();

    // Custom space url.
    $custom_url = $space_url . $node_url;

    $row = [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'content-browser-row',
        ],
      ],
    ];

    $row['top'] = [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'top-header',
        ],
      ],
    ];

    $row['top']['type_container'] = [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'type-header',
          'views-field',
        ],
      ],
    ];

    $marker_class = $workflow_state ? 'marker--published' : 'marker--unpublished';
    $row['top']['type_container']['type'] = [
      '#markup' => $node->type->entity->label() . " <span class='marker " . $marker_class . "'>" . $workflow_state_label . "</span>",
    ];

    $row['top']['operations_container'] = [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'operations-header',
        ],
      ],
    ];

    $row['top']['operations_container']['operations'] = [
      '#type' => 'operations',
      '#links' => [
        [
          'title' => $this->t('View'),
          'weight' => 10,
          'url' => Url::fromRoute(
            'entity.node.canonical',
            [
              'node' => $node->id(),
            ],
            [
              'absolute' => FALSE,
              'language' => $this->languageManager->getLanguage($language),
            ],
          ),
        ],
        [
          'title' => $this->t('Edit'),
          'weight' => 10,
          'url' => Url::fromRoute(
            'entity.node.edit_form',
            [
              'node' => $node->id(),
            ],
            [
              'absolute' => TRUE,
              'language' => $this->languageManager->getLanguage($language),
            ],
          ),
        ],
      ],
      // Allow links to use modals.
      '#attached' => [
        'library' => ['core/drupal.dialog.ajax'],
      ],
    ];

    if ($workflow_state == 1) {
      $paul_url = 'https://paul.netnode.ch/results/nodehive?url=' . Html::escape($custom_url);
      $row['top']['operations_container']['operations']['#links'][] = [
        'title' => $this->t('PaulAI'),
        'weight' => 10,
        'url' => Url::fromUri(
          $paul_url,
          [
            'absolute' => FALSE,
            'language' => $this->languageManager->getLanguage($language),
          ],
        ),
      ];
    }

    $row['middle'] = [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'middle-container',
        ],
      ],
    ];

    // Title.
    $row['middle']['title_container'] = [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'title-container',
        ],
      ],
    ];

    $iframe_view_link = Link::fromTextAndUrl(
      $node->label(),
      Url::fromUri($custom_url)
    )->toRenderable();
    $iframe_view_link['#attributes'] = ['class' => ['open_in_iframe']];
    $iframe_view_link_html = $this->renderer->renderRoot($iframe_view_link);

    $row['middle']['title_container']['title'] = [
      '#markup' => $iframe_view_link_html,
    ];

    $row['middle']['lang_container'] = [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'lang-container',
        ],
      ],
    ];

    $language_links = $this->getLanguageLinks($node, $space_url);
    $row['middle']['lang_container']['lang'] = [
      '#markup' => $this->renderer->renderRoot($language_links),
    ];

    $row['bottom'] = [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'bottom-container',
        ],
      ],
    ];

    $row['bottom']['url_container'] = [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'url-container',
        ],
      ],
    ];

    $row['bottom']['url_container']['title'] = [
      '#markup' => "<p>" . $node_url . "</p>",
    ];

    $row_rendered = $this->renderer->renderRoot($row);

    return $row_rendered;
  }

  /**
   * Get the language links for a node.
   *
   * @param \Drupal\node\NodeInterface $node
   *   Node entity.
   * @param string $space_url
   *   Space url.
   */
  private function getLanguageLinks(NodeInterface $node, $space_url) {
    $languages = $this->languageManager->getLanguages();
    $items = [];

    foreach ($languages as $lang_code => $language) {
      if ($node->hasTranslation($lang_code)) {
        $translated_node = $node->getTranslation($lang_code);
        $options = ['absolute' => FALSE, 'language' => $language];

        $node_url = Url::fromRoute(
          'entity.node.canonical',
          [
            'node' => $translated_node->id(),
          ],
          $options
        )->toString();

        $custom_url = $space_url . $node_url;

        $view_link = Link::fromTextAndUrl(
          strtoupper($lang_code),
          Url::fromUri($custom_url)
        )->toRenderable();
        $view_link['#attributes'] = ['class' => ['open_in_iframe']];
        $view_link_html = $this->renderer->renderRoot($view_link);

        $items[] = [
          '#markup' => $view_link_html,
          '#allowed_tags' => ['a'],
        ];
      }
      else {
        $items[] = [
          '#markup' => strtoupper($lang_code),
          '#allowed_tags' => ['a', 's'],
        ];
      }
    }

    $build['translations'] = [
      '#type' => 'html_tag',
      '#tag' => 'div',
      '#attributes' => ['class' => ['gin-utility-flex', 'gin-utility-gap-2']],
    ];

    foreach ($items as $item) {
      $build['translations'][] = [
        '#type' => 'html_tag',
        '#tag' => 'span',
        '#value' => $item['#markup'],
      ];
    }

    return $build;
  }

  /**
   * Helper function to load nodes based on the title search.
   */
  private function getNodes(
    $title = '',
    $content_type = '',
    $space = '',
    $language = '',
  ) {
    $query = $this->entityTypeManager->getStorage('node')->getQuery()
      ->sort('changed', 'DESC')
      ->accessCheck(FALSE)
      ->range(0, 100);

    if (!empty($title)) {
      $query->condition('title', '%' . $title . '%', 'LIKE');
    }

    if (!empty($content_type)) {
      $query->condition('type', $content_type);
    }

    if (!empty($language)) {
      $query->condition('langcode', $language);
    }

    if (!empty($space)) {
      $query->condition('nodehive_space', $space);
    }

    $nids = $query->execute();
    return $this->entityTypeManager->getStorage('node')->loadMultiple($nids);
  }

}
