<?php

namespace App\Values;

use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Str;
use Webmozart\Assert\Assert;

final class SmartPlaylistRule implements Arrayable
{
    public const OPERATOR_IS = 'is';
    public const OPERATOR_IS_NOT = 'isNot';
    public const OPERATOR_CONTAINS = 'contains';
    public const OPERATOR_NOT_CONTAIN = 'notContain';
    public const OPERATOR_IS_BETWEEN = 'isBetween';
    public const OPERATOR_IS_GREATER_THAN = 'isGreaterThan';
    public const OPERATOR_IS_LESS_THAN = 'isLessThan';
    public const OPERATOR_BEGINS_WITH = 'beginsWith';
    public const OPERATOR_ENDS_WITH = 'endsWith';
    public const OPERATOR_IN_LAST = 'inLast';
    public const OPERATOR_NOT_IN_LAST = 'notInLast';
    public const OPERATOR_IS_NOT_BETWEEN = 'isNotBetween';

    public const VALID_OPERATORS = [
        self::OPERATOR_BEGINS_WITH,
        self::OPERATOR_CONTAINS,
        self::OPERATOR_ENDS_WITH,
        self::OPERATOR_IN_LAST,
        self::OPERATOR_IS,
        self::OPERATOR_IS_BETWEEN,
        self::OPERATOR_IS_NOT_BETWEEN,
        self::OPERATOR_IS_GREATER_THAN,
        self::OPERATOR_IS_LESS_THAN,
        self::OPERATOR_IS_NOT,
        self::OPERATOR_NOT_CONTAIN,
        self::OPERATOR_NOT_IN_LAST,
    ];

    private const MODEL_TITLE = 'title';
    public const MODEL_ALBUM_NAME = 'album.name';
    public const MODEL_ARTIST_NAME = 'artist.name';
    private const MODEL_PLAY_COUNT = 'interactions.play_count';
    public const MODEL_LAST_PLAYED = 'interactions.last_played_at';
    private const MODEL_USER_ID = 'interactions.user_id';
    private const MODEL_LENGTH = 'length';
    public const MODEL_DATE_ADDED = 'created_at';
    public const MODEL_DATE_MODIFIED = 'updated_at';
    private const MODEL_GENRE = 'genre';
    private const MODEL_YEAR = 'year';

    private const VALID_MODELS = [
        self::MODEL_TITLE,
        self::MODEL_ALBUM_NAME,
        self::MODEL_ARTIST_NAME,
        self::MODEL_PLAY_COUNT,
        self::MODEL_LAST_PLAYED,
        self::MODEL_LENGTH,
        self::MODEL_DATE_ADDED,
        self::MODEL_DATE_MODIFIED,
        self::MODEL_GENRE,
        self::MODEL_YEAR,
    ];

    public string $id;
    public string $operator;
    public array $value;
    public string $model;

    private function __construct(array $config)
    {
        self::assertConfig($config);

        $this->id = $config['id'] ?? Str::uuid()->toString();
        $this->value = $config['value'];
        $this->model = $config['model'];
        $this->operator = $config['operator'];
    }

    public static function assertConfig(array $config, bool $allowUserIdModel = true): void
    {
        if ($config['id'] ?? null) {
            Assert::uuid($config['id']);
        }

        Assert::oneOf($config['operator'], self::VALID_OPERATORS);
        Assert::oneOf(
            $config['model'],
            $allowUserIdModel ? array_prepend(self::VALID_MODELS, self::MODEL_USER_ID) : self::VALID_MODELS
        );
        Assert::isArray($config['value']);
        Assert::countBetween($config['value'], 1, 2);
    }

    public static function make(array $config): self
    {
        return new self($config);
    }

    /** @return array<mixed> */
    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'model' => $this->model,
            'operator' => $this->operator,
            'value' => $this->value,
        ];
    }

    public function equals(array|self $rule): bool
    {
        if (is_array($rule)) {
            $rule = self::make($rule);
        }

        return $this->operator === $rule->operator
            && !array_diff($this->value, $rule->value)
            && $this->model === $rule->model;
    }
}