<?php

declare(strict_types=1);

namespace Drupal\Tests\acquia_dam\Functional\Drush\Commands;

use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\Tests\BrowserTestBase;
use Drush\TestTraits\DrushTestTrait;

/**
 * Tests Acquia DAM Integration Links Drush Commands.
 *
 * @group acquia_dam
 */
class AcquiaDamIntegrationLinksDrushCommandsTest extends BrowserTestBase {

  use DrushTestTrait;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'acquia_dam',
    'acquia_dam_test',
    'node',
    'field',
    'media',
    'media_library',
    'taxonomy',
    'user',
    'block_content',
  ];

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

  /**
   * {@inheritdoc}
   */
  protected bool $useOneTimeLoginLinks = FALSE;

  /**
   * Created media entities for testing.
   *
   * @var \Drupal\media\Entity\Media[]
   */
  protected array $mediaEntities = [];

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

    // Create Image media entity.
    $imageMedia = Media::create([
      'bundle' => 'acquia_dam_image_asset',
      'name' => 'Wheel Illustration.ai',
      'acquia_dam_asset_id' => [
        'asset_id' => '56ff14de-02cd-41b5-9a73-c917eab19abf',
        'version_id' => '4f656c07-6a08-47b3-9403-16082d2fcda5',
        'external_id' => 'kcnabdscl5',
      ],
    ]);
    $imageMedia->save();
    $this->mediaEntities['image'] = $imageMedia;

    // Create PDF media entity.
    $pdfMedia = Media::create([
      'bundle' => 'acquia_dam_pdf_asset',
      'name' => 'bobsCarsAgileExercises.pdf',
      'acquia_dam_asset_id' => [
        'asset_id' => 'a56fb261-8ad5-4e0d-8323-0e8a3659ed39',
        'version_id' => '56768476-748a-46c1-ad40-ac23f16654qc',
        'external_id' => 'jwel87liro',
      ],
    ]);
    $pdfMedia->save();
    $this->mediaEntities['pdf'] = $pdfMedia;
  }

  /**
   * Setup content type with media reference field for enhanced mode testing.
   */
  protected function setupContentTypeWithMediaField(): void {
    // Create a content type.
    $node_type = NodeType::create([
      'type' => 'article',
      'name' => 'Article',
    ]);
    $node_type->save();

    // Create field storage for media reference.
    $field_storage = FieldStorageConfig::create([
      'field_name' => 'field_media_reference',
      'entity_type' => 'node',
      'type' => 'entity_reference',
      'settings' => [
        'target_type' => 'media',
      ],
      'cardinality' => -1,
    ]);
    $field_storage->save();

    // Create field instance.
    $field = FieldConfig::create([
      'field_storage' => $field_storage,
      'bundle' => 'article',
      'label' => 'Media Reference',
      'settings' => [
        'handler' => 'default:media',
        'handler_settings' => [
          'target_bundles' => [
            'acquia_dam_image_asset' => 'acquia_dam_image_asset',
            'acquia_dam_pdf_asset' => 'acquia_dam_pdf_asset',
          ],
        ],
      ],
    ]);
    $field->save();

    // Configure entity form display to use media_library widget.
    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
    $display_repository = \Drupal::service('entity_display.repository');
    $form_display = $display_repository->getFormDisplay('node', 'article');
    $form_display->setComponent('field_media_reference', [
      'type' => 'media_library_widget',
      'settings' => [
        'media_types' => [
          'acquia_dam_image_asset',
          'acquia_dam_pdf_asset',
        ],
      ],
    ]);
    $form_display->save();

    // Configure entity view display.
    $view_display = $display_repository->getViewDisplay('node', 'article');
    $view_display->setComponent('field_media_reference', [
      'type' => 'entity_reference_entity_view',
    ]);
    $view_display->save();
  }

  /**
   * Create nodes that reference DAM media entities.
   */
  protected function createNodesWithMediaReferences(): array {
    $image_id = $this->mediaEntities['image']->id();
    $pdf_id = $this->mediaEntities['pdf']->id();

    $definitions = [
      [
        'storage' => 'node',
        'values' => [
          'type' => 'article',
          'title' => 'Article with Image',
          'field_media_reference' => [
            ['target_id' => $image_id],
          ],
        ],
      ],
      [
        'storage' => 'node',
        'values' => [
          'type' => 'article',
          'title' => 'Article with PDF',
          'field_media_reference' => [
            ['target_id' => $pdf_id],
          ],
        ],
      ],
      [
        'storage' => 'node',
        'values' => [
          'type' => 'article',
          'title' => 'Article with Both Media',
          'field_media_reference' => [
            ['target_id' => $image_id],
            ['target_id' => $pdf_id],
          ],
        ],
      ],
    ];

    return $this->createEntitiesFromDefinitions($definitions);
  }

  /**
   * Data provider for queue integration links command tests.
   */
  public static function queueIntegrationLinksCommandProvider(): array {
    return [
      'default options' => [
        'options' => [],
      ],
      'batch size 1' => [
        'options' => ['batch-size' => 1],
      ],
      'media type filter' => [
        'options' => ['media-type' => 'acquia_dam_image_asset'],
      ],
    ];
  }

  /**
   * Tests the acquia-dam:queue-integration-links Drush command variants.
   *
   * @dataProvider queueIntegrationLinksCommandProvider
   */
  public function testQueueIntegrationLinksCommandVariants(array $options): void {
    // Clear queue before test to ensure clean state.
    $this->clearQueue();

    $this->drush('acquia-dam:queue-integration-links', [], $options);
    $queued = array_key_exists('media-type', $options) === TRUE ? 1 : 2;
    $this->assertQueueSummary($queued, $queued);
  }

  /**
   * Data provider for process integration links queue command tests.
   */
  public static function processIntegrationLinksQueueProvider(): array {
    return [
      'no limit' => [
        'options' => [],
        'expect_limit_message' => FALSE,
      ],
      'with limit' => [
        'options' => ['process-limit' => 1],
        'expect_limit_message' => TRUE,
      ],
    ];
  }

  /**
   * Tests the acquia-dam:process-integration-links-queue command variants.
   *
   * @dataProvider processIntegrationLinksQueueProvider
   */
  public function testProcessIntegrationLinksQueueCommandVariants(array $options, bool $expect_limit_message): void {
    // Clear queue and re-populate for this test.
    $this->clearQueue();
    $this->drush('acquia-dam:queue-integration-links');

    // Get queue count before processing.
    $queue_factory = \Drupal::service('queue');
    $queue = $queue_factory->get('acquia_dam_integration_links');
    $items_before = $queue->numberOfItems();

    $this->drush('acquia-dam:process-integration-links-queue', [], $options);

    // Determine expected items after processing.
    $expected_processed = $expect_limit_message ? 1 : 2;
    $expected_remaining = $expect_limit_message ? 1 : 0;

    $this->assertProcessSummary($items_before, $expected_processed, $expected_remaining, $expect_limit_message);
  }

  /**
   * Tests processing queue when it is empty.
   */
  public function testProcessEmptyIntegrationLinksQueue(): void {
    // Ensure queue is empty.
    $this->clearQueue();

    $this->drush('acquia-dam:process-integration-links-queue');
    $output = $this->getOutput();
    $this->assertStringContainsString('Processing integration links queue (items in queue: 0)', $output);
    $this->assertStringContainsString('Queue is empty. Nothing to process.', $output);
  }

  /**
   * Data provider for validation tests.
   */
  public static function validationTestProvider(): array {
    return [
      'invalid batch-size' => [
        'options' => [
          'batch-size' => 0,
        ],
        'expected_message' => 'Invalid batch-size. Must be greater than 0.',
        'setup_callback' => NULL,
      ],
      'non-existent media-type' => [
        'options' => [
          'media-type' => 'non_existent_type',
        ],
        'expected_message' => 'Cannot queue integration links: The media type "non_existent_type" does not exist.',
        'setup_callback' => NULL,
      ],
      'non-DAM media-type' => [
        'options' => [
          'media-type' => 'test_non_dam',
        ],
        'expected_message' => 'Cannot queue integration links: The media type "test_non_dam" is not associated with the Acquia DAM source plugin.',
        'setup_callback' => 'setupNonDamMediaType',
      ],
      'invalid process-limit' => [
        'options' => [
          'process-limit' => -1,
        ],
        'expected_message' => 'Invalid process-limit. Must be 0 (no limit) or a positive number.',
        'setup_callback' => NULL,
      ],
    ];
  }

  /**
   * Tests validation parameters using data provider.
   *
   * @dataProvider validationTestProvider
   */
  public function testValidation(array $options, string $expected_message, ?string $setup_callback): void {
    if ($setup_callback) {
      $this->{$setup_callback}();
    }
    // Validation failures should return exit code 1.
    $this->drush('acquia-dam:register-integration-links', [], $options, NULL, NULL, 1);
    $output = $this->getErrorOutput();
    $this->assertStringContainsString($expected_message, $output);
  }

  /**
   * Setup helper for non-DAM media type test.
   */
  private function setupNonDamMediaType(): void {
    $media_type = \Drupal::entityTypeManager()->getStorage('media_type')->create([
      'id' => 'test_non_dam',
      'label' => 'Test Non-DAM',
      'source' => 'image',
    ]);
    $media_type->save();
  }

  /**
   * Tests command aliases work correctly in basic mode.
   */
  public function testCommandAliases(): void {
    // Clear queue before testing aliases.
    $this->clearQueue();

    // Queue command alias - this will add items to queue.
    $this->drush('adqil');
    $this->assertQueueSummary(2, 2);

    // Verify output shows basic mode.
    $output = $this->getOutput();
    $this->assertStringContainsString('Basic mode - processing media entities only', $output);

    // Verify queue has items before processing.
    $queue_factory = \Drupal::service('queue');
    $queue = $queue_factory->get('acquia_dam_integration_links');
    $items_before_processing = $queue->numberOfItems();
    $this->assertSame(2, $items_before_processing, 'Queue should have 2 items after queueing.');

    // Process command alias - this will process and empty the queue.
    $this->drush('adpilq');
    $this->assertProcessSummary($items_before_processing, 2, 0);

    // Verify queue is now empty after processing.
    $items_after_processing = $queue->numberOfItems();
    $this->assertSame(0, $items_after_processing, 'Queue should be empty after processing.');
  }

  /**
   * Tests command aliases work correctly in enhanced mode.
   */
  public function testCommandAliasesEnhancedMode(): void {
    // Enable the enhanced mode module.
    $this->enableModules(['acquia_dam_integration_links']);
    $this->rebuildContainer();

    // Setup content type with media field.
    $this->setupContentTypeWithMediaField();

    // Create nodes that reference DAM media.
    $nodes = $this->createNodesWithMediaReferences();
    $this->assertCount(3, $nodes, 'Three nodes should be created.');

    // Clear queue before testing aliases.
    $this->clearQueue();

    // Queue command alias - this will add items to queue.
    $this->drush('adqil');
    // Expect 6 items: 2 media items + 4 content entity references.
    $this->assertQueueSummary(6, 5, TRUE, [
      'node',
    ]);

    // Process command alias - this will process and empty the queue.
    $this->drush('adpilq');
    $this->assertProcessSummary(6, 6, 0);
  }

  /**
   * Tests enhanced mode correctly tracks media references in content entities.
   */
  public function testEnhancedModeTracksMediaReferencesInContent(): void {
    // Enable the enhanced mode module.
    $this->enableModules(['acquia_dam_integration_links']);
    $this->rebuildContainer();

    // Setup content type with media field.
    $this->setupContentTypeWithMediaField();

    // Create nodes that reference DAM media.
    $this->createNodesWithMediaReferences();

    // Clear queue before test.
    $this->clearQueue();

    // Queue integration links.
    $this->drush('acquia-dam:queue-integration-links');
    // Expect 6 items: 2 media items + 4 content entity references
    // (node1: 1 image, node2: 1 pdf, node3: 1 image + 1 pdf = 4 total).
    $this->assertQueueSummary(6, 5, TRUE, [
      'node',
    ]);
  }

  /**
   * Asserts that the queue summary output is present.
   *
   * @param int $queued
   *   The expected number of items in the queue at start.
   * @param int $processed
   *   The expected number of processed items.
   * @param bool $enhanced_mode
   *   Whether enhanced mode is expected to be enabled.
   * @param array $options
   *   Additional options to check for in output,
   *   keyed by entity type (e.g., 'node', 'taxonomy_term').
   *   This is used to verify that specific entity types were
   *   processed in enhanced mode.
   */
  private function assertQueueSummary(int $queued, int $processed, bool $enhanced_mode = FALSE, array $options = []): void {
    $output = $this->getOutput();
    $this->assertStringContainsString("=== QUEUE SUMMARY ===", $output);
    $this->assertStringContainsString("Total entities processed: {$processed}", $output);
    $this->assertStringContainsString("Integration links queued: {$queued}", $output);
    $this->assertStringContainsString("Integration links have been queued and will be registered during the next cron run or when the queue is manually processed.", $output);

    if ($enhanced_mode) {
      $this->assertStringContainsString('Enhanced mode: Queued links for media + content entities', $output);
      $this->assertStringContainsString("Enhanced mode enabled - acquia_dam_integration_links submodule detected\nWill process both media entities and content entities with DAM references", $output);
      $this->assertStringContainsString('Processing content entities with DAM references', $output);
      if (array_key_exists('block_content', $options)) {
        $this->assertStringContainsString('block_content: batch 1/1 (100%)', $output);
      }
      if (array_key_exists('node', $options)) {
        $this->assertStringContainsString('node: batch 1/1 (100%)', $output);
      }
      if (array_key_exists('taxonomy_term', $options)) {
        $this->assertStringContainsString('taxonomy_term: batch 1/1 (100%)', $output);
      }
      if (array_key_exists('user', $options)) {
        $this->assertStringContainsString('user: batch 1/1 (100%)', $output);
      }

    }

    // Verify queue actually has items.
    $queue_factory = \Drupal::service('queue');
    $queue = $queue_factory->get('acquia_dam_integration_links');
    $this->assertGreaterThanOrEqual($queued, $queue->numberOfItems(), "Queue should have at least {$queued} items after queueing.");
  }

  /**
   * Asserts that the processing summary output is present.
   *
   * @param int $queued
   *   The expected number of items in the queue at start.
   * @param int $processed
   *   The expected number of processed items.
   * @param int $remaining
   *   The expected number of remaining items in the queue.
   * @param bool $expect_limit_message
   *   Whether to expect the processing limit message.
   */
  private function assertProcessSummary(int $queued, int $processed, int $remaining, bool $expect_limit_message = FALSE): void {
    $output = $this->getOutput();

    $this->assertStringContainsString("Processing integration links queue (items in queue: {$queued})", $output);
    if ($queued) {
      $this->assertStringContainsString('=== PROCESSING SUMMARY ===', $output);
      $this->assertStringContainsString("Items processed: {$processed}", $output);
      $this->assertStringContainsString('Items failed: 0', $output);
      $this->assertStringContainsString("Items remaining: {$remaining}", $output);
      if ($expect_limit_message) {
        $this->assertStringContainsString('Reached processing limit of 1 items', $output);
        $this->assertStringContainsString('There are still items in the queue. Run this command again to continue processing.', $output);
      }
      else {
        $this->assertStringNotContainsString('Reached processing limit of 1 items', $output);
        $this->assertStringContainsString('✓ Queue processing complete!', $output);
      }
    }
    else {
      $this->assertStringContainsString('Queue is empty. Nothing to process.', $output);
    }
  }

  /**
   * Helper to clear queue before test execution.
   */
  private function clearQueue(): void {
    $queue_factory = \Drupal::service('queue');
    $queue = $queue_factory->get('acquia_dam_integration_links');
    $queue->deleteQueue();

    // Also clear the tracking table to ensure clean state.
    \Drupal::database()->truncate('acquia_dam_integration_link_tracking')->execute();
  }

  /**
   * Enable additional modules during test execution.
   *
   * @param array $modules
   *   Array of module names to enable.
   */
  protected function enableModules(array $modules): void {
    $module_installer = \Drupal::service('module_installer');
    $module_installer->install($modules);
  }

  /**
   * Tests dynamic discovery finds all entity types with DAM references.
   *
   * @covers \Drupal\acquia_dam\Service\IntegrationLinksBatchProcessor::discoverEntityTypesWithDamReferences
   */
  public function testDynamicDiscoveryFindsMultipleEntityTypes(): void {
    // Enable enhanced mode.
    $this->enableModules(['acquia_dam_integration_links']);
    $this->rebuildContainer();

    // Setup fields on multiple entity types.
    $this->setupMultipleEntityTypesWithDamFields();

    // Create entities with DAM references.
    $this->createEntitiesAcrossMultipleTypes();

    // Clear queue before test.
    $this->clearQueue();

    // Run queue command - should discover and process all entity types.
    $this->drush('acquia-dam:queue-integration-links');

    $output = $this->getOutput();

    // Verify enhanced mode is detected.
    $this->assertStringContainsString('Enhanced mode enabled', $output);
    $this->assertStringContainsString('Processing content entities with DAM references', $output);

    // Verify all entity types are processed.
    $this->assertStringContainsString('node: batch', $output);
    $this->assertStringContainsString('taxonomy_term: batch', $output);
    $this->assertStringContainsString('user: batch', $output);
    $this->assertStringContainsString('block_content: batch', $output);

    $this->assertQueueSummary(10, 12, TRUE, [
      'block_content',
      'node',
      'taxonomy_term',
      'user',
    ]);
  }

  /**
   * Tests dynamic discovery skips entity types with non-DAM media references.
   *
   * @covers \Drupal\acquia_dam\Service\IntegrationLinksBatchProcessor::discoverEntityTypesWithDamReferences
   */
  public function testDynamicDiscoverySkipsNonDamMediaReferences(): void {
    // Enable enhanced mode.
    $this->enableModules(['acquia_dam_integration_links']);
    $this->rebuildContainer();

    // Setup node with non-DAM media field.
    $this->setupNodeWithNonDamMediaField();

    // Create a core image media entity.
    $non_dam_media = Media::create([
      'bundle' => 'image',
      'name' => 'Non-DAM Image',
    ]);
    $non_dam_media->save();

    // Create a node referencing non-DAM media.
    $node = Node::create([
      'type' => 'article',
      'title' => 'Article with Non-DAM Media',
      'field_non_dam_media' => [
        ['target_id' => $non_dam_media->id()],
      ],
    ]);
    $node->save();

    // Clear queue before test.
    $this->clearQueue();

    // Run queue command.
    $this->drush('acquia-dam:queue-integration-links');

    $output = $this->getOutput();

    // Should still process and queue the 2 DAM media entities from setUp().
    // Should process the node with non-DAM media.
    $this->assertQueueSummary(2, 2, TRUE, []);

    // Verify node was not discovered.
    $this->assertStringNotContainsString('node: batch', $output);

  }

  /**
   * Setup helper for creating fields on multiple entity types.
   *
   * Creates entity_reference fields pointing to DAM media on:
   * - node (article)
   * - taxonomy_term (tags)
   * - user
   * - block_content (basic)
   */
  protected function setupMultipleEntityTypesWithDamFields(): void {
    // 1. Setup Node (article) with DAM image field.
    $node_type = NodeType::load('article');
    if (!$node_type) {
      $node_type = NodeType::create([
        'type' => 'article',
        'name' => 'Article',
      ]);
      $node_type->save();
    }

    $this->createMediaReferenceField(
      'node',
      'article',
      'field_dam_image',
      'DAM Image',
      ['acquia_dam_image_asset']
    );

    // 2. Setup Taxonomy Term (tags) with unrestricted media field.
    $vocabulary_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary');
    $vocabulary = $vocabulary_storage->load('tags');
    if (!$vocabulary) {
      $vocabulary = $vocabulary_storage->create([
        'vid' => 'tags',
        'name' => 'Tags',
      ]);
      $vocabulary->save();
    }

    $this->createMediaReferenceField(
      'taxonomy_term',
      'tags',
      'field_dam_video',
      'DAM Video',
      []
    );

    // 3. Setup User with DAM document field.
    $this->createMediaReferenceField(
      'user',
      'user',
      'field_dam_document',
      'DAM Document',
      ['acquia_dam_documents_asset']
    );

    // 4. Setup Block Content (basic) with multiple DAM types.
    $block_content_type_storage = \Drupal::entityTypeManager()
      ->getStorage('block_content_type');

    $block_content_type = $block_content_type_storage->load('basic');
    if (!$block_content_type) {
      $block_content_type = $block_content_type_storage->create([
        'id' => 'basic',
        'label' => 'Basic block',
      ]);
      $block_content_type->save();
    }

    $this->createMediaReferenceField(
      'block_content',
      'basic',
      'field_dam_asset',
      'DAM Asset',
      [
        'acquia_dam_image_asset',
        'acquia_dam_pdf_asset',
        'acquia_dam_documents_asset',
      ]
    );
  }

  /**
   * Helper to create entities across multiple entity types with DAM references.
   *
   * @return array
   *   Array of created entities keyed by entity type.
   */
  protected function createEntitiesAcrossMultipleTypes(): array {
    $entities = [
      'node' => [],
      'taxonomy_term' => [],
      'user' => [],
      'block_content' => [],
    ];

    $image_id = $this->mediaEntities['image']->id();
    $pdf_id = $this->mediaEntities['pdf']->id();

    $definitions = [
      // Nodes (2), each referencing DAM image.
      [
        'storage' => 'node',
        'count' => 2,
        'base' => static fn(int $i) => [
          'type' => 'article',
          'title' => "Node with DAM Image {$i}",
          'field_dam_image' => [
            ['target_id' => $image_id],
          ],
        ],
        'collector' => static function (array &$all, $created) {
          $all['node'] = array_merge($all['node'], $created);
        },
      ],
      // Taxonomy terms (2), each referencing DAM pdf.
      [
        'storage' => 'taxonomy_term',
        'count' => 2,
        'base' => static fn(int $i) => [
          'vid' => 'tags',
          'name' => "Term with DAM Video {$i}",
          'field_dam_video' => [
            ['target_id' => $pdf_id],
          ],
        ],
        'collector' => static function (array &$all, $created) {
          $all['taxonomy_term'] = array_merge($all['taxonomy_term'], $created);
        },
      ],
      // Users (2), each referencing DAM pdf.
      [
        'storage' => 'user',
        'count' => 2,
        'base' => static fn(int $i) => [
          'name' => "user_dam_{$i}",
          'mail' => "user_dam_{$i}@example.com",
          'status' => 1,
          'field_dam_document' => [
            ['target_id' => $pdf_id],
          ],
        ],
        'collector' => static function (array &$all, $created) {
          $all['user'] = array_merge($all['user'], $created);
        },
      ],
      // Blocks (2), each referencing DAM image.
      [
        'storage' => 'block_content',
        'count' => 2,
        'base' => static fn(int $i) => [
          'type' => 'basic',
          'info' => "Block with DAM Asset {$i}",
          'field_dam_asset' => [
            ['target_id' => $image_id],
          ],
        ],
        'collector' => static function (array &$all, $created) {
          $all['block_content'] = array_merge($all['block_content'], $created);
        },
      ],
    ];

    $created_by_def = $this->createEntitiesFromDefinitions($definitions);

    // Merge created entities into the typed buckets via collectors.
    foreach ($definitions as $index => $def) {
      if (isset($def['collector']) && is_callable($def['collector'])) {
        $def['collector']($entities, $created_by_def[$index] ?? []);
      }
    }

    return $entities;
  }

  /**
   * Generic helper to create entities from compact definitions.
   *
   * Each definition supports:
   * - storage: entity storage ID (node, taxonomy_term, user, block_content)
   * - values: direct values for a single entity
   * - base: callable(int $i): array values for multiple entities
   * - count: number of entities to create when using \`base\`
   *
   * @param array $definitions
   *   List of entity creation definitions.
   *
   * @return array
   *   Array keyed by definition index with arrays of created entities.
   */
  protected function createEntitiesFromDefinitions(array $definitions): array {
    $result = [];
    $entity_type_manager = \Drupal::entityTypeManager();

    foreach ($definitions as $idx => $def) {
      $storage_id = $def['storage'];
      $storage = $entity_type_manager->getStorage($storage_id);
      $created = [];

      if (isset($def['values'])) {
        $entity = $storage->create($def['values']);
        $entity->save();
        $created[] = $entity;
      }
      elseif (isset($def['base']) && is_callable($def['base'])) {
        $count = (int) ($def['count'] ?? 1);
        for ($i = 1; $i <= $count; $i++) {
          $values = $def['base']($i);
          $entity = $storage->create($values);
          $entity->save();
          $created[] = $entity;
        }
      }

      $result[$idx] = $created;
    }

    return $result;
  }

  /**
   * Helper method to create media reference field on any entity type.
   *
   * @param string $entity_type_id
   *   The entity type ID (e.g., 'node', 'taxonomy_term').
   * @param string $bundle
   *   The bundle (e.g., 'article', 'tags').
   * @param string $field_name
   *   The field name.
   * @param string $field_label
   *   The field label.
   * @param array $target_bundles
   *   Array of media type IDs to restrict to, empty for no restriction.
   */
  protected function createMediaReferenceField(
    string $entity_type_id,
    string $bundle,
    string $field_name,
    string $field_label,
    array $target_bundles = [],
  ): void {
    // Create field storage.
    $field_storage = FieldStorageConfig::create([
      'field_name' => $field_name,
      'entity_type' => $entity_type_id,
      'type' => 'entity_reference',
      'settings' => [
        'target_type' => 'media',
      ],
      'cardinality' => -1,
    ]);
    $field_storage->save();

    // Build handler settings.
    $handler_settings = ['handler' => 'default:media'];
    if (!empty($target_bundles)) {
      $handler_settings['handler_settings']['target_bundles'] = array_combine($target_bundles, $target_bundles);
    }

    // Create field instance.
    $field = FieldConfig::create([
      'field_storage' => $field_storage,
      'bundle' => $bundle,
      'label' => $field_label,
      'settings' => $handler_settings,
    ]);
    $field->save();
  }

  /**
   * Setup a node with field pointing to non-DAM media (control test).
   */
  protected function setupNodeWithNonDamMediaField(): void {
    // Ensure article type exists.
    $node_type = NodeType::load('article');
    if (!$node_type) {
      $node_type = NodeType::create([
        'type' => 'article',
        'name' => 'Article',
      ]);
      $node_type->save();
    }

    // Create a core 'image' media type if it doesn't exist.
    $image_media_type = MediaType::create([
      'id' => 'image',
      'label' => 'Image',
      'source' => 'image',
    ]);
    $image_media_type->save();
    $source_field = $image_media_type->getSource()->createSourceField($image_media_type);
    // @phpstan-ignore-next-line
    $source_field->getFieldStorageDefinition()->save();
    $source_field->save();
    $image_media_type
      ->set('source_configuration', [
        'source_field' => $source_field->getName(),
      ])
      ->save();

    // Create field pointing only to non-DAM media.
    $this->createMediaReferenceField(
      'node',
      'article',
      'field_non_dam_media',
      'Non-DAM Media',
      ['image']
    );
  }

}
