<?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();
    }
}
