<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop\Unit\DTO;

use Drupal\Tests\UnitTestCase;
use Drupal\flowdrop\DTO\Data;
use Drupal\flowdrop\DTO\DataInterface;

/**
 * Tests the Data DTO.
 *
 * @coversDefaultClass \Drupal\flowdrop\DTO\Data
 * @group flowdrop
 */
class DataTest extends UnitTestCase {

  /**
   * @covers ::__construct
   * @covers ::toArray
   */
  public function testConstructorWithData(): void {
    $data = new Data(["key" => "value", "nested" => ["a" => 1]]);
    $this->assertEquals(["key" => "value", "nested" => ["a" => 1]], $data->toArray());
  }

  /**
   * @covers ::__construct
   * @covers ::isEmpty
   */
  public function testConstructorEmpty(): void {
    $data = new Data();
    $this->assertTrue($data->isEmpty());
    $this->assertEquals([], $data->toArray());
  }

  /**
   * @covers ::get
   */
  public function testGet(): void {
    $data = new Data(["key" => "value"]);
    $this->assertEquals("value", $data->get("key"));
    $this->assertNull($data->get("missing"));
    $this->assertEquals("default", $data->get("missing", "default"));
  }

  /**
   * @covers ::set
   */
  public function testSet(): void {
    $data = new Data();
    $result = $data->set("key", "value");
    $this->assertSame($data, $result);
    $this->assertEquals("value", $data->get("key"));
  }

  /**
   * @covers ::has
   */
  public function testHas(): void {
    $data = new Data(["exists" => "value", "null_value" => NULL]);
    $this->assertTrue($data->has("exists"));
    $this->assertTrue($data->has("null_value"));
    $this->assertFalse($data->has("missing"));
  }

  /**
   * @covers ::remove
   */
  public function testRemove(): void {
    $data = new Data(["key" => "value"]);
    $result = $data->remove("key");
    $this->assertSame($data, $result);
    $this->assertFalse($data->has("key"));
  }

  /**
   * @covers ::fromArray
   */
  public function testFromArray(): void {
    $data = new Data(["old" => "data"]);
    $result = $data->fromArray(["new" => "data"]);
    $this->assertSame($data, $result);
    $this->assertEquals(["new" => "data"], $data->toArray());
  }

  /**
   * @covers ::keys
   */
  public function testKeys(): void {
    $data = new Data(["a" => 1, "b" => 2, "c" => 3]);
    $this->assertEquals(["a", "b", "c"], $data->keys());
  }

  /**
   * @covers ::clear
   */
  public function testClear(): void {
    $data = new Data(["a" => 1, "b" => 2]);
    $result = $data->clear();
    $this->assertSame($data, $result);
    $this->assertTrue($data->isEmpty());
  }

  /**
   * @covers ::merge
   */
  public function testMerge(): void {
    $data1 = new Data(["a" => 1, "shared" => "first"]);
    $data2 = new Data(["b" => 2, "shared" => "second"]);
    $result = $data1->merge($data2);
    $this->assertSame($data1, $result);
    $this->assertEquals(1, $data1->get("a"));
    $this->assertEquals(2, $data1->get("b"));
    $this->assertEquals("second", $data1->get("shared"));
  }

  /**
   * Tests that Data implements DataInterface.
   */
  public function testImplementsInterface(): void {
    $data = new Data();
    $this->assertInstanceOf(DataInterface::class, $data);
  }

  /**
   * @covers ::__get
   * @covers ::__set
   * @covers ::__isset
   * @covers ::__unset
   */
  public function testMagicMethods(): void {
    $data = new Data();
    /** @phpstan-ignore property.notFound (testing __set/__get magic methods) */
    $data->myKey = "myValue";
    $this->assertTrue(isset($data->myKey));
    $this->assertEquals("myValue", $data->myKey);
    unset($data->myKey);
    $this->assertFalse(isset($data->myKey));
  }

}
