<?php

declare(strict_types=1);

namespace PrettyLinks\GroundLevel\InProductNotifications\Models;

use PrettyLinks\GroundLevel\Support\Time;
use InvalidArgumentException;

class Notification
{
    /**
     * Notification database context.
     */
    public const CONTEXT_DB = 'db';

    /**
     * Notification display context.
     */
    public const CONTEXT_DISPLAY = 'display';

    /**
     * The HTML content of the notification.
     *
     * @var string
     */
    public string $content;

    /**
     * Expiration date.
     *
     * @var string
     */
    public string $expiresAt;

    /**
     * The HTML content of the notification icon.
     *
     * @var string
     */
    public string $icon;

    /**
     * The unique ID.
     *
     * @var string
     */
    public string $id;

    /**
     * Publication date.
     *
     * @var string
     */
    public string $publishesAt;

    /**
     * Whether the notification has been read.
     *
     * @var boolean
     */
    public bool $read;

    /**
     * Read date.
     *
     * @var string
     */
    public int $readAt;

    /**
     * The subject of the notification.
     *
     * @var string
     */
    public string $subject;

    /**
     * The current notification context.
     *
     * When preparing a notification for storage in the database, the {@see self::CONTEXT_DB}
     * context should be used and when preparing a notification for display, the
     * {@see self::CONTEXT_DISPLAY} context should be used.
     *
     * @var string
     */
    private string $context;

    /**
     * Notification constructor.
     *
     * @param array  $rawData An associative array of raw notification data.
     * @param string $context The current notification context.
     *
     * @throws InvalidArgumentException When an invalid context is provided.
     */
    public function __construct(array $rawData, string $context = self::CONTEXT_DB)
    {
        $contexts = [self::CONTEXT_DB, self::CONTEXT_DISPLAY];
        if (! in_array($context, $contexts, true)) {
            throw new InvalidArgumentException(
                "Invalid context: {$context}. Must be one of: " . implode('|', $contexts)
            );
        }
        $this->context = $context;

        $this->content     = $rawData['content'] ?? '';
        $this->expiresAt   = $rawData['expires_at'] ?? $rawData['expiresAt'] ?? '';
        $this->icon        = $rawData['icon'] ?? '';
        $this->id          = $rawData['id'] ?? '';
        $this->publishesAt = $rawData['publishes_at'] ?? $rawData['publishesAt'] ?? '';
        $this->read        = $rawData['read'] ?? false;
        $this->readAt      = $rawData['readAt'] ?? 0;
        $this->subject     = $rawData['subject'] ?? '';

        if (!empty($rawData['buttons'])) {
            $this->appendButtons($rawData['buttons']);
        }
    }

    /**
     * Appends the buttons to the notification content.
     *
     * @param array[] $buttons An array of buttons to append. The arrays are
     *                         passed to {@see \GroundLevel\InProductNotifications\Models\Button::__construct}.
     */
    private function appendButtons(array $buttons): void
    {
        $btns           = array_map(
            function ($button): string {
                return (string) (new Button((array) $button));
            },
            $buttons
        );
        $this->content .= '<p>' . implode(' ', $btns) . '</p>';
    }

    /**
     * Determine if the notification is expired.
     *
     * @return boolean
     */
    public function isExpired(): bool
    {
        return !empty($this->expiresAt) && strtotime($this->expiresAt) <= Time::now();
    }

    /**
     * Determine if the notification is stale.
     *
     * A stale notification is one that has been read more than two weeks ago.
     *
     * Stale notifications are automatically deleted by the {@see Cleaner} service
     * daily.
     *
     * @return boolean
     */
    public function isStale(): bool
    {
        return $this->read && $this->readAt <= strtotime('-2 weeks');
    }

    /**
     * Determines if the notification can be displayed on site.
     *
     * A notification should not be displayed if it's expired or if it's scheduled
     * for publication in the future.
     *
     * @return boolean
     */
    public function shouldDisplay(): bool
    {
        return ! $this->isExpired() && Time::now() >= strtotime($this->publishesAt);
    }

    /**
     * Convert the notification to an associative array.
     *
     * @return array
     */
    public function toArray(): array
    {
        $arr = get_object_vars($this);

        if (self::CONTEXT_DISPLAY === $this->context) {
            $arr['created_at'] = $this->publishesAt;
            unset($arr['expiresAt'], $arr['publishesAt'], $arr['readAt']);
        }
        unset($arr['context']);

        return $arr;
    }
}
