162 lines
4.0 KiB
PHP
162 lines
4.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Baselines\SnapshotRendering;
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
final readonly class GapSummary
|
|
{
|
|
/**
|
|
* @param list<string> $messages
|
|
*/
|
|
public function __construct(
|
|
public int $count = 0,
|
|
public array $messages = [],
|
|
) {}
|
|
|
|
public static function none(): self
|
|
{
|
|
return new self;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $meta
|
|
*/
|
|
public static function fromItemMeta(array $meta, FidelityState $fidelity, bool $usesFallback = false): self
|
|
{
|
|
$messages = [];
|
|
|
|
$warnings = $meta['warnings'] ?? null;
|
|
|
|
if (is_array($warnings)) {
|
|
foreach ($warnings as $warning) {
|
|
if (is_string($warning) && trim($warning) !== '') {
|
|
$messages[] = trim($warning);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($fidelity === FidelityState::ReferenceOnly) {
|
|
$messages[] = 'Metadata-only evidence was captured for this item.';
|
|
}
|
|
|
|
if ($fidelity === FidelityState::Unsupported || $usesFallback) {
|
|
$messages[] = 'A fallback renderer is being used for this item.';
|
|
}
|
|
|
|
$messages = self::uniqueMessages($messages);
|
|
|
|
return new self(
|
|
count: count($messages),
|
|
messages: $messages,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, int> $reasons
|
|
*/
|
|
public static function fromReasonMap(array $reasons): self
|
|
{
|
|
$messages = [];
|
|
|
|
foreach ($reasons as $reason => $count) {
|
|
if (! is_string($reason) || ! is_numeric($count) || (int) $count <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$messages[] = sprintf('%s (%d)', self::humanizeReason($reason), (int) $count);
|
|
}
|
|
|
|
return new self(
|
|
count: array_sum(array_map(static fn (mixed $value): int => is_numeric($value) ? (int) $value : 0, $reasons)),
|
|
messages: self::uniqueMessages($messages),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<int, self> $summaries
|
|
*/
|
|
public static function merge(array $summaries): self
|
|
{
|
|
$count = 0;
|
|
$messages = [];
|
|
|
|
foreach ($summaries as $summary) {
|
|
$count += $summary->count;
|
|
$messages = [...$messages, ...$summary->messages];
|
|
}
|
|
|
|
$messages = self::uniqueMessages($messages);
|
|
|
|
if ($count === 0) {
|
|
$count = count($messages);
|
|
}
|
|
|
|
return new self(
|
|
count: $count,
|
|
messages: $messages,
|
|
);
|
|
}
|
|
|
|
public function withMessage(string $message): self
|
|
{
|
|
$message = trim($message);
|
|
|
|
if ($message === '') {
|
|
return $this;
|
|
}
|
|
|
|
$messages = self::uniqueMessages([...$this->messages, $message]);
|
|
|
|
return new self(
|
|
count: max($this->count, count($messages)),
|
|
messages: $messages,
|
|
);
|
|
}
|
|
|
|
public function hasGaps(): bool
|
|
{
|
|
return $this->count > 0 || $this->messages !== [];
|
|
}
|
|
|
|
public function badgeState(): string
|
|
{
|
|
return $this->hasGaps() ? 'gaps_present' : 'clear';
|
|
}
|
|
|
|
/**
|
|
* @return array{count: int, has_gaps: bool, messages: list<string>, badge_state: string}
|
|
*/
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'count' => $this->count,
|
|
'has_gaps' => $this->hasGaps(),
|
|
'messages' => $this->messages,
|
|
'badge_state' => $this->badgeState(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param list<string> $messages
|
|
* @return list<string>
|
|
*/
|
|
private static function uniqueMessages(array $messages): array
|
|
{
|
|
return array_values(array_unique(array_values(array_filter(
|
|
array_map(static fn (string $message): string => trim($message), $messages),
|
|
static fn (string $message): bool => $message !== '',
|
|
))));
|
|
}
|
|
|
|
private static function humanizeReason(string $reason): string
|
|
{
|
|
return Str::of($reason)
|
|
->replace(['_', '-'], ' ')
|
|
->headline()
|
|
->toString();
|
|
}
|
|
}
|