<?php

namespace App\Policy\Service;

use App\Policy\Entity\Policy;

/**
 * فهرست **بستهٔ** فیلدها، عملگرها و اثرها per دسته.
 *
 * بند ۸ مستند صریح است: شرط از فهرست بسته می‌آید و کد دلخواه وجود ندارد. دو دلیل:
 *
 *   ۱. قانونی که هر عبارتی را بتواند ارزیابی کند، قابل تحلیل ایستا نیست — و دستهٔ
 *      `spacing` باید به **کوئری** تبدیل شود، نه اینکه per اسلات در PHP اجرا شود
 *      (برای ۹۰ روز غیرقابل قبول است).
 *   ۲. فرم ساخت قانون در پنل از همین schema ساخته می‌شود، نه از فهرستی که در فرانت
 *      دوباره نوشته شده باشد. دو فهرست یعنی دو حقیقت.
 */
final class PolicySchema
{
    // نام‌های عملگر همان‌هایی است که `OperatorRegistry` تعریف می‌کند؛ اینجا فقط برای
    // خوانایی نقاط مصرف نگه داشته شده‌اند و **تعریف تازه‌ای نیستند**.
    public const OP_EQUALS       = OperatorRegistry::OP_EQUALS;
    public const OP_NOT_EQUALS   = OperatorRegistry::OP_NOT_EQUALS;
    public const OP_GREATER_THAN = OperatorRegistry::OP_GREATER_THAN;
    public const OP_LESS_THAN    = OperatorRegistry::OP_LESS_THAN;
    public const OP_IN           = OperatorRegistry::OP_IN;
    public const OP_CONTAINS     = OperatorRegistry::OP_CONTAINS;

    public const OPERATORS = OperatorRegistry::ALL;

    // ── اثرها ───────────────────────────────────────────────────────────────
    public const EFFECT_FORBID           = 'forbid';
    public const EFFECT_REQUIRE_RESOURCE = 'require_resource';
    public const EFFECT_MIN_DURATION     = 'min_duration_minutes';
    public const EFFECT_ADD_DURATION     = 'add_duration_minutes';
    public const EFFECT_MIN_DAYS_BETWEEN = 'min_days_between';
    public const EFFECT_DISCOUNT_PERCENT = 'discount_percent';
    public const EFFECT_DISCOUNT_RIALS   = 'discount_rials';
    public const EFFECT_REQUIRE_FLAG     = 'require_flag';

    /** اثرهای مجاز per دسته — اثر ناسازگار با دسته پذیرفته نمی‌شود. */
    public const EFFECTS = [
        Policy::CATEGORY_SELECTION   => [self::EFFECT_FORBID],
        Policy::CATEGORY_ELIGIBILITY => [self::EFFECT_FORBID, self::EFFECT_REQUIRE_FLAG],
        Policy::CATEGORY_RESOURCE    => [self::EFFECT_REQUIRE_RESOURCE, self::EFFECT_FORBID],
        Policy::CATEGORY_TIMING      => [self::EFFECT_MIN_DURATION, self::EFFECT_ADD_DURATION],
        Policy::CATEGORY_SPACING     => [self::EFFECT_MIN_DAYS_BETWEEN],
        Policy::CATEGORY_PRICING     => [self::EFFECT_DISCOUNT_PERCENT, self::EFFECT_DISCOUNT_RIALS],
    ];

    /**
     * چگونه چند اثرِ هم‌نوع با هم ترکیب می‌شوند — جدول بند ۸.
     *
     * `forbid` هیچ‌وقت ترکیب نمی‌شود: یک ممنوعیت کل عملیات را رد می‌کند، حتی اگر ده
     * قانون مجازکننده باشند.
     */
    public const COMBINATION = [
        self::EFFECT_FORBID           => 'veto',
        self::EFFECT_REQUIRE_RESOURCE => 'union',
        self::EFFECT_REQUIRE_FLAG     => 'union',
        self::EFFECT_MIN_DURATION     => 'max',
        self::EFFECT_MIN_DAYS_BETWEEN => 'max',
        self::EFFECT_ADD_DURATION     => 'sum',
        self::EFFECT_DISCOUNT_PERCENT => 'sum',
        self::EFFECT_DISCOUNT_RIALS   => 'sum',
    ];

    /** برچسب فارسیِ هر اثر — همان چیزی که در فرم دیده می‌شود. */
    private const EFFECT_META = [
        self::EFFECT_FORBID           => ['label' => 'ممنوع کن', 'value_type' => 'none'],
        self::EFFECT_REQUIRE_RESOURCE => ['label' => 'نیاز به نقش', 'value_type' => 'string'],
        self::EFFECT_REQUIRE_FLAG     => ['label' => 'نیاز به تأیید', 'value_type' => 'string'],
        self::EFFECT_MIN_DURATION     => ['label' => 'حداقل مدت (دقیقه)', 'value_type' => 'int'],
        self::EFFECT_ADD_DURATION     => ['label' => 'افزودن مدت (دقیقه)', 'value_type' => 'int'],
        self::EFFECT_MIN_DAYS_BETWEEN => ['label' => 'حداقل فاصله (روز)', 'value_type' => 'int'],
        self::EFFECT_DISCOUNT_PERCENT => ['label' => 'تخفیف درصدی', 'value_type' => 'int'],
        self::EFFECT_DISCOUNT_RIALS   => ['label' => 'تخفیف مبلغی (ریال)', 'value_type' => 'int'],
    ];

    private const CATEGORY_LABELS = [
        Policy::CATEGORY_SELECTION   => 'انتخاب خدمات',
        Policy::CATEGORY_ELIGIBILITY => 'صلاحیت بیمار',
        Policy::CATEGORY_RESOURCE    => 'منابع لازم',
        Policy::CATEGORY_TIMING      => 'مدت نوبت',
        Policy::CATEGORY_SPACING     => 'فاصلهٔ جلسات',
        Policy::CATEGORY_PRICING     => 'قیمت و تخفیف',
    ];

    public function __construct(
        private readonly FieldRegistry    $fields,
        private readonly OperatorRegistry $operators,
    ) {}

    /** @return array<string, mixed> */
    public function describe(): array
    {
        $out = [];

        foreach (Policy::CATEGORIES as $category) {
            $meta = $this->fields->describeCategory($category);

            $out[$category] = [
                'label'      => self::CATEGORY_LABELS[$category],
                'fields'     => array_keys($meta),
                'operators'  => $this->operators->describe(),
                'field_meta' => array_map(
                    static fn (string $key): array => $meta[$key] + ['key' => $key],
                    array_keys($meta),
                ),
                'effects'    => array_map(
                    static fn (string $effect): array => self::EFFECT_META[$effect] + [
                        'type'        => $effect,
                        'combination' => self::COMBINATION[$effect],
                    ],
                    self::EFFECTS[$category],
                ),
            ];
        }

        return $out;
    }

    public function allowsField(string $category, string $field): bool
    {
        return $this->fields->has($field) && $this->fields->allowedIn($field, $category);
    }

    /** @return list<string> */
    public function fieldsFor(string $category): array
    {
        return $this->fields->forCategory($category);
    }

    public function allowsEffect(string $category, string $effect): bool
    {
        return in_array($effect, self::EFFECTS[$category] ?? [], true);
    }
}
