<?php

namespace App\Services;

use Illuminate\Database\Eloquent\Builder;

/**
 * SearchHelper — Smart Search untuk produk storefront.
 *
 * Fitur:
 * - Case-insensitive (DB utf8mb4_unicode_ci sudah handle ini)
 * - Normalize: strip tanda baca, tanda hubung, spasi berlebih
 * - Multi-token: "galon badri" → ['galon', 'badri']
 * - Cari di: nama produk, nama mitra, nama kategori, deskripsi
 * - Scoring: nama produk > mitra > kategori > deskripsi
 *
 * Penggunaan:
 *   $helper  = new SearchHelper($query);
 *   $results = Product::with([...])->where('is_active', true)
 *       ->where(fn($q) => $helper->applyWhere($q))
 *       ->orderByRaw($helper->scoreExpression() . ' DESC')
 *       ->get();
 */
class SearchHelper
{
    /** @var string[] Token hasil normalisasi */
    public readonly array $tokens;

    /** @var string Query mentah */
    public readonly string $raw;

    public function __construct(string $query)
    {
        $this->raw    = $query;
        $this->tokens = $this->tokenize($query);
    }

    /**
     * Apakah ada token yang valid?
     */
    public function hasTokens(): bool
    {
        return count($this->tokens) > 0;
    }

    /**
     * Terapkan WHERE ke query Eloquent Builder.
     * Setiap token harus cocok minimal di satu kolom.
     * Produk yang cocok dengan semua token akan tetap muncul.
     */
    public function applyWhere(Builder $q): void
    {
        foreach ($this->tokens as $token) {
            $like = "%{$token}%";
            $q->where(function (Builder $sub) use ($like) {
                $sub->where('products.name', 'like', $like)
                    ->orWhere('products.description', 'like', $like)
                    ->orWhereHas('partner', fn (Builder $p) =>
                        $p->where('partners.name', 'like', $like))
                    ->orWhereHas('category', fn (Builder $c) =>
                        $c->where('categories.name', 'like', $like));
            });
        }
    }

    /**
     * Ekspresi SQL untuk scoring/ranking.
     * Skor lebih tinggi = lebih relevan.
     *
     * Bobot:
     *   Nama produk   = 8 poin per token
     *   Nama mitra    = 4 poin per token
     *   Kategori      = 2 poin per token
     *   Deskripsi     = 1 poin per token
     *
     * Jika query multi-token (mis: "galon badri"):
     *   - Produk "Galon" dari mitra "Badri" dapat skor tinggi karena
     *     "galon" cocok nama produk (+8) dan "badri" cocok nama mitra (+4)
     */
    public function scoreExpression(): string
    {
        if (empty($this->tokens)) {
            return '0';
        }

        $parts = [];

        foreach ($this->tokens as $token) {
            // Gunakan DB::getPdo()->quote() untuk escaping yang aman (PDO-level)
            // Ini jauh lebih aman dari addslashes() karena PDO escape sesuai driver DB
            $quoted = \Illuminate\Support\Facades\DB::getPdo()->quote('%' . $token . '%');

            // Nama produk — bobot 8
            $parts[] = "(CASE WHEN products.name LIKE {$quoted} THEN 8 ELSE 0 END)";

            // Nama mitra — bobot 4 (via EXISTS subquery)
            $parts[] = "(CASE WHEN EXISTS (
                SELECT 1 FROM partners
                WHERE partners.id = products.partner_id
                AND partners.name LIKE {$quoted}
                AND partners.deleted_at IS NULL
            ) THEN 4 ELSE 0 END)";

            // Nama kategori — bobot 2
            $parts[] = "(CASE WHEN EXISTS (
                SELECT 1 FROM categories
                WHERE categories.id = products.category_id
                AND categories.name LIKE {$quoted}
            ) THEN 2 ELSE 0 END)";

            // Deskripsi — bobot 1
            $parts[] = "(CASE WHEN products.description LIKE {$quoted} THEN 1 ELSE 0 END)";
        }

        return '(' . implode(' + ', $parts) . ')';
    }

    /**
     * Normalisasi query menjadi array token bersih.
     *
     * Langkah:
     * 1. Lowercase
     * 2. Ganti tanda hubung dan tanda baca dengan spasi
     * 3. Hapus karakter yang tidak perlu
     * 4. Trim dan split per spasi
     * 5. Filter token kosong dan terlalu pendek (< 2 karakter)
     *
     * @return string[]
     */
    private function tokenize(string $query): array
    {
        // 1. Lowercase
        $q = mb_strtolower($query);

        // 2. Ganti tanda hubung, garis bawah, dan tanda baca umum dengan spasi
        $q = preg_replace('/[-_.,\/#!$%\^&\*;:{}=\`~()\[\]\'\"]+/', ' ', $q);

        // 3. Normalisasi whitespace
        $q = preg_replace('/\s+/', ' ', $q);
        $q = trim($q);

        if ($q === '') {
            return [];
        }

        // 4. Split per spasi
        $tokens = explode(' ', $q);

        // 5. Filter token terlalu pendek (< 2 char) dan duplikat
        $tokens = array_values(array_unique(
            array_filter($tokens, fn (string $t) => mb_strlen($t) >= 2)
        ));

        return $tokens;
    }
}
