<?php

namespace Drupal\Tests\gutenberg\FunctionalJavascript;

use Drupal\FunctionalJavascriptTests\WebDriverTestBase;

/**
 * Base class for JS enabled tests.
 */
abstract class GutenbergWebdriverTestBase extends WebDriverTestBase {

  /**
   * {@inheritdoc}
   */
  protected $defaultTheme = 'claro';

  /**
   * {@inheritdoc}
   */
  protected $strictConfigSchema = FALSE;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'gutenberg',
    'node',
  ];

  /**
   * The account.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $account;

  /**
   * {@inheritdoc}
   */
  public function setUp(): void {
    parent::setUp();
    // When we create a content type like this it will automatically get a body
    // field.
    $this->drupalCreateContentType([
      'type' => 'article',
    ]);
    $this->config('gutenberg.settings')
      ->set('article_enable_full', TRUE)->save();
    $this->account = $this->drupalCreateUser([
      'view the administration theme',
      'create article content',
      'edit any article content',
      'use text format gutenberg',
      'use gutenberg',
      // We need this to edit this content type and enable gutenberg and the
      // blocks we want.
      'administer content types',
    ]);
    $this->drupalLogin($this->account);
    $this->drupalGet('/node/add/article');
    $this->waitForGutenbergEditor();
  }

  /**
   * Waits for the Gutenberg editor to fully initialize and closes any modals.
   */
  protected function waitForGutenbergEditor() {
    $editor_selector = '.edit-post-header, .editor-header';
    $element = $this->assertSession()->waitForElement('css', $editor_selector, 15000);
    if (!$element) {
      $errors = $this->getSession()->evaluateScript(
        "return JSON.parse(sessionStorage.getItem('js_testing_log_test.errors') || '[]')"
      );
      $msg = 'Gutenberg editor did not initialize within 15s.';
      if (!empty($errors)) {
        $msg .= ' JS errors: ' . implode('; ', $errors);
      }
      $this->fail($msg);
    }
    // Try to close the welcome guide modal if it appears.
    $modal_close_selector = '.components-modal__screen-overlay .components-modal__header button';
    $element = $this->assertSession()->waitForElement('css', $modal_close_selector, 2000);
    if ($element) {
      $element->click();
      // Wait for the modal to disappear.
      $this->assertSession()->waitForElementRemoved('css', '.components-modal__screen-overlay');
    }
  }

  /**
   * Helper to assert that a block is not enabled.
   */
  protected function assertBlockIsNotEnabled($block, $content_type = 'article') {
    $blocks = $this->findBlocksByText($block, $content_type);
    if (!empty($blocks)) {
      throw new \Exception('Block ' . $block . ' was found, but should not have been.');
    }
  }

  /**
   * Helper to assert that a block is enabled.
   */
  protected function assertBlockIsEnabled($block, $content_type = 'article') {
    $blocks = $this->findBlocksByText($block, $content_type);
    if (empty($blocks)) {
      throw new \Exception('Block ' . $block . ' was not found, but should have been.');
    }
  }

  /**
   * Helper to find blocks by text.
   */
  protected function findBlocksByText($block, $content_type = 'article') {
    $this->drupalGet('/node/add/' . $content_type);
    $this->waitForGutenbergEditor();
    $insert_block_button = '.editor-document-tools__inserter-toggle';
    $this->assertSession()->waitForElement('css', $insert_block_button, 5000);
    $page = $this->getSession()->getPage();
    $page->find('css', $insert_block_button)->click();
    // Wait for the block list to render.
    $this->assertSession()->waitForElement('css', '.block-editor-inserter__block-list .block-editor-block-types-list__list-item', 5000);
    // Use JS to get block names since Mink getText() may not capture
    // React-rendered text content.
    $blockNames = $this->getSession()->evaluateScript(
      "return Array.from(document.querySelectorAll('.block-editor-inserter__block-list .block-editor-block-types-list__list-item')).map(function(el) { return el.textContent.trim(); })"
    );
    $blocks = [];
    foreach ($blockNames as $name) {
      if ($name === $block) {
        $blocks[] = $name;
      }
    }
    return $blocks;
  }

  /**
   * Helper method to get reusable blocks available.
   */
  protected function getReusableBlocks() {
    $block_storage = $this->container->get('entity_type.manager')->getStorage('block_content');
    // Reset cache as the result might have changed within another PHP thread.
    $block_storage->resetCache();
    return $block_storage
      ->loadByProperties(['type' => 'reusable_block']);
  }

}
