<?php

declare(strict_types=1);

namespace Drupal\Tests\system\Functional\ParamConverter;

use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\BrowserTestBase;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;

// cspell:ignore deutscher titel
/**
 * Tests upcasting of URL arguments to entities.
 */
#[Group('ParamConverter')]
#[RunTestsInSeparateProcesses]
class UpcastingTest extends BrowserTestBase {

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

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

  /**
   * Confirms that all parameters are converted as expected.
   *
   * All of these requests end up being processed by a controller with the
   * signature: f($user, $node, $foo) returning either values or labels
   * like "user: Dries, node: First post, foo: bar"
   *
   * The test shuffles the parameters around and checks if the right thing is
   * happening.
   */
  public function testUpcasting(): void {
    $node = $this->drupalCreateNode(['title' => $this->randomMachineName(8)]);
    $user = $this->drupalCreateUser(['access content']);
    $foo = 'bar';

    // Test "paramconverter_test/test_user_node_foo/{user}/{node}/{foo}".
    $this->drupalGet("paramconverter_test/test_user_node_foo/" . $user->id() . '/' . $node->id() . "/$foo");
    // Verify user and node upcast by entity name.
    $this->assertSession()->pageTextContains("user: {$user->label()}, node: {$node->label()}, foo: $foo");

    // Test "paramconverter_test/test_node_user_user/{node}/{foo}/{user}" with
    // "options.parameters.foo.type = entity:user".
    $this->drupalGet("paramconverter_test/test_node_user_user/" . $node->id() . "/" . $user->id() . "/" . $user->id());
    // Verify foo converted to user as well.
    $this->assertSession()->pageTextContains("user: {$user->label()}, node: {$node->label()}, foo: {$user->label()}");

    // Test "paramconverter_test/test_node_node_foo/{user}/{node}/{foo}" with
    // "options.parameters.user.type = entity:node".
    $this->drupalGet("paramconverter_test/test_node_node_foo/" . $node->id() . "/" . $node->id() . "/$foo");
    // Verify that user is upcast to node (rather than to user).
    $this->assertSession()->pageTextContains("user: {$node->label()}, node: {$node->label()}, foo: $foo");
  }

  /**
   * Confirms we can upcast to controller arguments of the same type.
   */
  public function testSameTypes(): void {
    $node = $this->drupalCreateNode(['title' => $this->randomMachineName(8)]);
    $parent = $this->drupalCreateNode(['title' => $this->randomMachineName(8)]);
    // Test "paramconverter_test/node/{node}/set/parent/{parent}" with
    // "options.parameters.parent.type = entity:node".
    $this->drupalGet("paramconverter_test/node/" . $node->id() . "/set/parent/" . $parent->id());
    $this->assertSession()->pageTextContains("Setting '" . $parent->getTitle() . "' as parent of '" . $node->getTitle() . "'.");
  }

  /**
   * Confirms entity is shown in user's language by default.
   */
  public function testEntityLanguage(): void {
    $language = ConfigurableLanguage::createFromLangcode('de');
    $language->save();
    \Drupal::configFactory()->getEditable('language.negotiation')
      ->set('url.prefixes', ['de' => 'de'])
      ->save();

    // The container must be recreated after adding a new language.
    $this->rebuildContainer();

    $node = $this->drupalCreateNode(['title' => 'English label']);
    $translation = $node->addTranslation('de');
    $translation->setTitle('Deutscher Titel')->save();

    $this->drupalGet("/paramconverter_test/node/" . $node->id() . "/test_language");
    $this->assertSession()->pageTextContains("English label");
    $this->drupalGet("paramconverter_test/node/" . $node->id() . "/test_language", ['language' => $language]);
    $this->assertSession()->pageTextContains("Deutscher Titel");
  }

  /**
   * Tests that non-ASCII config entity IDs in routes return 404.
   *
   * Config entity routes get regex requirements derived from their schema
   * constraints (e.g. machine_name pattern). Non-ASCII characters do not
   * match these patterns, so the router rejects them with a 404.
   */
  public function testNonAsciiConfigEntityRoute(): void {
    // No login needed: the router rejects the URL before access checking.
    $this->drupalGet('node/add/öüä');
    $this->assertSession()->statusCodeEquals(404);
  }

}
