<?php

declare(strict_types=1);

namespace App\Administration\Domain\Model\HomeworkRules;

use App\Administration\Domain\Exception\HomeworkRuleParamsInvalidException;

use function array_diff;
use function array_keys;
use function count;
use function in_array;
use function is_int;
use function is_string;
use function preg_match;

/**
 * Définition d'une règle individuelle de devoirs.
 *
 * Value Object immutable contenant le type de règle et ses paramètres.
 * Chaque type de règle impose ses propres paramètres requis.
 *
 * @see FR81: Configurer règles de timing des devoirs
 */
final readonly class HomeworkRule
{
    /**
     * @param array<string, mixed> $params
     */
    public function __construct(
        public RuleType $type,
        public array $params,
    ) {
        $missing = array_diff($type->requiredParams(), array_keys($params));
        if (count($missing) > 0) {
            throw HomeworkRuleParamsInvalidException::missingParams($type, $missing);
        }

        self::validateValues($type, $params);
    }

    private const array VALID_DAYS = [
        'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday',
    ];

    /**
     * @param array<string, mixed> $params
     */
    private static function validateValues(RuleType $type, array $params): void
    {
        match ($type) {
            RuleType::MINIMUM_DELAY => self::validateMinimumDelay($params),
            RuleType::NO_MONDAY_AFTER => self::validateNoMondayAfter($params),
        };
    }

    /**
     * @param array<string, mixed> $params
     */
    private static function validateMinimumDelay(array $params): void
    {
        $days = $params['days'];

        if (!is_int($days) || $days < 1 || $days > 30) {
            throw HomeworkRuleParamsInvalidException::invalidValue(
                RuleType::MINIMUM_DELAY,
                'days',
                'doit être un entier entre 1 et 30',
            );
        }
    }

    /**
     * @param array<string, mixed> $params
     */
    private static function validateNoMondayAfter(array $params): void
    {
        $day = $params['day'];

        if (!is_string($day) || !in_array($day, self::VALID_DAYS, true)) {
            throw HomeworkRuleParamsInvalidException::invalidValue(
                RuleType::NO_MONDAY_AFTER,
                'day',
                'doit être un jour valide en anglais (monday, tuesday, ...)',
            );
        }

        $time = $params['time'];

        if (!is_string($time) || preg_match('/^\d{2}:\d{2}$/', $time) !== 1) {
            throw HomeworkRuleParamsInvalidException::invalidValue(
                RuleType::NO_MONDAY_AFTER,
                'time',
                'doit être au format HH:MM',
            );
        }
    }

    public function equals(self $other): bool
    {
        return $this->type === $other->type && $this->params === $other->params;
    }

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

    /**
     * @param array{type: string, params: array<string, mixed>} $data
     */
    public static function fromArray(array $data): self
    {
        return new self(
            type: RuleType::from($data['type']),
            params: $data['params'],
        );
    }
}
