<?php

namespace App\Services\Product;

use App\Models\Product;
use App\Models\ProductConfigOptionDependency;
use Illuminate\Support\Collection;

/**
 * PRC-686 — Bağımlı configurable option değerlendiricisi.
 *
 * Tek karar noktası: bir kaynak option'ın seçili değeri hedef option'ın
 * görünürlüğünü/zorunluluğunu belirler. Hem store JS (kuralları serialize
 * ederek client tarafında) hem de SUNUCU-TARAF guard (bu servis) aynı
 * mantığı kullanır — client-side bypass'ı kapatmak için.
 *
 * Semantik:
 *  - Bir option'a birden fazla show/hide kuralı varsa: kurallar OR birleşir
 *    (herhangi biri "göster" derse görünür; yalnız hiçbiri sağlanmıyorsa
 *    show→gizli, hide→görünür varsayılanına düşer).
 *  - Gizli option zorunlu sayılmaz ve fiyata/validasyona girmez.
 *  - require/optional yalnız görünür option'lar için etkilidir.
 */
class ConfigOptionDependencyEvaluator
{
    /**
     * Bir ürünün bağımlılık kurallarını (grup bazında) düz bir listeye çevir.
     * Store getConfigurableOptions çıktısına eklenir; JS aynı serilizasyonu okur.
     *
     * @return array<int, array<string, mixed>>
     */
    public function rulesForProduct(Product $product): array
    {
        $groupIds = $product->configOptions()
            ->pluck('product_config_options.group_id')
            ->filter()
            ->unique()
            ->values();

        if ($groupIds->isEmpty()) {
            return [];
        }

        return ProductConfigOptionDependency::whereIn('group_id', $groupIds)
            ->orderBy('order')
            ->get()
            ->map(fn (ProductConfigOptionDependency $d) => [
                'source_option_id' => (int) $d->source_option_id,
                'operator' => $d->operator,
                'value_ids' => $d->matchValueIds(),
                'target_option_id' => (int) $d->target_option_id,
                'action' => $d->action,
            ])
            ->all();
    }

    /**
     * Verilen kurallar + seçili değerlere göre her hedef option için
     * görünürlük ve zorunluluk durumunu hesapla.
     *
     * @param  array<int, array<string,mixed>>  $rules      rulesForProduct() çıktısı (ya da eşdeğeri)
     * @param  array<int|string, mixed>          $selected   optionId => value (scalar) | [values]
     * @param  array<int, bool>                  $baseRequired  optionId => option'ın kendi is_required değeri
     * @return array<int, array{visible: bool, required: bool}>  target optionId => durum
     */
    public function evaluate(array $rules, array $selected, array $baseRequired = []): array
    {
        // Kural hedefi olan option'ları topla; kural-hedefi olmayan option'lar
        // her zaman görünür (dokunulmaz).
        $result = [];

        // Hedef bazında kuralları grupla
        $byTarget = [];
        foreach ($rules as $rule) {
            $byTarget[(int) $rule['target_option_id']][] = $rule;
        }

        foreach ($byTarget as $targetId => $targetRules) {
            $base = $baseRequired[$targetId] ?? false;

            // Görünürlük: show/hide kurallarının OR birleşimi.
            $showRules = array_filter($targetRules, fn ($r) => $r['action'] === 'show');
            $hideRules = array_filter($targetRules, fn ($r) => $r['action'] === 'hide');

            $visible = true;

            if (! empty($showRules)) {
                // En az bir show kuralı sağlanmalı, yoksa gizli.
                $visible = false;
                foreach ($showRules as $r) {
                    if ($this->conditionMet($r, $selected)) {
                        $visible = true;
                        break;
                    }
                }
            }

            if ($visible && ! empty($hideRules)) {
                // Herhangi bir hide kuralı sağlanırsa gizle.
                foreach ($hideRules as $r) {
                    if ($this->conditionMet($r, $selected)) {
                        $visible = false;
                        break;
                    }
                }
            }

            // Zorunluluk: require/optional kuralları (yalnız görünürken anlamlı).
            $required = $base;
            if ($visible) {
                foreach ($targetRules as $r) {
                    if ($r['action'] === 'require' && $this->conditionMet($r, $selected)) {
                        $required = true;
                    } elseif ($r['action'] === 'optional' && $this->conditionMet($r, $selected)) {
                        $required = false;
                    }
                }
            } else {
                $required = false; // gizli option asla zorunlu değildir
            }

            $result[(int) $targetId] = [
                'visible' => $visible,
                'required' => $required,
            ];
        }

        return $result;
    }

    /**
     * Tek bir kuralın koşulu, seçili değerler karşısında sağlanıyor mu?
     *
     * @param  array<string,mixed>  $rule
     * @param  array<int|string,mixed>  $selected
     */
    public function conditionMet(array $rule, array $selected): bool
    {
        $sourceId = (int) $rule['source_option_id'];
        $raw = $selected[$sourceId] ?? ($selected[(string) $sourceId] ?? null);

        // Seçili değer(ler)i int listesine indir.
        $selectedIds = $this->normalizeSelected($raw);
        $hasSelection = ! empty($selectedIds);

        $valueIds = array_map('intval', $rule['value_ids'] ?? []);

        switch ($rule['operator']) {
            case 'any':
                return $hasSelection;

            case 'equals':
                if (empty($valueIds)) {
                    return false;
                }
                return in_array($valueIds[0], $selectedIds, true);

            case 'not_equals':
                if (empty($valueIds)) {
                    return false;
                }
                // Kaynak seçili VE seçilen değer eşit değil.
                return $hasSelection && ! in_array($valueIds[0], $selectedIds, true);

            case 'in':
                if (empty($valueIds)) {
                    return false;
                }
                return count(array_intersect($valueIds, $selectedIds)) > 0;

            default:
                return false;
        }
    }

    /**
     * Seçili değeri int id listesine indirger. select → tek id; checkboxgroup
     * → id dizisi; boş/"" → [].
     *
     * @return array<int, int>
     */
    private function normalizeSelected(mixed $raw): array
    {
        if ($raw === null || $raw === '' || $raw === []) {
            return [];
        }

        if ($raw instanceof Collection) {
            $raw = $raw->all();
        }

        if (is_array($raw)) {
            return array_values(array_filter(array_map('intval', $raw)));
        }

        $int = (int) $raw;

        return $int > 0 ? [$int] : [];
    }
}
