<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_stategraph\Unit\Plugin\FlowDropNodeProcessor;

use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\flowdrop_orchestration\DTO\Snapshot\IteratorState;
use Drupal\flowdrop_stategraph\Plugin\FlowDropNodeProcessor\LoopControlNode;
use Drupal\Tests\UnitTestCase;
use Prophecy\PhpUnit\ProphecyTrait;

/**
 * Tests the LoopControlNode processor.
 *
 * @coversDefaultClass \Drupal\flowdrop_stategraph\Plugin\FlowDropNodeProcessor\LoopControlNode
 * @group flowdrop_stategraph
 */
class LoopControlNodeTest extends UnitTestCase {

  use ProphecyTrait;

  /**
   * The LoopControlNode processor under test.
   *
   * @var \Drupal\flowdrop_stategraph\Plugin\FlowDropNodeProcessor\LoopControlNode
   */
  private LoopControlNode $node;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->node = new LoopControlNode([], 'loop_control', ['id' => 'loop_control']);
  }

  /**
   * @covers ::validateParams
   */
  public function testValidateParamsMissingActionFails(): void {
    $result = $this->node->validateParams([]);
    $this->assertFalse($result->isValid());
  }

  /**
   * @covers ::validateParams
   */
  public function testValidateParamsInvalidActionFails(): void {
    $result = $this->node->validateParams(['action' => 'invalid']);
    $this->assertFalse($result->isValid());
  }

  /**
   * @covers ::validateParams
   * @dataProvider validActionsProvider
   */
  public function testValidateParamsValidActions(string $action): void {
    $result = $this->node->validateParams(['action' => $action]);
    $this->assertTrue($result->isValid());
  }

  /**
   * Provides valid action values for testing.
   */
  public static function validActionsProvider(): array {
    return [
      'continue' => ['continue'],
      'break' => ['break'],
      'reset' => ['reset'],
      'check' => ['check'],
    ];
  }

  /**
   * @covers ::process
   */
  public function testConditionNotMetPassthrough(): void {
    $params = $this->createParams('break', FALSE);

    $output = $this->node->process($params->reveal());

    $this->assertEquals('none', $output['action_taken']);
    $this->assertFalse($output['condition_met']);
    $this->assertFalse($output['should_break']);
  }

  /**
   * @covers ::process
   */
  public function testContinueAction(): void {
    $iterator = IteratorState::fromItems(['a', 'b', 'c']);
    $params = $this->createParams('continue', TRUE, $iterator->toArray());

    $output = $this->node->process($params->reveal());

    $this->assertEquals('continue', $output['action_taken']);
    $this->assertTrue($output['should_continue']);
    $this->assertFalse($output['should_break']);
    $this->assertTrue($output['loop_active']);
  }

  /**
   * @covers ::process
   */
  public function testBreakAction(): void {
    $iterator = IteratorState::fromItems(['a', 'b', 'c']);
    $params = $this->createParams('break', TRUE, $iterator->toArray());

    $output = $this->node->process($params->reveal());

    $this->assertEquals('break', $output['action_taken']);
    $this->assertTrue($output['should_break']);
    $this->assertFalse($output['loop_active']);
    $this->assertIsArray($output['collected_results']);
    $this->assertNull($output['state_update']['iterator']);
  }

  /**
   * @covers ::process
   */
  public function testResetActionWithIterator(): void {
    $iterator = IteratorState::fromItems(['a', 'b', 'c']);
    // Advance to index 1.
    $iterator = $iterator->advance();
    $params = $this->createParams('reset', TRUE, $iterator->toArray());

    $output = $this->node->process($params->reveal());

    $this->assertEquals('reset', $output['action_taken']);
    $this->assertTrue($output['should_reset']);
    $this->assertTrue($output['loop_active']);
    $this->assertEquals(0, $output['current_index']);
    $this->assertEquals('a', $output['current_item']);
  }

  /**
   * @covers ::process
   */
  public function testResetActionWithoutIterator(): void {
    $params = $this->createParams('reset', TRUE, NULL);

    $output = $this->node->process($params->reveal());

    $this->assertEquals('reset', $output['action_taken']);
    $this->assertFalse($output['loop_active']);
    $this->assertArrayHasKey('error', $output);
  }

  /**
   * @covers ::process
   */
  public function testCheckAction(): void {
    $iterator = IteratorState::fromItems(['a', 'b', 'c']);
    $params = $this->createParams('check', TRUE, $iterator->toArray());

    $output = $this->node->process($params->reveal());

    $this->assertEquals('check', $output['action_taken']);
    $this->assertTrue($output['loop_active']);
    $this->assertTrue($output['has_more_items']);
    $this->assertEquals(0, $output['current_index']);
    $this->assertEquals(3, $output['total_count']);
  }

  /**
   * @covers ::process
   */
  public function testConditionEvaluationNullIsFalse(): void {
    $params = $this->createParams('break', NULL);

    $output = $this->node->process($params->reveal());

    $this->assertEquals('none', $output['action_taken']);
    $this->assertFalse($output['condition_met']);
  }

  /**
   * @covers ::process
   */
  public function testConditionEvaluationEmptyStringIsFalse(): void {
    $params = $this->createParams('break', '');

    $output = $this->node->process($params->reveal());

    $this->assertFalse($output['condition_met']);
  }

  /**
   * @covers ::process
   */
  public function testConditionEvaluationTruthyValues(): void {
    $params = $this->createParams('check', 1, NULL);

    $output = $this->node->process($params->reveal());

    $this->assertTrue($output['condition_met']);
    $this->assertEquals('check', $output['action_taken']);
  }

  /**
   * Creates a mock ParameterBagInterface with the given parameters.
   *
   * @return \Prophecy\Prophecy\ObjectProphecy<\Drupal\flowdrop\DTO\ParameterBagInterface>
   *   The mock parameter bag.
   */
  private function createParams(string $action, mixed $condition, ?array $iteratorData = NULL) {
    $params = $this->prophesize(ParameterBagInterface::class);
    $params->get('action', 'check')->willReturn($action);
    $params->get('condition', TRUE)->willReturn($condition);
    $params->get('__iterator__')->willReturn($iteratorData);
    return $params;
  }

}
