<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_stategraph\Unit\Reducer;

use Drupal\Tests\UnitTestCase;
use Drupal\flowdrop_stategraph\Reducer\AppendReducer;

/**
 * Tests the AppendReducer.
 *
 * @coversDefaultClass \Drupal\flowdrop_stategraph\Reducer\AppendReducer
 * @group flowdrop_stategraph
 */
class AppendReducerTest extends UnitTestCase {

  /**
   * The reducer under test.
   *
   * @var \Drupal\flowdrop_stategraph\Reducer\AppendReducer
   */
  private AppendReducer $reducer;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->reducer = new AppendReducer();
  }

  /**
   * Tests getId returns "append".
   *
   * @covers ::getId
   */
  public function testGetId(): void {
    $this->assertEquals("append", $this->reducer->getId());
  }

  /**
   * Tests getDefaultValue returns empty array.
   *
   * @covers ::getDefaultValue
   */
  public function testGetDefaultValue(): void {
    $this->assertEquals([], $this->reducer->getDefaultValue());
  }

  /**
   * Tests appending a single item to an array.
   *
   * @covers ::reduce
   */
  public function testAppendSingleItem(): void {
    $current = ["existing"];
    $update = "new_item";

    $result = $this->reducer->reduce($current, $update);

    $this->assertEquals(["existing", "new_item"], $result);
  }

  /**
   * Tests appending multiple items (array) to an array.
   *
   * @covers ::reduce
   */
  public function testAppendMultipleItems(): void {
    $current = ["a"];
    $update = ["b", "c", "d"];

    $result = $this->reducer->reduce($current, $update);

    $this->assertEquals(["a", "b", "c", "d"], $result);
  }

  /**
   * Tests appending to an empty array.
   *
   * @covers ::reduce
   */
  public function testAppendToEmptyArray(): void {
    $current = [];
    $update = "first_item";

    $result = $this->reducer->reduce($current, $update);

    $this->assertEquals(["first_item"], $result);
  }

  /**
   * Tests appending array to empty array.
   *
   * @covers ::reduce
   */
  public function testAppendArrayToEmptyArray(): void {
    $current = [];
    $update = ["first", "second"];

    $result = $this->reducer->reduce($current, $update);

    $this->assertEquals(["first", "second"], $result);
  }

  /**
   * Tests appending when current value is not an array.
   *
   * @covers ::reduce
   */
  public function testAppendToNonArray(): void {
    $current = "not_an_array";
    $update = "new_item";

    $result = $this->reducer->reduce($current, $update);

    // Should create a new array with the update item.
    $this->assertEquals(["new_item"], $result);
  }

  /**
   * Tests appending when current value is null.
   *
   * @covers ::reduce
   */
  public function testAppendToNull(): void {
    $current = NULL;
    $update = "item";

    $result = $this->reducer->reduce($current, $update);

    $this->assertEquals(["item"], $result);
  }

  /**
   * Tests appending when current value is an integer.
   *
   * @covers ::reduce
   */
  public function testAppendToInteger(): void {
    $current = 42;
    $update = "item";

    $result = $this->reducer->reduce($current, $update);

    $this->assertEquals(["item"], $result);
  }

  /**
   * Tests appending complex objects.
   *
   * @covers ::reduce
   */
  public function testAppendComplexObjects(): void {
    $current = [
      ["id" => 1, "name" => "first"],
    ];
    $update = [
      ["id" => 2, "name" => "second"],
      ["id" => 3, "name" => "third"],
    ];

    $result = $this->reducer->reduce($current, $update);

    $this->assertCount(3, $result);
    $this->assertEquals(1, $result[0]["id"]);
    $this->assertEquals(2, $result[1]["id"]);
    $this->assertEquals(3, $result[2]["id"]);
  }

  /**
   * Tests appending empty array does nothing.
   *
   * @covers ::reduce
   */
  public function testAppendEmptyArray(): void {
    $current = ["existing"];
    $update = [];

    $result = $this->reducer->reduce($current, $update);

    $this->assertEquals(["existing"], $result);
  }

  /**
   * Tests appending preserves order.
   *
   * @covers ::reduce
   */
  public function testAppendPreservesOrder(): void {
    $current = [1, 2, 3];
    $update = [4, 5, 6];

    $result = $this->reducer->reduce($current, $update);

    $this->assertEquals([1, 2, 3, 4, 5, 6], $result);
  }

  /**
   * Tests appending null item.
   *
   * @covers ::reduce
   */
  public function testAppendNullItem(): void {
    $current = ["a"];
    $update = NULL;

    $result = $this->reducer->reduce($current, $update);

    // NULL is appended as a single item.
    $this->assertEquals(["a", NULL], $result);
  }

  /**
   * Tests multiple sequential appends.
   *
   * @covers ::reduce
   */
  public function testMultipleSequentialAppends(): void {
    $state = [];

    $state = $this->reducer->reduce($state, "first");
    $state = $this->reducer->reduce($state, "second");
    $state = $this->reducer->reduce($state, ["third", "fourth"]);

    $this->assertEquals(["first", "second", "third", "fourth"], $state);
  }

  /**
   * Tests appending mixed types.
   *
   * @covers ::reduce
   */
  public function testAppendMixedTypes(): void {
    $current = ["string"];
    $update = [42, TRUE, ["nested" => "array"]];

    $result = $this->reducer->reduce($current, $update);

    $this->assertCount(4, $result);
    $this->assertEquals("string", $result[0]);
    $this->assertEquals(42, $result[1]);
    $this->assertTrue($result[2]);
    $this->assertEquals(["nested" => "array"], $result[3]);
  }

  /**
   * Tests appending boolean value.
   *
   * @covers ::reduce
   */
  public function testAppendBooleanValue(): void {
    $current = ["a"];
    $update = FALSE;

    $result = $this->reducer->reduce($current, $update);

    $this->assertEquals(["a", FALSE], $result);
  }

}
