<?php

namespace App\Synapse\Tools;

use App\Models\Institution\Institution;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;

class SearchInstitutionsTool implements Tool
{
    public function description(): Stringable|string
    {
        return 'Search the firm\'s institution database by name or alias. Returns matching institutions with their contact details and postal addresses. Use this whenever you identify an institution name to check whether it already exists in the database and retrieve its verified contact information.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'query' => $schema->string()
                ->description('Institution name or partial name to search for. Searches both display names and known aliases.')
                ->required(),
            'limit' => $schema->integer()
                ->description('Maximum number of results to return (default 10, max 25).')
                ->required(),
        ];
    }

    public function handle(Request $request): Stringable|string
    {
        $query = trim($request['query'] ?? '');
        $limit = min((int) ($request['limit'] ?? 10), 25);

        if ($query === '') {
            return json_encode(['error' => 'query is required']);
        }

        // Break query into significant words (3+ chars, skip noise words) for fuzzy matching
        $stopwords = ['the', 'and', 'for', 'with', 'from', 'that', 'this', 'are', 'was', 'its'];
        $words = collect(preg_split('/[\s\-\/]+/', strtolower($query)))
            ->map(fn ($w) => preg_replace('/[^a-z0-9]/', '', $w))
            ->filter(fn ($w) => strlen($w) >= 3 && !in_array($w, $stopwords))
            ->unique()
            ->values()
            ->all();

        $institutions = Institution::with(['contacts', 'currentAddress', 'aliases', 'dsarType'])
            ->where(function ($q) use ($query, $words) {
                // Full phrase match
                $q->where('display_name', 'like', "%{$query}%")
                  ->orWhereHas('aliases', fn ($a) => $a->where('alias', 'like', "%{$query}%"));
                // Word-level fallback — each significant word as an independent OR clause
                foreach ($words as $word) {
                    $q->orWhere('display_name', 'like', "%{$word}%")
                      ->orWhereHas('aliases', fn ($a) => $a->where('alias', 'like', "%{$word}%"));
                }
            })
            ->limit($limit)
            ->get()
            // Sort by how many query words appear in the display name (best match first)
            ->sortByDesc(function (Institution $inst) use ($words) {
                $name = strtolower($inst->display_name);
                return collect($words)->filter(fn ($w) => str_contains($name, $w))->count();
            })
            ->values();

        if ($institutions->isEmpty()) {
            return json_encode(['results' => [], 'count' => 0, 'message' => 'No institutions found matching that query.']);
        }

        $results = $institutions->map(fn (Institution $inst) => $this->formatInstitution($inst));

        return json_encode(['results' => $results, 'count' => $results->count()]);
    }

    private function formatInstitution(Institution $inst): array
    {
        $addr = $inst->currentAddress;
        $address = null;
        if ($addr) {
            $lines = array_filter([
                $addr->line_1, $addr->line_2, $addr->line_3,
                $addr->line_4, $addr->line_5, $addr->line_6,
                $addr->postcode,
            ]);
            $address = implode(', ', $lines) ?: null;
        }

        return [
            'id'           => $inst->id,
            'display_name' => $inst->display_name,
            'dsar_type'    => $inst->dsarType?->display_name,
            'send_type'    => $inst->send_type,
            'aliases'      => $inst->aliases->pluck('alias')->values()->all(),
            'address'      => $address,
            'contacts'     => $inst->contacts->map(fn ($c) => [
                'name'       => $c->name,
                'job_title'  => $c->job_title,
                'email'      => $c->email,
                'telephone'  => $c->telephone,
                'mobile'     => $c->mobile,
                'is_primary' => $c->is_primary,
            ])->values()->all(),
        ];
    }
}
