<?php

namespace Drupal\Tests\eb\Unit\Plugin\EbValidator;

use Drupal\Core\Field\WidgetPluginManager;
use Drupal\eb\Plugin\EbValidator\WidgetCompatibilityValidator;
use Drupal\Tests\eb\Traits\ValidationAssertionsTrait;
use Drupal\Tests\eb\Unit\EbUnitTestBase;
use PHPUnit\Framework\MockObject\MockObject;

/**
 * Unit tests for WidgetCompatibilityValidator plugin.
 *
 * @coversDefaultClass \Drupal\eb\Plugin\EbValidator\WidgetCompatibilityValidator
 * @group eb
 */
class WidgetCompatibilityValidatorTest extends EbUnitTestBase {

  use ValidationAssertionsTrait;

  /**
   * Mock widget plugin manager.
   */
  protected WidgetPluginManager|MockObject $widgetManager;

  /**
   * The validator under test.
   */
  protected WidgetCompatibilityValidator $validator;

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

    $this->widgetManager = $this->createMock(WidgetPluginManager::class);

    $this->validator = new WidgetCompatibilityValidator(
      [],
      'widget_compatibility',
      [
        'id' => 'widget_compatibility',
        'label' => 'Widget Compatibility Validator',
      ],
      $this->widgetManager
    );
  }

  /**
   * Gets mock widget definitions.
   *
   * @return array<string, array<string, mixed>>
   *   Widget definitions.
   */
  protected function getWidgetDefinitions(): array {
    return [
      'string_textfield' => [
        'id' => 'string_textfield',
        'label' => 'Textfield',
        'field_types' => ['string', 'string_long'],
      ],
      'text_textarea' => [
        'id' => 'text_textarea',
        'label' => 'Textarea',
        'field_types' => ['text_long', 'text_with_summary'],
      ],
      'boolean_checkbox' => [
        'id' => 'boolean_checkbox',
        'label' => 'Single on/off checkbox',
        'field_types' => ['boolean'],
      ],
      'entity_reference_autocomplete' => [
        'id' => 'entity_reference_autocomplete',
        'label' => 'Autocomplete',
        'field_types' => ['entity_reference'],
      ],
      'options_select' => [
        'id' => 'options_select',
        'label' => 'Select list',
        'field_types' => ['list_string', 'list_integer', 'entity_reference'],
      ],
    ];
  }

  /**
   * Tests validation passes when widget is compatible.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenWidgetCompatible(): void {
    $this->widgetManager
      ->method('getDefinitions')
      ->willReturn($this->getWidgetDefinitions());

    $data = [
      'field_type' => 'string',
      'widget' => 'string_textfield',
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation fails when widget is not compatible.
   *
   * @covers ::validate
   */
  public function testValidateFailsWhenWidgetNotCompatible(): void {
    $this->widgetManager
      ->method('getDefinitions')
      ->willReturn($this->getWidgetDefinitions());

    $data = [
      'field_type' => 'boolean',
      'widget' => 'string_textfield',
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultFails($result);
    $this->assertValidationHasErrorCode($result, 'incompatible_widget');
  }

  /**
   * Tests validation fails when widget does not exist.
   *
   * @covers ::validate
   */
  public function testValidateFailsWhenWidgetNotFound(): void {
    $this->widgetManager
      ->method('getDefinitions')
      ->willReturn($this->getWidgetDefinitions());

    $data = [
      'field_type' => 'string',
      'widget' => 'nonexistent_widget',
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultFails($result);
    $this->assertValidationHasErrorCode($result, 'widget_not_found');
  }

  /**
   * Tests validation passes when widget is not specified.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenNoWidget(): void {
    $data = [
      'field_type' => 'string',
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation passes when field_type is not specified.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenNoFieldType(): void {
    $data = [
      'widget' => 'string_textfield',
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation with widget as array with type key.
   *
   * @covers ::validate
   */
  public function testValidateWithWidgetAsArray(): void {
    $this->widgetManager
      ->method('getDefinitions')
      ->willReturn($this->getWidgetDefinitions());

    $data = [
      'field_type' => 'string',
      'widget' => [
        'type' => 'string_textfield',
        'settings' => ['size' => 60],
      ],
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation with incompatible widget as array.
   *
   * @covers ::validate
   */
  public function testValidateFailsWithIncompatibleWidgetAsArray(): void {
    $this->widgetManager
      ->method('getDefinitions')
      ->willReturn($this->getWidgetDefinitions());

    $data = [
      'field_type' => 'boolean',
      'widget' => [
        'type' => 'string_textfield',
        'settings' => [],
      ],
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultFails($result);
  }

  /**
   * Tests validation passes when widget array has no type.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenWidgetArrayNoType(): void {
    $data = [
      'field_type' => 'string',
      'widget' => [
        'settings' => ['size' => 60],
      ],
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests error message includes compatible widgets.
   *
   * @covers ::validate
   */
  public function testValidateErrorIncludesCompatibleWidgets(): void {
    $this->widgetManager
      ->method('getDefinitions')
      ->willReturn($this->getWidgetDefinitions());

    $data = [
      'field_type' => 'entity_reference',
      'widget' => 'string_textfield',
    ];

    $result = $this->validator->validate($data);
    $errors = $result->getErrors();

    // Should include entity_reference_autocomplete and options_select as
    // compatible.
    $this->assertStringContainsString('entity_reference_autocomplete', (string) $errors[0]['message']);
  }

  /**
   * Tests validation with various field type and widget combinations.
   *
   * @covers ::validate
   * @dataProvider fieldTypeWidgetProvider
   */
  public function testValidateWithVariousCombinations(string $fieldType, string $widget, bool $expectPass): void {
    $this->widgetManager
      ->method('getDefinitions')
      ->willReturn($this->getWidgetDefinitions());

    $data = [
      'field_type' => $fieldType,
      'widget' => $widget,
    ];

    $result = $this->validator->validate($data);

    if ($expectPass) {
      $this->assertValidationResultPasses($result);
    }
    else {
      $this->assertValidationResultFails($result);
    }
  }

  /**
   * Data provider for field type and widget combinations.
   *
   * @return array<string, array<mixed>>
   *   Test cases.
   */
  public static function fieldTypeWidgetProvider(): array {
    return [
      'string_with_textfield' => ['string', 'string_textfield', TRUE],
      'string_long_with_textfield' => ['string_long', 'string_textfield', TRUE],
      'text_long_with_textarea' => ['text_long', 'text_textarea', TRUE],
      'boolean_with_checkbox' => ['boolean', 'boolean_checkbox', TRUE],
      'entity_reference_with_autocomplete' => ['entity_reference', 'entity_reference_autocomplete', TRUE],
      'entity_reference_with_select' => ['entity_reference', 'options_select', TRUE],
      'list_string_with_select' => ['list_string', 'options_select', TRUE],
      'string_with_checkbox' => ['string', 'boolean_checkbox', FALSE],
      'boolean_with_textarea' => ['boolean', 'text_textarea', FALSE],
    ];
  }

  /**
   * Tests getPluginId returns correct ID.
   *
   * @covers ::getPluginId
   */
  public function testGetPluginIdReturnsCorrectId(): void {
    $this->assertEquals('widget_compatibility', $this->validator->getPluginId());
  }

}
