<?php

declare(strict_types=1);

namespace Drupal\Tests\Core\Menu;

use Drupal\Core\Menu\StaticMenuLinkOverrides;
use Drupal\Tests\UnitTestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;

/**
 * Tests Drupal\Core\Menu\StaticMenuLinkOverrides.
 */
#[CoversClass(StaticMenuLinkOverrides::class)]
#[Group('Menu')]
class StaticMenuLinkOverridesTest extends UnitTestCase {

  /**
   * Tests the reload method.
   */
  public function testReload(): void {
    $config_factory = $this->createMock('Drupal\Core\Config\ConfigFactoryInterface');
    $config_factory->expects($this->once())
      ->method('reset')
      ->with('core.menu.static_menu_link_overrides');

    $static_override = new StaticMenuLinkOverrides($config_factory);

    $static_override->reload();
  }

  /**
   * Tests the loadOverride method.
   *
   * @legacy-covers ::loadOverride
   * @legacy-covers ::getConfig
   */
  #[DataProvider('providerTestLoadOverride')]
  public function testLoadOverride($overrides, $id, $expected): void {
    $config_factory = $this->getConfigFactoryStub(['core.menu.static_menu_link_overrides' => ['definitions' => $overrides]]);
    $static_override = new StaticMenuLinkOverrides($config_factory);

    $this->assertEquals($expected, $static_override->loadOverride($id));
  }

  /**
   * Provides test data for testLoadOverride.
   */
  public static function providerTestLoadOverride(): array {
    $data = [];
    // Valid ID.
    $data[] = [['test1' => ['parent' => 'test0']], 'test1', ['parent' => 'test0']];
    // Non existing ID.
    $data[] = [['test1' => ['parent' => 'test0']], 'test2', []];
    // Ensure that the ID is encoded properly.
    $data[] = [['test1__la___ma' => ['parent' => 'test0']], 'test1.la__ma', ['parent' => 'test0']];

    return $data;
  }

  /**
   * Tests the loadMultipleOverrides method.
   *
   * @legacy-covers ::loadMultipleOverrides
   * @legacy-covers ::getConfig
   */
  public function testLoadMultipleOverrides(): void {
    $overrides = [];
    $overrides['test1'] = ['parent' => 'test0'];
    $overrides['test2'] = ['parent' => 'test1'];
    $overrides['test1__la___ma'] = ['parent' => 'test2'];

    $config_factory = $this->getConfigFactoryStub(['core.menu.static_menu_link_overrides' => ['definitions' => $overrides]]);
    $static_override = new StaticMenuLinkOverrides($config_factory);

    $this->assertEquals(
      [
        'test1' => ['parent' => 'test0'],
        'test1.la__ma' => ['parent' => 'test2'],
      ],
      $static_override->loadMultipleOverrides(['test1', 'test1.la__ma'])
    );
  }

  /**
   * Tests the saveOverride method.
   *
   * @legacy-covers ::saveOverride
   * @legacy-covers ::loadOverride
   * @legacy-covers ::getConfig
   */
  public function testSaveOverride(): void {
    $config = $this->getMockBuilder('Drupal\Core\Config\Config')
      ->disableOriginalConstructor()
      ->getMock();

    $definition_save_1 = [
      'definitions' => [
        'test1' => ['parent' => 'test0', 'menu_name' => '', 'weight' => 0, 'expanded' => FALSE, 'enabled' => FALSE],
      ],
    ];
    $definitions_save_2 = [
      'definitions' => [
        'test1' => [
          'parent' => 'test0',
          'menu_name' => '',
          'weight' => 0,
          'expanded' => FALSE,
          'enabled' => FALSE,
        ],
        'test1__la___ma' => [
          'parent' => 'test1',
          'menu_name' => '',
          'weight' => 0,
          'expanded' => FALSE,
          'enabled' => FALSE,
        ],
      ],
    ];

    $config->expects($this->exactly(4))
      ->method('get')
      ->with('definitions')
      ->willReturnOnConsecutiveCalls(
        [],
        [],
        $definition_save_1['definitions'],
        $definition_save_1['definitions'],
      );
    $definitions = [
      $definition_save_1['definitions'],
      $definitions_save_2['definitions'],
    ];
    $config->expects($this->exactly(count($definitions)))
      ->method('set')
      ->with('definitions', $this->callback(function (array $value) use (&$definitions): bool {
        return array_shift($definitions) === $value;
      }))
      ->willReturnSelf();
    $config->expects($this->exactly(2))
      ->method('save');

    $config_factory = $this->createMock('Drupal\Core\Config\ConfigFactoryInterface');
    $config_factory->expects($this->once())
      ->method('getEditable')
      ->willReturn($config);

    $static_override = new StaticMenuLinkOverrides($config_factory);

    $static_override->saveOverride('test1', ['parent' => 'test0']);
    $static_override->saveOverride('test1.la__ma', ['parent' => 'test1']);
  }

  /**
   * Tests the deleteOverride and deleteOverrides method.
   *
   * @param array|string $ids
   *   Either a single ID or multiple ones as array.
   * @param array $old_definitions
   *   The definitions before the deleting.
   * @param array $new_definitions
   *   The definitions after the deleting.
   */
  #[DataProvider('providerTestDeleteOverrides')]
  public function testDeleteOverrides($ids, array $old_definitions, array $new_definitions): void {
    $config = $this->getMockBuilder('Drupal\Core\Config\Config')
      ->disableOriginalConstructor()
      ->getMock();
    $config->expects($this->once())
      ->method('get')
      ->with('definitions')
      ->willReturn($old_definitions);

    // Only save if the definitions changes.
    $config->expects($old_definitions != $new_definitions ? $this->once() : $this->never())
      ->method('set')
      ->with('definitions', $new_definitions)
      ->willReturnSelf();
    $config->expects($old_definitions != $new_definitions ? $this->once() : $this->never())
      ->method('save');

    $config_factory = $this->createMock('Drupal\Core\Config\ConfigFactoryInterface');
    $config_factory->expects($this->once())
      ->method('getEditable')
      ->willReturn($config);

    $static_override = new StaticMenuLinkOverrides($config_factory);

    if (is_array($ids)) {
      $static_override->deleteMultipleOverrides($ids);
    }
    else {
      $static_override->deleteOverride($ids);
    }
  }

  /**
   * Provides test data for testDeleteOverrides.
   */
  public static function providerTestDeleteOverrides(): array {
    $data = [];
    // Delete a non existing ID.
    $data[] = ['test0', [], []];
    // Delete an existing ID.
    $data[] = ['test1', ['test1' => ['parent' => 'test0']], []];
    // Delete an existing ID with a special ID.
    $data[] = ['test1.la__ma', ['test1__la___ma' => ['parent' => 'test0']], []];
    // Delete multiple IDs.
    $data[] = [
      ['test1.la__ma', 'test1'],
      [
        'test1' => ['parent' => 'test0'],
        'test1__la___ma' => ['parent' => 'test0'],
      ],
      [],
    ];

    return $data;
  }

}
