<?php

declare(strict_types=1);

namespace App\Services;

use Illuminate\Database\Eloquent\Builder;

/**
 * Single search implementation shared by GET /members?q= and
 * GET /members/search (docs/05-performance-and-scale.md §2).
 */
class MemberSearchService
{
    private const OPERATOR_CHARS = ['+', '-', '<', '>', '(', ')', '~', '*', '"', '@'];

    public function apply(Builder $query, string $term): Builder
    {
        $term = trim($term);

        if ($term === '') {
            return $query;
        }

        $digits = preg_replace('/\D+/', '', $term);

        if (strlen($digits) >= 4) {
            return $query->where(function (Builder $q) use ($term, $digits) {
                $q->where('phone_primary', 'like', $digits.'%');

                if (preg_match('/^M?\d+$/i', $term)) {
                    $q->orWhere('member_number', $this->normalizeMemberNumber($digits));
                }
            });
        }

        if (preg_match('/^M\d+$/i', $term)) {
            return $query->where('member_number', $this->normalizeMemberNumber($digits));
        }

        return $this->applyNameSearch($query, $term);
    }

    private function normalizeMemberNumber(string $digits): string
    {
        return 'M'.str_pad($digits, 8, '0', STR_PAD_LEFT);
    }

    private function applyNameSearch(Builder $query, string $term): Builder
    {
        $tokens = preg_split('/\s+/', $term, -1, PREG_SPLIT_NO_EMPTY) ?: [];
        $cleanTokens = array_values(array_filter(
            array_map($this->stripOperatorChars(...), $tokens),
            fn (string $t) => $t !== '',
        ));

        if ($cleanTokens === []) {
            return $query->whereRaw('1 = 0');
        }

        $longTokens = array_values(array_filter($cleanTokens, fn (string $t) => mb_strlen($t) >= 3));
        $shortTokens = array_values(array_filter($cleanTokens, fn (string $t) => mb_strlen($t) < 3));

        return $query->where(function (Builder $q) use ($longTokens, $shortTokens) {
            if ($longTokens !== []) {
                $boolean = implode(' ', array_map(fn (string $t) => '+'.$t.'*', $longTokens));
                $q->whereRaw(
                    'MATCH(first_name, middle_name, last_name, preferred_name) AGAINST (? IN BOOLEAN MODE)',
                    [$boolean],
                );
            }

            foreach ($shortTokens as $token) {
                $q->where(fn (Builder $q2) => $q2
                    ->where('last_name', 'like', $token.'%')
                    ->orWhere('first_name', 'like', $token.'%'));
            }
        });
    }

    private function stripOperatorChars(string $token): string
    {
        return str_replace(self::OPERATOR_CHARS, '', $token);
    }
}
