Lines 90.90% 40 / 44
Methods 80.95% 17 / 21
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 withName 0.00% 0 / 1 0.00% 0 / 1 2
 withoutName 100.00% 1 / 1 100.00% 1 / 1 1
 withIcon 100.00% 1 / 1 100.00% 1 / 1 1
 withoutIcon 0.00% 0 / 1 0.00% 0 / 1 2
 withArea 0.00% 0 / 1 0.00% 0 / 1 2
 withoutArea 100.00% 1 / 1 100.00% 1 / 1 1
 withLabels 100.00% 1 / 1 100.00% 1 / 1 1
 withAddedLabels 100.00% 1 / 1 100.00% 1 / 1 1
 withRemovedLabels 100.00% 1 / 1 100.00% 1 / 1 1
 withAliases 0.00% 0 / 1 0.00% 0 / 1 2
 withAddedAliases 100.00% 1 / 1 100.00% 1 / 1 1
 withRemovedAliases 100.00% 1 / 1 100.00% 1 / 1 1
 withHidden 100.00% 1 / 1 100.00% 1 / 1 1
 withDisabled 100.00% 1 / 1 100.00% 1 / 1 1
 isEmpty 100.00% 7 / 7 100.00% 1 / 1 7
 needsCurrentEntry 100.00% 1 / 1 100.00% 1 / 1 2
 resolveAgainst 100.00% 4 / 4 100.00% 1 / 1 1
 applyTo 100.00% 12 / 12 100.00% 1 / 1 8
 collectLabelIds 100.00% 1 / 1 100.00% 1 / 1 1
 collectAliases 100.00% 4 / 4 100.00% 1 / 1 2
16final readonly class EntityRegistryUpdate
17{
18    public const string CHANGED_BY_USER = 'user';
19
20    public function __construct(
21        public ?EntityNameChange $name = null,
22        public ?EntityIconChange $icon = null,
23        public ?EntityAreaChange $area = null,
24        public ?EntityLabelsChange $labels = null,
25        public ?EntityAliasesChange $aliases = null,
26        public ?bool $hidden = null,
27        public ?bool $disabled = null,
28    ) {}
29
30    public function withName(string $name): self
31    {
32        return clone($this, ['name' => new EntityNameChange($name)]);
33    }
34
35    public function withoutName(): self
36    {
37        return clone($this, ['name' => new EntityNameChange(null)]);
38    }
39
40    public function withIcon(string $icon): self
41    {
42        return clone($this, ['icon' => new EntityIconChange($icon)]);
43    }
44
45    public function withoutIcon(): self
46    {
47        return clone($this, ['icon' => new EntityIconChange(null)]);
48    }
49
50    /** @throws IdentifierException */
51    public function withArea(AreaId|string $areaId): self
52    {
53        return clone($this, ['area' => new EntityAreaChange(AreaId::fromStringOrId($areaId))]);
54    }
55
56    public function withoutArea(): self
57    {
58        return clone($this, ['area' => new EntityAreaChange(null)]);
59    }
60
61    /** @throws IdentifierException */
62    public function withLabels(LabelId|string ...$labelIds): self
63    {
64        return clone($this, ['labels' => EntityLabelsChange::replacingWith($this->collectLabelIds($labelIds))]);
65    }
66
67    /** @throws IdentifierException */
68    public function withAddedLabels(LabelId|string ...$labelIds): self
69    {
70        return clone($this, ['labels' => ($this->labels ?? new EntityLabelsChange())->withAdded($this->collectLabelIds($labelIds))]);
71    }
72
73    /** @throws IdentifierException */
74    public function withRemovedLabels(LabelId|string ...$labelIds): self
75    {
76        return clone($this, ['labels' => ($this->labels ?? new EntityLabelsChange())->withRemoved($this->collectLabelIds($labelIds))]);
77    }
78
79    public function withAliases(EntityAlias|string ...$aliases): self
80    {
81        return clone($this, ['aliases' => EntityAliasesChange::replacingWith($this->collectAliases($aliases))]);
82    }
83
84    public function withAddedAliases(EntityAlias|string ...$aliases): self
85    {
86        return clone($this, ['aliases' => ($this->aliases ?? new EntityAliasesChange())->withAdded($this->collectAliases($aliases))]);
87    }
88
89    public function withRemovedAliases(EntityAlias|string ...$aliases): self
90    {
91        return clone($this, ['aliases' => ($this->aliases ?? new EntityAliasesChange())->withRemoved($this->collectAliases($aliases))]);
92    }
93
94    public function withHidden(bool $hidden): self
95    {
96        return clone($this, ['hidden' => $hidden]);
97    }
98
99    public function withDisabled(bool $disabled): self
100    {
101        return clone($this, ['disabled' => $disabled]);
102    }
103
104    public function isEmpty(): bool
105    {
106        return $this->name === null
107            && $this->icon === null
108            && $this->area === null
109            && $this->labels === null
110            && $this->aliases === null
111            && $this->hidden === null
112            && $this->disabled === null;
113    }
114
115    public function needsCurrentEntry(): bool
116    {
117        return $this->labels?->needsCurrentLabels() === true || $this->aliases?->needsCurrentAliases() === true;
118    }
119
120    public function resolveAgainst(RegisteredEntity $current): self
121    {
122        return clone($this, [
123            'labels' => $this->labels?->resolveAgainst($current->listLabelIds()),
124            'aliases' => $this->aliases?->resolveAgainst($current->listAliases() ?? EntityAliasCollection::empty()),
125        ]);
126    }
127
128    public function applyTo(RegisteredEntity $current): RegisteredEntity
129    {
130        return new RegisteredEntity(
131            entityId: $current->entityId,
132            deviceId: $current->deviceId,
133            areaId: $this->area === null ? $current->areaId : $this->area->areaId,
134            labelIds: $this->labels?->applyTo($current->listLabelIds())->listValues() ?? $current->labelIds,
135            name: $this->name === null ? $current->name : $this->name->name,
136            entityCategory: $current->entityCategory,
137            hiddenBy: $this->hidden === null ? $current->hiddenBy : ($this->hidden ? self::CHANGED_BY_USER : null),
138            disabledBy: $this->disabled === null ? $current->disabledBy : ($this->disabled ? self::CHANGED_BY_USER : null),
139            icon: $this->icon === null ? $current->icon : $this->icon->icon,
140            aliases: $this->aliases?->applyTo($current->listAliases() ?? EntityAliasCollection::empty())->listValues() ?? $current->aliases,
141        );
142    }
143
144    /**
145     * @param array<LabelId|string> $labelIds
146     * @throws IdentifierException
147     */
148    private function collectLabelIds(array $labelIds): LabelIdCollection
149    {
150        return LabelIdCollection::fromList(array_map(LabelId::fromStringOrId(...), array_values($labelIds)));
151    }
152
153    /** @param array<EntityAlias|string> $aliases */
154    private function collectAliases(array $aliases): EntityAliasCollection
155    {
156        return EntityAliasCollection::fromList(array_map(
157            static fn(EntityAlias|string $alias): EntityAlias => $alias instanceof EntityAlias ? $alias : EntityAlias::named($alias),
158            array_values($aliases),
159        ));
160    }
161}