<?php

namespace App\Policy\Service;

use App\Policy\Entity\Policy;

/**
 * تنها منبع حقیقتِ فیلدها — schema، استخراج مقدار، و اعتبارسنجی، هر سه از یک جدول.
 *
 * دلیلِ یکی بودنشان همان چیزی است که تسک ۰۹ به‌عنوان خطرِ باقی‌مانده ثبت کرده بود: وقتی
 * فهرست فیلدها در یک کلاس باشد و ساختنِ حقایق در ده نقطهٔ دیگر، فیلدی که در فرم هست و
 * هیچ‌کس نمی‌سازدش بی‌صدا «همیشه‌رد» می‌شود. حالا `extract()` همان‌جایی است که فرم از آن
 * ساخته می‌شود، پس چنین فیلدی اصلاً نمی‌تواند وجود داشته باشد.
 *
 * نام فیلدها عمداً همان نام‌های امروز است، نه نام‌های نقطه‌دار مستند (`patient.age`):
 * شرط‌های ذخیره‌شده در `condition_json` به همین نام‌ها اشاره می‌کنند و تغییرشان یعنی
 * مهاجرت داده روی قانون‌های زندهٔ کلینیک‌ها.
 */
final class FieldRegistry
{
    /**
     * @var array<string, array{label: string, type: string, values?: list<string>, categories: list<string>}>
     */
    private const FIELDS = [
        'item_count' => [
            'label'      => 'تعداد موارد انتخابی',
            'type'       => 'int',
            'categories' => [Policy::CATEGORY_SELECTION, Policy::CATEGORY_RESOURCE, Policy::CATEGORY_TIMING, Policy::CATEGORY_PRICING],
        ],
        'item_uuids' => [
            'label'      => 'موارد انتخابی',
            'type'       => 'list',
            'categories' => [Policy::CATEGORY_SELECTION],
        ],
        'catalog_category' => [
            'label'      => 'دستهٔ کاتالوگ',
            'type'       => 'uuid',
            'categories' => [Policy::CATEGORY_SELECTION, Policy::CATEGORY_RESOURCE, Policy::CATEGORY_TIMING, Policy::CATEGORY_SPACING],
        ],
        'service_uuid' => [
            'label'      => 'سرویس',
            'type'       => 'uuid',
            'categories' => [Policy::CATEGORY_RESOURCE, Policy::CATEGORY_TIMING, Policy::CATEGORY_SPACING],
        ],
        'patient_age' => [
            'label'      => 'سن بیمار',
            'type'       => 'int',
            'categories' => [Policy::CATEGORY_ELIGIBILITY, Policy::CATEGORY_TIMING],
        ],
        'patient_gender' => [
            'label'      => 'جنسیت بیمار',
            'type'       => 'enum',
            'values'     => ['male', 'female'],
            'categories' => [Policy::CATEGORY_ELIGIBILITY],
        ],
        'patient_tags' => [
            'label'      => 'برچسب‌های بیمار',
            'type'       => 'list',
            'categories' => [Policy::CATEGORY_ELIGIBILITY, Policy::CATEGORY_PRICING],
        ],
        'has_parental_consent' => [
            'label'      => 'رضایت والدین',
            'type'       => 'bool',
            'categories' => [Policy::CATEGORY_ELIGIBILITY],
        ],
        'visit_count' => [
            'label'      => 'تعداد ویزیت قبلی',
            'type'       => 'int',
            'categories' => [Policy::CATEGORY_ELIGIBILITY, Policy::CATEGORY_PRICING],
        ],
        'subtotal_rials' => [
            'label'      => 'جمع مبلغ (ریال)',
            'type'       => 'int',
            'categories' => [Policy::CATEGORY_PRICING],
        ],
        'last_visit_at' => [
            'label'      => 'آخرین ویزیت',
            'type'       => 'timestamp',
            'categories' => [Policy::CATEGORY_ELIGIBILITY, Policy::CATEGORY_SPACING, Policy::CATEGORY_PRICING],
        ],
    ];

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

    public function has(string $field): bool
    {
        return isset(self::FIELDS[$field]);
    }

    public function allowedIn(string $field, string $category): bool
    {
        return in_array($category, self::FIELDS[$field]['categories'] ?? [], true);
    }

    /** @return list<string> */
    public function forCategory(string $category): array
    {
        $out = [];

        foreach (self::FIELDS as $name => $meta) {
            if (in_array($category, $meta['categories'], true)) {
                $out[] = $name;
            }
        }

        return $out;
    }

    public function typeOf(string $field): string
    {
        return self::FIELDS[$field]['type'] ?? 'int';
    }

    /**
     * فرادادهٔ فیلدهای یک دسته — همان چیزی که فرم ساخت قانون از آن ساخته می‌شود.
     *
     * عملگرها **فیلترشده per نوع** می‌آیند: اگر فرم همهٔ یازده عملگر را نشان بدهد، کاربر
     * `patient_tags > 5` می‌سازد و ۴۲۲ می‌گیرد بدون اینکه بفهمد چرا.
     *
     * @return array<string, array<string, mixed>>
     */
    public function describeCategory(string $category): array
    {
        $out = [];

        foreach ($this->forCategory($category) as $field) {
            $meta = self::FIELDS[$field];

            $out[$field] = [
                'label'     => $meta['label'],
                'type'      => $meta['type'],
                'operators' => $this->operators->forType($meta['type']),
            ] + (isset($meta['values']) ? ['values' => $meta['values']] : []);
        }

        return $out;
    }

    /**
     * مقدار یک فیلد از حقایق درخواست.
     *
     * `null` در آرایه با «غایب» فرق دارد: اولی یعنی «می‌دانیم که ندارد» (سنِ ثبت‌نشده) و
     * دومی یعنی «این نقطه اصلاً این فیلد را نمی‌سازد». هر دو شرط را رد می‌کنند، ولی فقط
     * دومی نشانهٔ خطای پیکربندی است و باید لاگ شود.
     *
     * @param array<string, mixed> $facts
     */
    public function extract(string $field, array $facts): mixed
    {
        return $facts[$field] ?? null;
    }

    /** @param array<string, mixed> $facts */
    public function supplies(string $field, array $facts): bool
    {
        return array_key_exists($field, $facts);
    }
}
