Lines 100.00% 33 / 33
Methods 100.00% 5 / 5
Classes 100.00% 1 / 1
Name Lines Methods CRAP
 recordState 100.00% 7 / 7 100.00% 1 / 1 1
 sliceForWindow 100.00% 16 / 16 100.00% 1 / 1 7
 dropRepeatedStates 100.00% 6 / 6 100.00% 1 / 1 4
 listStatesInOrder 100.00% 3 / 3 100.00% 1 / 1 1
 stripAttributes 100.00% 1 / 1 100.00% 1 / 1 1
15final class SeededHistory
16{
17    /** @var array<string, list<EntityState>> */
18    private array $statesByEntityId = [];
19
20    public function recordState(EntityState $state, Instant $changedAt): void
21    {
22        $this->statesByEntityId[$state->entityId->value][] = new EntityState(
23            $state->entityId,
24            $state->state,
25            $state->attributes,
26            $state->lastChangedAt ?? $changedAt,
27            $state->lastUpdatedAt ?? $state->lastChangedAt ?? $changedAt,
28        );
29    }
30
31    public function sliceForWindow(EntityId $entityId, HistoryWindow $window, HistoryDetail $detail): EntityStateHistory
32    {
33        $startState = null;
34        $inWindow = [];
35
36        foreach ($this->listStatesInOrder($entityId) as $state) {
37            $changedAt = $state->lastChangedAt ?? $window->startsAt;
38
39            if (!$changedAt->isAfter($window->startsAt)) {
40                $startState = new EntityState($entityId, $state->state, $state->attributes, $window->startsAt, $window->startsAt);
41            } elseif (!$changedAt->isAfter($window->endsAt)) {
42                $inWindow[] = $state;
43            }
44        }
45
46        $states = $startState === null ? $inWindow : [$startState, ...$inWindow];
47
48        if (!$detail->includesAttributeOnlyChanges()) {
49            $states = self::dropRepeatedStates($states);
50        }
51
52        return new EntityStateHistory(
53            $entityId,
54            $window,
55            HistoricalStateCollection::fromList($detail->includesAttributes() ? $states : array_map(self::stripAttributes(...), $states)),
56        );
57    }
58
59    /**
60     * @param list<EntityState> $states
61     * @return list<EntityState>
62     */
63    private static function dropRepeatedStates(array $states): array
64    {
65        $kept = [];
66
67        foreach ($states as $state) {
68            $previous = $kept[\count($kept) - 1] ?? null;
69
70            if ($previous === null || $previous->state !== $state->state) {
71                $kept[] = $state;
72            }
73        }
74
75        return $kept;
76    }
77
78    /** @return list<EntityState> */
79    private function listStatesInOrder(EntityId $entityId): array
80    {
81        $states = $this->statesByEntityId[$entityId->value] ?? [];
82        usort($states, static fn(EntityState $a, EntityState $b): int => $a->lastChangedAt?->toEpochMicroseconds() <=> $b->lastChangedAt?->toEpochMicroseconds());
83
84        return $states;
85    }
86
87    private static function stripAttributes(EntityState $state): EntityState
88    {
89        return new EntityState($state->entityId, $state->state, [], $state->lastChangedAt, $state->lastUpdatedAt);
90    }
91}