<?php
namespace nur\data\types;

use nur\b\coll\FancyArray;
use nur\b\coll\TGenericArray;
use nur\b\ValueException;
use nur\data\types\impl\CAbType;
use nur\php\Autogen;
use nur\t\TestCase;

class MetadataTest extends TestCase {
  const ENSURE_STRUCTURE_DATA = [
    ## type, default, absent, input, present
    # null
    [null, null, null, null, null],
    [null, null, null, false, null],
    [null, null, null, "value", "value"],
    [null, false, false, null, null],
    [null, false, false, false, false],
    [null, false, false, "value", "value"],
    [null, "default", "default", null, null],
    [null, "default", "default", false, "default"],
    [null, "default", "default", "value", "value"],
    # mixed
    ["mixed", null, null, null, null],
    ["mixed", null, null, false, false],
    ["mixed", null, null, "value", "value"],
    ["mixed", false, false, null, null],
    ["mixed", false, false, false, false],
    ["mixed", false, false, "value", "value"],
    ["mixed", "default", "default", null, null],
    ["mixed", "default", "default", false, false],
    ["mixed", "default", "default", "value", "value"],
    # bool
    ["?bool", null, null, null, null],
    ["?bool", null, null, false, false],
    ["?bool", null, null, "value", true],
    ["?bool", false, false, null, null],
    ["?bool", false, false, false, false],
    ["?bool", false, false, "value", true],
    ["?bool", true, true, null, null],
    ["?bool", true, true, false, false],
    ["?bool", true, true, "value", true],
    ["?bool", "default", true, null, null],
    ["?bool", "default", true, false, false],
    ["?bool", "default", true, "value", true],
    ["bool", null, false, null, false],
    ["bool", null, false, false, false],
    ["bool", null, false, "value", true],
    ["bool", false, false, null, false],
    ["bool", false, false, false, false],
    ["bool", false, false, "value", true],
    ["bool", true, true, null, true],
    ["bool", true, true, false, false],
    ["bool", true, true, "value", true],
    ["bool", "default", true, null, true],
    ["bool", "default", true, false, false],
    ["bool", "default", true, "value", true],
    # int
    ["?int", null, null, null, null],
    ["?int", null, null, false, null],
    ["?int", null, null, "value", 0],
    ["?int", null, null, 123, 123],
    ["?int", false, 0, null, null],
    ["?int", false, 0, false, 0],
    ["?int", false, 0, "value", 0],
    ["?int", false, 0, 123, 123],
    ["?int", 42, 42, null, null],
    ["?int", 42, 42, false, 42],
    ["?int", 42, 42, "value", 0],
    ["?int", 42, 42, 123, 123],
    ["?int", "default", 0, null, null],
    ["?int", "default", 0, false, 0],
    ["?int", "default", 0, "value", 0],
    ["?int", "default", 0, 123, 123],
    ["int", null, 0, null, 0],
    ["int", null, 0, false, 0],
    ["int", null, 0, "value", 0],
    ["int", null, 0, 123, 123],
    ["int", false, 0, null, 0],
    ["int", false, 0, false, 0],
    ["int", false, 0, "value", 0],
    ["int", false, 0, 123, 123],
    ["int", 42, 42, null, 42],
    ["int", 42, 42, false, 42],
    ["int", 42, 42, "value", 0],
    ["int", 42, 42, 123, 123],
    ["int", "default", 0, null, 0],
    ["int", "default", 0, false, 0],
    ["int", "default", 0, "value", 0],
    ["int", "default", 0, 123, 123],
    # float
    ["?float", null, null, null, null],
    ["?float", null, null, false, null],
    ["?float", null, null, "value", 0.0],
    ["?float", null, null, 123, 123.0],
    ["?float", false, 0.0, null, null],
    ["?float", false, 0.0, false, 0.0],
    ["?float", false, 0.0, "value", 0.0],
    ["?float", false, 0.0, 123, 123.0],
    ["?float", 42, 42.0, null, null],
    ["?float", 42, 42.0, false, 42.0],
    ["?float", 42, 42.0, "value", 0.0],
    ["?float", 42, 42.0, 123, 123.0],
    ["?float", "default", 0.0, null, null],
    ["?float", "default", 0.0, false, 0.0],
    ["?float", "default", 0.0, "value", 0.0],
    ["?float", "default", 0.0, 123, 123.0],
    ["float", null, 0.0, null, 0.0],
    ["float", null, 0.0, false, 0.0],
    ["float", null, 0.0, "value", 0.0],
    ["float", null, 0.0, 123, 123.0],
    ["float", false, 0.0, null, 0.0],
    ["float", false, 0.0, false, 0.0],
    ["float", false, 0.0, "value", 0.0],
    ["float", false, 0.0, 123, 123.0],
    ["float", 42, 42.0, null, 42.0],
    ["float", 42, 42.0, false, 42.0],
    ["float", 42, 42.0, "value", 0.0],
    ["float", 42, 42.0, 123, 123.0],
    ["float", "default", 0.0, null, 0.0],
    ["float", "default", 0.0, false, 0.0],
    ["float", "default", 0.0, "value", 0.0],
    ["float", "default", 0.0, 123, 123.0],
    # string
    ["?string", null, null, null, null],
    ["?string", null, null, false, null],
    ["?string", null, null, "value", "value"],
    ["?string", null, null, 123, "123"],
    ["?string", false, "", null, null],
    ["?string", false, "", false, ""],
    ["?string", false, "", "value", "value"],
    ["?string", false, "", 123, "123"],
    ["?string", 42, "42", null, null],
    ["?string", 42, "42", false, "42"],
    ["?string", 42, "42", "value", "value"],
    ["?string", 42, "42", 123, "123"],
    ["?string", "default", "default", null, null],
    ["?string", "default", "default", false, "default"],
    ["?string", "default", "default", "value", "value"],
    ["?string", "default", "default", 123, "123"],
    ["string", null, "", null, ""],
    ["string", null, "", false, ""],
    ["string", null, "", "value", "value"],
    ["string", null, "", 123, "123"],
    ["string", false, "", null, ""],
    ["string", false, "", false, ""],
    ["string", false, "", "value", "value"],
    ["string", false, "", 123, "123"],
    ["string", 42, "42", null, "42"],
    ["string", 42, "42", false, "42"],
    ["string", 42, "42", "value", "value"],
    ["string", 42, "42", 123, "123"],
    ["string", "default", "default", null, "default"],
    ["string", "default", "default", false, "default"],
    ["string", "default", "default", "value", "value"],
    ["string", "default", "default", 123, "123"],
    # key
    ["?key", null, null, null, null],
    ["?key", null, null, false, null],
    ["?key", null, null, "value", "value"],
    ["?key", null, null, 123, 123],
    ["?key", false, "", null, null],
    ["?key", false, "", false, ""],
    ["?key", false, "", "value", "value"],
    ["?key", false, "", 123, 123],
    ["?key", 42, 42, null, null],
    ["?key", 42, 42, false, 42],
    ["?key", 42, 42, "value", "value"],
    ["?key", 42, 42, 123, 123],
    ["?key", "42", 42, null, null],
    ["?key", "42", 42, false, 42],
    ["?key", "42", 42, "value", "value"],
    ["?key", "42", 42, 123, 123],
    ["?key", "default", "default", null, null],
    ["?key", "default", "default", false, "default"],
    ["?key", "default", "default", "value", "value"],
    ["?key", "default", "default", 123, 123],
    ["key", null, "", null, ""],
    ["key", null, "", false, ""],
    ["key", null, "", "value", "value"],
    ["key", null, "", 123, 123],
    ["key", false, "", null, ""],
    ["key", false, "", false, ""],
    ["key", false, "", "value", "value"],
    ["key", false, "", 123, 123],
    ["key", 42, 42, null, 42],
    ["key", 42, 42, false, 42],
    ["key", 42, 42, "value", "value"],
    ["key", 42, 42, 123, 123],
    ["key", "42", 42, null, 42],
    ["key", "42", 42, false, 42],
    ["key", "42", 42, "value", "value"],
    ["key", "42", 42, 123, 123],
    ["key", "default", "default", null, "default"],
    ["key", "default", "default", false, "default"],
    ["key", "default", "default", "value", "value"],
    ["key", "default", "default", 123, 123],
    # array
    ["?array", null, null, null, null],
    ["?array", null, null, false, null],
    ["?array", null, null, "value", ["value"]],
    ["?array", null, null, 123, [123]],
    ["?array", false, [], null, null],
    ["?array", false, [], false, []],
    ["?array", false, [], "value", ["value"]],
    ["?array", false, [], 123, [123]],
    ["?array", 42, [42], null, null],
    ["?array", 42, [42], false, [42]],
    ["?array", 42, [42], "value", ["value"]],
    ["?array", 42, [42], 123, [123]],
    ["?array", "default", ["default"], null, null],
    ["?array", "default", ["default"], false, ["default"]],
    ["?array", "default", ["default"], "value", ["value"]],
    ["?array", "default", ["default"], 123, [123]],
    ["array", null, [], null, []],
    ["array", null, [], false, []],
    ["array", null, [], "value", ["value"]],
    ["array", null, [], 123, [123]],
    ["array", false, [], null, []],
    ["array", false, [], false, []],
    ["array", false, [], "value", ["value"]],
    ["array", false, [], 123, [123]],
    ["array", 42, [42], null, [42]],
    ["array", 42, [42], false, [42]],
    ["array", 42, [42], "value", ["value"]],
    ["array", 42, [42], 123, [123]],
    ["array", "default", ["default"], null, ["default"]],
    ["array", "default", ["default"], false, ["default"]],
    ["array", "default", ["default"], "value", ["value"]],
    ["array", "default", ["default"], 123, [123]],
    # array[]
    ["?array[]", null, null, null, null],
    ["?array[]", null, null, false, null],
    ["?array[]", null, null, "value", [["value"]]],
    ["?array[]", null, null, 123, [[123]]],
    ["?array[]", false, [], null, null],
    ["?array[]", false, [], false, []],
    ["?array[]", false, [], "value", [["value"]]],
    ["?array[]", false, [], 123, [[123]]],
    ["?array[]", 42, [[42]], null, null],
    ["?array[]", 42, [[42]], false, [[42]]],
    ["?array[]", 42, [[42]], "value", [["value"]]],
    ["?array[]", 42, [[42]], 123, [[123]]],
    ["?array[]", "default", [["default"]], null, null],
    ["?array[]", "default", [["default"]], false, [["default"]]],
    ["?array[]", "default", [["default"]], "value", [["value"]]],
    ["?array[]", "default", [["default"]], 123, [[123]]],
    ["array[]", null, [], null, []],
    ["array[]", null, [], false, []],
    ["array[]", null, [], "value", [["value"]]],
    ["array[]", null, [], 123, [[123]]],
    ["array[]", false, [], null, []],
    ["array[]", false, [], false, []],
    ["array[]", false, [], "value", [["value"]]],
    ["array[]", false, [], 123, [[123]]],
    ["array[]", 42, [[42]], null, [[42]]],
    ["array[]", 42, [[42]], false, [[42]]],
    ["array[]", 42, [[42]], "value", [["value"]]],
    ["array[]", 42, [[42]], 123, [[123]]],
    ["array[]", "default", [["default"]], null, [["default"]]],
    ["array[]", "default", [["default"]], false, [["default"]]],
    ["array[]", "default", [["default"]], "value", [["value"]]],
    ["array[]", "default", [["default"]], 123, [[123]]],
    # les types simples de la forme [type] ainsi que les types non simples sont
    # laissés en l'état
    [["string"], null, null, null, null],
    [["string"], null, null, false, false],
    [["string"], null, null, "value", "value"],
    [["string"], null, null, 123, 123],
    [["string"], false, false, null, null],
    [["string"], false, false, false, false],
    [["string"], false, false, "value", "value"],
    [["string"], false, false, 123, 123],
    [["string"], 42, 42, null, null],
    [["string"], 42, 42, false, false],
    [["string"], 42, 42, "value", "value"],
    [["string"], 42, 42, 123, 123],
    [["string"], "default", "default", null, null],
    [["string"], "default", "default", false, false],
    [["string"], "default", "default", "value", "value"],
    [["string"], "default", "default", 123, 123],
    ["unknown_type", null, null, null, null],
    ["unknown_type", null, null, false, false],
    ["unknown_type", null, null, "value", "value"],
    ["unknown_type", null, null, 123, 123],
    ["unknown_type", false, false, null, null],
    ["unknown_type", false, false, false, false],
    ["unknown_type", false, false, "value", "value"],
    ["unknown_type", false, false, 123, 123],
    ["unknown_type", 42, 42, null, null],
    ["unknown_type", 42, 42, false, false],
    ["unknown_type", 42, 42, "value", "value"],
    ["unknown_type", 42, 42, 123, 123],
    ["unknown_type", "default", "default", null, null],
    ["unknown_type", "default", "default", false, false],
    ["unknown_type", "default", "default", "value", "value"],
    ["unknown_type", "default", "default", 123, 123],
  ];

  function testEnsureStructure() {
    foreach (self::ENSURE_STRUCTURE_DATA as $value) {
      [$type, $default, $absent, $input, $present] = $value;
      $schema = [
        "present" => [$type, $default],
        "absent" => [$type, $default],
      ];
      $md = new Metadata($schema);
      $data = [$input];
      $md->ensureSchema($data);
      self::assertSame(["present" => $present, "absent" => $absent], $data, var_export($value, true));
    }
  }

  const ITEM_SCHEMA = [
    "id" => null,
    "name" => null,
  ];
  const RECURSIVE_SCHEMA = [
    "item" => ["array", "default", "schema" => self::ITEM_SCHEMA],
    "items" => ["array[]", "default", "schema" => self::ITEM_SCHEMA],
    "nitem" => ["?array", "default", "schema" => self::ITEM_SCHEMA],
    "nitems" => ["?array[]", "default", "schema" => self::ITEM_SCHEMA],
  ];

  function testRecursiveSchema() {
    $md = new Metadata(self::RECURSIVE_SCHEMA);

    $data = null;
    $md->ensureSchema($data);
    self::assertSame([
      "item" => ["id" => "default", "name" => null],
      "items" => [["id" => "default", "name" => null]],
      "nitem" => ["id" => "default", "name" => null],
      "nitems" => [["id" => "default", "name" => null]],
    ], $data);

    $data = [false, false, false, false];
    $md->ensureSchema($data);
    self::assertSame([
      "item" => ["id" => "default", "name" => null],
      "items" => [["id" => "default", "name" => null]],
      "nitem" => ["id" => "default", "name" => null],
      "nitems" => [["id" => "default", "name" => null]],
    ], $data);

    $data = [null, null, null, null];
    $md->ensureSchema($data);
    self::assertSame([
      "item" => ["id" => "default", "name" => null],
      "items" => [["id" => "default", "name" => null]],
      "nitem" => null,
      "nitems" => null,
    ], $data);

    $data = ["id", "id", "id", "id"];
    $md->ensureSchema($data);
    self::assertSame([
      "item" => ["id" => "id", "name" => null],
      "items" => [["id" => "id", "name" => null]],
      "nitem" => ["id" => "id", "name" => null],
      "nitems" => [["id" => "id", "name" => null]],
    ], $data);
  }

  function testEnsureSchemaWithKey() {
    $schema = [
      "name" => null,
      "title" => null,
      "desc" => null,
    ];
    $md = new Metadata($schema);

    $item = null;
    $md->ensureSchema($item, "key");
    self::assertSame(["name" => "key", "title" => null, "desc" => null], $item);

    $item = [];
    $md->ensureSchema($item, "key");
    self::assertSame(["name" => "key", "title" => null, "desc" => null], $item);

    $item = "title";
    $md->ensureSchema($item, "key");
    self::assertSame(["name" => "key", "title" => "title", "desc" => null], $item);

    $item = ["title", "desc"];
    $md->ensureSchema($item, "key");
    self::assertSame(["name" => "key", "title" => "title", "desc" => "desc"], $item);

    $item = ["name" => "name", "title", "desc"];
    $md->ensureSchema($item, "key");
    self::assertSame(["name" => "name", "title" => "title", "desc" => "desc"], $item);
  }

  const ENSURE_KEYS_DATA = [
    [[1],                           ["first" => 1]],
    [[false, 2],                    ["first" => null, "second" => 2]],
    [[1, false],                    ["first" => 1, "second" => null]],
    [[1, false, 3],                 ["first" => 1, "second" => null, 3]],
    [[1, 2],                        ["first" => 1, "second" => 2]],
    [["first" => 1, 2],             ["first" => 1, "second" => 2]],
    [["first" => 1, "second" => 2], ["first" => 1, "second" => 2]],
    [["second" => 2, 1],            ["second" => 2, "first" => 1]],
    [["second" => 2, "first" => 1], ["second" => 2, "first" => 1]],
    [
      [1, 2, 3, "x" => "y"],
      ["x" => "y", "first" => 1, "second" => 2, 3],
    ],
  ];

  function testEnsureKeys() {
    $schema = [
      "first" => null,
      "second" => null,
    ];
    $md = new Metadata($schema, ["ensure_keys" => false, "order_keys" => false]);

    foreach (self::ENSURE_KEYS_DATA as $value) {
      [$input, $result] = $value;
      $md->ensureSchema($input);
      self::assertSame($result, $input, var_export(["input" => $input, "expected" => $result], true));
    }
  }

  const ORDER_KEYS_DATA = [
    [
      [1, 2],
      ["first" => 1, "second" => 2],
      ["first" => 1, "second" => 2],
    ],
    [
      ["first" => 1, 2],
      ["first" => 1, "second" => 2],
      ["first" => 1, "second" => 2],
    ],
    [
      ["first" => 1, "second" => 2],
      ["first" => 1, "second" => 2],
      ["first" => 1, "second" => 2],
    ],
    [
      ["second" => 2, 1],
      ["second" => 2, "first" => 1],
      ["first" => 1, "second" => 2],
    ],
    [
      ["second" => 2, "first" => 1],
      ["second" => 2, "first" => 1],
      ["first" => 1, "second" => 2],
    ],
    [
      [1, 2, 3, "x" => "y"],
      ["x" => "y", "first" => 1, "second" => 2, 3],
      ["first" => 1, "second" => 2, "x" => "y", 3],
    ],
  ];

  function testOrderKeys() {
    $schema = [
      "first" => null,
      "second" => null,
    ];
    $umd = new Metadata($schema, ["order_keys" => false]); # non ordonné
    $omd = new Metadata($schema, ["order_keys" => true]); # ordonné

    foreach (self::ORDER_KEYS_DATA as $value) {
      [$input, $uresult, $oresult] = $value;
      $uinput = $input;
      $umd->ensureSchema($uinput);
      self::assertSame($uresult, $uinput, var_export(["input" => $input, "expected" => $uresult], true));
      $oinput = $input;
      $omd->ensureSchema($oinput);
      self::assertSame($oresult, $oinput, var_export(["input" => $input, "expected" => $oresult], true));
    }
  }

  function testCompositeKeys() {
    $schema = [
      "a" => ["string"],
      "b" => ["int"],
      "ca" => ["string", "composite" => true],
      "cb" => ["int", "composite" => true],
      "" => [
        "ctypes" => [
          "ab" => CAbType::class,
        ],
      ],
    ];
    $md = new Metadata($schema);

    self::assertSame(["a", "b"], $md->getSikeys());
    self::assertSame(["ca", "cb"], $md->getCokeys());
    self::assertSame(["a", "b", "ca", "cb"], $md->getKeys());
    self::assertSame(["ab"], $md->getCikeys());
    self::assertSame(["a", "b", "ca", "cb", "ab"], $md->getAllKeys());

    self::assertSame([
      "getA" => "a", "getB" => "b", "getCa" => "ca", "getCb" => "cb",
    ], Autogen::auto_getters($md));
    self::assertSame([
      "getAb" => "ab",
    ], Autogen::auto_ci_getters($md));
    self::assertSame([
      "setA" => "a", "setB" => "b", "setCa" => "ca", "setCb" => "cb",
    ], Autogen::auto_setters($md));
    self::assertSame([
      "delA" => "a", "delB" => "b", "delCa" => "ca", "delCb" => "cb",
    ], Autogen::auto_deleters($md));
    self::assertSame([
      "getA(): string",
      "getB(): int",
      "getCa(): string",
      "getCb(): int",
      "getAb(): ?array", #XXX à remplacer par : array
    ], Autogen::auto_getters_methods($md));

    $fa = new class($schema) extends FancyArray {
      use TGenericArray;

      function __construct($schema, $data=null, $key=null) {
        if (self::$md === null) self::$md = new Metadata($schema);
        parent::__construct($data, $key);
      }

      /** @var Metadata */
      private static $md;
      protected static function _AUTO_GETTERS(): ?array {
        return Autogen::auto_getters(self::$md);
      }
      protected static function _AUTO_SETTERS(): ?array {
        return Autogen::auto_setters(self::$md);
      }
      protected static function _AUTO_DELETERS(): ?array {
        return Autogen::auto_deleters(self::$md);
      }
      protected static function _AUTO_CI_GETTERS(): ?array {
        return Autogen::auto_ci_getters(self::$md);
      }
      protected static function _AUTO_CI_SETTERS(): ?array {
        return Autogen::auto_ci_setters(self::$md);
      }

      function md(): Metadata {
        return self::$md;
      }
      function ensureData($data, $key=null): array {
        self::$md->ensureSchema($data, $key);
        return $data;
      }
    };
    $fa->setA("seta");
    $fa->setB(10);
    $fa->setCa("setca");
    // auto-conversion si on passe par la méthode
    $fa->setCb("15");
    self::assertSame(15, $fa->getCb());
    self::assertSame([
      "ca" => "setca", "cb" => 15,
    ], $fa->getAb());
    // pas de conversion si on met à jour directement le tableau
    $fa["cb"] = "15";
    self::assertSame("15", $fa->getCb());
    self::assertSame([
      "ca" => "setca", "cb" => "15",
    ], $fa->getAb());
    // set
    $fa->setAb(null);

    $fa->setCa("setca");
    $fa->setCb("15");
    $fa->setAb(["ca" => "newca"]);
    self::assertSame([
      "ca" => "newca", "cb" => 15,
    ], $fa->getAb());
    $fa->setAb(["cb" => 20]);
    self::assertSame([
      "ca" => "newca", "cb" => 20,
    ], $fa->getAb());
    $fa->setAb(["ca" => "newca2", "cb" => 30]);
    self::assertSame([
      "ca" => "newca2", "cb" => 30,
    ], $fa->getAb());
  }

  function testNestedSchemasRequired() {
    $schema = [[
      "a" => ["?string", "required" => true],
    ]];
    $md = new Metadata($schema);

    $item = [[]];
    self::assertException(ValueException::class, function () use ($md, $item) {
      $md->ensureSchema($item);
    });

    $item = ["av"];
    $md->ensureSchema($item);
    self::assertSame([
      ["a" => "av"],
    ], $item);

    $schema = [
      "as" => ["array[]", "schema" => [
        "a" => ["?string", "required" => true],
      ]],
    ];
    $md = new Metadata($schema);

    $item = ["as" => [[]]];
    self::assertException(ValueException::class, function () use ($md, $item) {
      $md->ensureSchema($item);
    });

    $item = ["av"];
    $md->ensureSchema($item);
    self::assertSame([
      "as" => [["a" => "av"]],
    ], $item);
  }

  function testDataWithKeys() {
    $schema = [
      "key" => "?key",
      "value" => "?int",
    ];
    $md = new Metadata($schema);

    $items = []; $md->eachEnsureSchema($items);
    self::assertSame([], $items);

    $items = ["a", "b"]; $md->eachEnsureSchema($items);
    self::assertSame([
      ["key" => "a", "value" => null],
      ["key" => "b", "value" => null],
    ], $items);

    $items = [["a"], ["b"]]; $md->eachEnsureSchema($items);
    self::assertSame([
      ["key" => "a", "value" => null],
      ["key" => "b", "value" => null],
    ], $items);

    $items = [["a", 1], ["b", 2]]; $md->eachEnsureSchema($items);
    self::assertSame([
      ["key" => "a", "value" => 1],
      ["key" => "b", "value" => 2],
    ], $items);

    $items = ["a" => null, "b" => null]; $md->eachEnsureSchema($items);
    self::assertSame([
      "a" => ["key" => "a", "value" => null],
      "b" => ["key" => "b", "value" => null],
    ], $items);

    $items = ["a" => 1, "b" => 2]; $md->eachEnsureSchema($items);
    self::assertSame([
      "a" => ["key" => "a", "value" => 1],
      "b" => ["key" => "b", "value" => 2],
    ], $items);

    $items = ["a", "b" => 2]; $md->eachEnsureSchema($items);
    self::assertSame([
      ["key" => "a", "value" => null],
      "b" => ["key" => "b", "value" => 2],
    ], $items);

    $items = [null, "a", "b" => 2]; $md->eachEnsureSchema($items);
    self::assertSame([
      ["key" => null, "value" => null],
      ["key" => "a", "value" => null],
      "b" => ["key" => "b", "value" => 2],
    ], $items);
  }
}
