<?php

declare(strict_types=1);

namespace Kreait\Firebase\Messaging;

use JsonSerializable;
use Kreait\Firebase\Exception\Messaging\InvalidArgument;

use function array_filter;
use function array_key_exists;
use function implode;
use function in_array;
use function is_array;
use function is_int;
use function is_string;
use function preg_match;
use function sprintf;

/**
 * @see https://tools.ietf.org/html/rfc8030#section-5.3 Web Push Message Urgency
 *
 * @phpstan-type WebPushHeadersShape array{
 *     TTL?: positive-int|numeric-string|null,
 *     Urgency?: self::URGENCY_*
 * }
 *
 * @see https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#webpushfcmoptions WebPush FCM Options
 *
 * @phpstan-type WebPushFcmOptionsShape array{
 *     link?: non-empty-string,
 *     analytics_label?: non-empty-string
 * }
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification#syntax WebPush Notification Syntax
 *
 * @phpstan-type WebPushNotificationShape array{
 *     title: non-empty-string,
 *     options?: array{
 *         dir?: 'ltr'|'rtl'|'auto',
 *         lang?: string,
 *         badge?: non-empty-string,
 *         body?: non-empty-string,
 *         tag?: non-empty-string,
 *         icon?: non-empty-string,
 *         image?: non-empty-string,
 *         data?: mixed,
 *         vibrate?: list<positive-int>,
 *         renotify?: bool,
 *         requireInteraction?: bool,
 *         actions?: list<array{
 *             action: non-empty-string,
 *             title: non-empty-string,
 *             icon: non-empty-string
 *         }>,
 *         silent?: bool
 *     }
 * }
 *
 * @see https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#webpushconfig WebPush Config Syntax
 *
 * @phpstan-type WebPushConfigShape array{
 *     headers?: WebPushHeadersShape,
 *     data?: array<non-empty-string, non-empty-string>,
 *     notification?: WebPushNotificationShape,
 *     fcm_options?: WebPushFcmOptionsShape
 * }
 */
final class WebPushConfig implements JsonSerializable
{
    private const URGENCY_VERY_LOW = 'very-low';
    private const URGENCY_LOW = 'low';
    private const URGENCY_NORMAL = 'normal';
    private const URGENCY_HIGH = 'high';
    private const VALID_URGENCIES = [
        self::URGENCY_VERY_LOW,
        self::URGENCY_LOW,
        self::URGENCY_NORMAL,
        self::URGENCY_HIGH,
    ];

    /**
     * @param WebPushConfigShape $config
     */
    private function __construct(private array $config)
    {
    }

    public static function new(): self
    {
        return new self([]);
    }

    /**
     * @param WebPushConfigShape $config
     */
    public static function fromArray(array $config): self
    {
        // @phpstan-ignore-next-line
        if (array_key_exists('headers', $config) && is_array($config['headers'])) {
            $config['headers'] = self::ensureValidHeaders($config['headers']);

            if ($config['headers'] === []) {
                unset($config['headers']);
            }
        }

        return new self($config);
    }

    public function withHighUrgency(): self
    {
        return $this->withUrgency(self::URGENCY_HIGH);
    }

    public function withNormalUrgency(): self
    {
        return $this->withUrgency(self::URGENCY_NORMAL);
    }

    public function withLowUrgency(): self
    {
        return $this->withUrgency(self::URGENCY_LOW);
    }

    public function withVeryLowUrgency(): self
    {
        return $this->withUrgency(self::URGENCY_VERY_LOW);
    }

    /**
     * @param self::URGENCY_* $urgency
     */
    public function withUrgency(string $urgency): self
    {
        $config = clone $this;

        $config->config['headers'] ??= [];
        $config->config['headers']['Urgency'] = $urgency;

        return $config;
    }

    public function jsonSerialize(): array
    {
        // @phpstan-ignore-next-line
        return array_filter($this->config, static fn($value) => $value !== null);
    }

    /**
     * @param WebPushHeadersShape $headers
     *
     * @return WebPushHeadersShape
     */
    private static function ensureValidHeaders(array $headers): array
    {
        if (array_key_exists('TTL', $headers)) {
            if (is_int($headers['TTL'])) {
                $headers['TTL'] = (string) $headers['TTL'];
            }

            if (is_string($headers['TTL']) && (preg_match('/^[1-9]\d*$/', $headers['TTL']) !== 1)) {
                throw new InvalidArgument('The TTL in the WebPushConfig must must be a positive int');
            }

            if ($headers['TTL'] === null) {
                unset($headers['TTL']);
            }
        }

        if (!array_key_exists('Urgency', $headers)) {
            return $headers;
        }

        if (in_array($headers['Urgency'], self::VALID_URGENCIES, true)) {
            return $headers;
        }

        throw new InvalidArgument(sprintf(
            'The Urgency in the WebPushConfig header must must be one of %s',
            implode(',', self::VALID_URGENCIES),
        ));
    }
}
