<?php

declare(strict_types=1);

namespace Drupal\Tests\ai_ckeditor\FunctionalJavascript;

use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;

/**
 * Covers drupalSettings entity context wiring and its JS consumption.
 *
 * Tests two concerns in the same browser environment:
 *
 * 1. hook_form_alter: populates drupalSettings.aiCkeditor.entityContext on
 *    entity forms (keyed by the form's HTML id), and leaves that key absent
 *    on non-entity forms.
 *
 * 2. getEntityContext() in AiDrupalDialog.js: reads that registry entry via
 *    the host form's HTML id and returns {entityType, entityId}, falling back
 *    to empty strings when no entry exists. Because the function lives inside
 *    a CKEditor5 ES module that is not exposed globally, its logic is
 *    replicated verbatim in evaluateScript() so the behavior is tested
 *    without mocking or instantiating a full CKEditor instance.
 *
 * @group ai_ckeditor
 */
class EntityContextJavascriptTest extends WebDriverTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'node',
    'filter',
    'editor',
    'ckeditor5',
    'ai',
    'ai_ckeditor',
  ];

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

  /**
   * A user account that can create and edit article nodes.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $editorUser;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    NodeType::create(['type' => 'article', 'name' => 'Article'])->save();

    $this->editorUser = $this->drupalCreateUser([
      'access content',
      'create article content',
      'edit any article content',
    ]);
  }

  /**
   * Existing entity form: entityContext entry has correct entity_type and id.
   *
   * @covers ai_ckeditor_form_alter
   */
  public function testEntityFormPopulatesDrupalSettings(): void {
    $node = Node::create(['type' => 'article', 'title' => 'Test node']);
    $node->setOwner($this->editorUser)->save();

    $this->drupalLogin($this->editorUser);
    $this->drupalGet('node/' . $node->id() . '/edit');

    // drupalSettings.aiCkeditor.entityContext is keyed by the form's HTML id.
    // Grab whichever key was written; there must be exactly one.
    $js = <<<'JS'
      (function () {
        var ctx = (drupalSettings.aiCkeditor || {}).entityContext || {};
        var keys = Object.keys(ctx);
        if (!keys.length) { return null; }
        var entry = ctx[keys[0]];
        return JSON.stringify({ entity_type: entry.entity_type, id: entry.id });
      })()
    JS;

    $raw = $this->getSession()->evaluateScript($js);
    $this->assertNotNull($raw, 'drupalSettings.aiCkeditor.entityContext must be set on an entity form.');

    $entry = json_decode($raw, TRUE);
    $this->assertSame('node', $entry['entity_type']);
    $this->assertSame((string) $node->id(), $entry['id']);
  }

  /**
   * New-entity form: entityContext entry exists but id is an empty string.
   *
   * @covers ai_ckeditor_form_alter
   */
  public function testNewEntityFormHasEmptyId(): void {
    $this->drupalLogin($this->editorUser);
    $this->drupalGet('node/add/article');

    $js = <<<'JS'
      (function () {
        var ctx = (drupalSettings.aiCkeditor || {}).entityContext || {};
        var keys = Object.keys(ctx);
        if (!keys.length) { return null; }
        return ctx[keys[0]].id;
      })()
    JS;

    $id = $this->getSession()->evaluateScript($js);
    $this->assertNotNull($id, 'entityContext entry must exist on the node create form.');
    $this->assertSame('', $id, 'id must be an empty string for an unsaved entity.');
  }

  /**
   * Non-entity form: entityContext must not be populated.
   *
   * @covers ai_ckeditor_form_alter
   */
  public function testNonEntityFormHasNoEntityContext(): void {
    // The user login form is a plain form, not an entity form.
    $this->drupalGet('user/login');

    $js = <<<'JS'
      (function () {
        var ctx = (drupalSettings.aiCkeditor || {}).entityContext || {};
        return Object.keys(ctx).length;
      })()
    JS;

    $count = $this->getSession()->evaluateScript($js);
    $this->assertSame(0, $count, 'entityContext must be empty on a non-entity form.');
  }

  /**
   * Method getEntityContext() returns entity_type and id from drupalSettings.
   *
   * The snippet below replicates the body of getEntityContext() in
   * AiDrupalDialog.js exactly (host form → form id → registry lookup).
   * Running it in a real browser on a real entity edit form proves the whole
   * chain works end-to-end without requiring a live CKEditor instance.
   *
   * @covers \Drupal\ai_ckeditor\Form\AiCKEditorDialogForm::buildForm
   */
  public function testGetEntityContextReadsSettings(): void {
    $node = Node::create(['type' => 'article', 'title' => 'Context node']);
    $node->setOwner($this->editorUser)->save();

    $this->drupalLogin($this->editorUser);
    $this->drupalGet('node/' . $node->id() . '/edit');

    // Replicate getEntityContext(editor) where editor.sourceElement is a
    // textarea inside the node edit form.  We use the first textarea as a
    // stand-in since the logic only calls .closest('form') on it.
    $js = <<<'JS'
      (function () {
        var result = { entityType: '', entityId: '' };
        try {
          // Mirror the production code: walk up from any field element to <form>.
          var hostForm = document.querySelector('form[id]');
          var formId = hostForm ? hostForm.getAttribute('id') : null;
          var registry = (drupalSettings.aiCkeditor || {}).entityContext;
          if (!formId || !registry) { return JSON.stringify(result); }
          var entry = registry[formId];
          if (!entry) { return JSON.stringify(result); }
          result.entityType = entry.entity_type || '';
          result.entityId   = entry.id          || '';
        } catch (e) {}
        return JSON.stringify(result);
      })()
    JS;

    $data = json_decode($this->getSession()->evaluateScript($js), TRUE);
    $this->assertSame('node', $data['entityType']);
    $this->assertSame((string) $node->id(), $data['entityId']);
  }

  /**
   * Method getEntityContext() returns empty strings on a non-entity page.
   *
   * @covers \Drupal\ai_ckeditor\Form\AiCKEditorDialogForm::buildForm
   */
  public function testGetEntityContextFallsBackToEmptyStrings(): void {
    $this->drupalGet('user/login');

    $js = <<<'JS'
      (function () {
        var result = { entityType: '', entityId: '' };
        try {
          var hostForm = document.querySelector('form[id]');
          var formId = hostForm ? hostForm.getAttribute('id') : null;
          var registry = (drupalSettings.aiCkeditor || {}).entityContext;
          if (!formId || !registry) { return JSON.stringify(result); }
          var entry = registry[formId];
          if (!entry) { return JSON.stringify(result); }
          result.entityType = entry.entity_type || '';
          result.entityId   = entry.id          || '';
        } catch (e) {}
        return JSON.stringify(result);
      })()
    JS;

    $data = json_decode($this->getSession()->evaluateScript($js), TRUE);
    $this->assertSame('', $data['entityType'], 'entityType must be empty string on a non-entity page.');
    $this->assertSame('', $data['entityId'], 'entityId must be empty string on a non-entity page.');
  }

}
