<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_stategraph\Unit\Reducer;

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

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

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

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

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

  /**
   * Tests getDefaultValue returns null.
   *
   * @covers ::getDefaultValue
   */
  public function testGetDefaultValue(): void {
    $this->assertNull($this->reducer->getDefaultValue());
  }

  /**
   * Tests replacing a string with a string.
   *
   * @covers ::reduce
   */
  public function testReplaceStringWithString(): void {
    $result = $this->reducer->reduce("old_value", "new_value");

    $this->assertEquals("new_value", $result);
  }

  /**
   * Tests replacing an array with an array.
   *
   * @covers ::reduce
   */
  public function testReplaceArrayWithArray(): void {
    $current = ["old" => "data", "key" => "value"];
    $update = ["new" => "data"];

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

    // Entire array is replaced, not merged.
    $this->assertEquals(["new" => "data"], $result);
    $this->assertArrayNotHasKey("old", $result);
    $this->assertArrayNotHasKey("key", $result);
  }

  /**
   * Tests replacing with null.
   *
   * @covers ::reduce
   */
  public function testReplaceWithNull(): void {
    $result = $this->reducer->reduce("current", NULL);

    $this->assertNull($result);
  }

  /**
   * Tests replacing null with value.
   *
   * @covers ::reduce
   */
  public function testReplaceNullWithValue(): void {
    $result = $this->reducer->reduce(NULL, "new_value");

    $this->assertEquals("new_value", $result);
  }

  /**
   * Tests replacing integer with integer.
   *
   * @covers ::reduce
   */
  public function testReplaceIntegerWithInteger(): void {
    $result = $this->reducer->reduce(5, 10);

    $this->assertEquals(10, $result);
  }

  /**
   * Tests replacing boolean with boolean.
   *
   * @covers ::reduce
   */
  public function testReplaceBooleanWithBoolean(): void {
    $result = $this->reducer->reduce(FALSE, TRUE);

    $this->assertTrue($result);
  }

  /**
   * Tests replacing string with integer (type change).
   *
   * @covers ::reduce
   */
  public function testReplaceWithDifferentType(): void {
    $result = $this->reducer->reduce("string", 42);

    $this->assertEquals(42, $result);
    $this->assertIsInt($result);
  }

  /**
   * Tests replacing array with string (type change).
   *
   * @covers ::reduce
   */
  public function testReplaceArrayWithString(): void {
    $result = $this->reducer->reduce(["a", "b"], "replaced");

    $this->assertEquals("replaced", $result);
    $this->assertIsString($result);
  }

  /**
   * Tests replacing with empty string.
   *
   * @covers ::reduce
   */
  public function testReplaceWithEmptyString(): void {
    $result = $this->reducer->reduce("content", "");

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

  /**
   * Tests replacing with empty array.
   *
   * @covers ::reduce
   */
  public function testReplaceWithEmptyArray(): void {
    $result = $this->reducer->reduce(["a", "b", "c"], []);

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

  /**
   * Tests replacing with zero.
   *
   * @covers ::reduce
   */
  public function testReplaceWithZero(): void {
    $result = $this->reducer->reduce(100, 0);

    $this->assertEquals(0, $result);
  }

  /**
   * Tests replacing with false.
   *
   * @covers ::reduce
   */
  public function testReplaceWithFalse(): void {
    $result = $this->reducer->reduce(TRUE, FALSE);

    $this->assertFalse($result);
  }

  /**
   * Tests multiple sequential replacements.
   *
   * @covers ::reduce
   */
  public function testMultipleSequentialReplacements(): void {
    $state = "initial";

    $state = $this->reducer->reduce($state, "first");
    $this->assertEquals("first", $state);

    $state = $this->reducer->reduce($state, "second");
    $this->assertEquals("second", $state);

    $state = $this->reducer->reduce($state, "third");
    $this->assertEquals("third", $state);
  }

  /**
   * Tests replacing complex nested array.
   *
   * @covers ::reduce
   */
  public function testReplaceComplexNestedArray(): void {
    $current = [
      "level1" => [
        "level2" => [
          "deep" => "value",
        ],
      ],
    ];

    $update = ["simple" => "replacement"];

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

    // Entire structure replaced.
    $this->assertEquals(["simple" => "replacement"], $result);
    $this->assertArrayNotHasKey("level1", $result);
  }

  /**
   * Tests replacing object-like arrays.
   *
   * @covers ::reduce
   */
  public function testReplaceObjectLikeArray(): void {
    $current = [
      "id" => 1,
      "name" => "Original",
      "status" => "active",
    ];

    $update = [
      "id" => 2,
      "name" => "Updated",
    ];

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

    // Complete replacement, no merge.
    $this->assertEquals([
      "id" => 2,
      "name" => "Updated",
    ], $result);
    $this->assertArrayNotHasKey("status", $result);
  }

}
