<?php

declare(strict_types=1);

namespace App\Tag;

use Psr\Log\LoggerInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

final readonly class TagVectorSearchClient
{
    public function __construct(
        private HttpClientInterface $http,
        private string $serviceUrl,
        private LoggerInterface $agentLogger,
        private float $minScore = 0.72,
        private int $defaultLimit = 8,
        private int $maxLimit = 50,
        private int $timeoutSeconds = 10,
    ) {
    }

    /**
     * Executes a vector search against the Python tag index.
     *
     * @return list<array{
     *   tag_id:string,
     *   score:float,
     *   label:string,
     *   tag_type:string
     * }>
     */
    public function search(string $query, ?int $limit = null): array
    {
        $query = trim($query);

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

        $limit = $this->clampLimit($limit ?? $this->defaultLimit);
        $serviceUrl = rtrim(trim($this->serviceUrl), '/');

        if ($serviceUrl === '') {
            $this->agentLogger->warning('Tag vector service URL is empty.');

            return [];
        }

        try {
            $response = $this->http->request(
                'POST',
                $serviceUrl . '/search-tags',
                [
                    'json' => [
                        'query' => $query,
                        'limit' => $limit,
                    ],
                    'timeout' => $this->timeoutSeconds,
                ]
            );

            if ($response->getStatusCode() !== 200) {
                $this->agentLogger->warning(
                    'Tag vector service returned non-200.',
                    ['status' => $response->getStatusCode()]
                );

                return [];
            }

            $data = $response->toArray(false);
        } catch (\Throwable $e) {
            $this->agentLogger->warning(
                'Tag vector service unreachable.',
                ['error' => $e->getMessage()]
            );

            return [];
        }

        if (!is_array($data)) {
            $this->agentLogger->warning('Tag vector service returned invalid payload.');

            return [];
        }

        return $this->normalizeHits($data, $limit);
    }

    /**
     * @param array<mixed> $rows
     * @return list<array{
     *   tag_id:string,
     *   score:float,
     *   label:string,
     *   tag_type:string
     * }>
     */
    private function normalizeHits(array $rows, int $limit): array
    {
        $hitsByTagId = [];

        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }

            $tagId = trim((string) ($row['tag_id'] ?? ''));
            $score = $row['score'] ?? null;

            if ($tagId === '' || !is_numeric($score)) {
                continue;
            }

            $score = (float) $score;

            if ($score < $this->minScore) {
                continue;
            }

            $normalizedHit = [
                'tag_id' => $tagId,
                'score' => $score,
                'label' => trim((string) ($row['label'] ?? '')),
                'tag_type' => TagTypes::normalize((string) ($row['tag_type'] ?? TagTypes::GENERIC)),
            ];

            $existingHit = $hitsByTagId[$tagId] ?? null;

            if ($existingHit === null || $normalizedHit['score'] > $existingHit['score']) {
                $hitsByTagId[$tagId] = $normalizedHit;
            }
        }

        if ($hitsByTagId === []) {
            return [];
        }

        $hits = array_values($hitsByTagId);

        usort(
            $hits,
            static function (array $left, array $right): int {
                $scoreComparison = $right['score'] <=> $left['score'];

                if ($scoreComparison !== 0) {
                    return $scoreComparison;
                }

                $typeComparison = strcmp($left['tag_type'], $right['tag_type']);

                if ($typeComparison !== 0) {
                    return $typeComparison;
                }

                return strcmp($left['tag_id'], $right['tag_id']);
            }
        );

        return array_slice($hits, 0, $limit);
    }

    private function clampLimit(int $limit): int
    {
        return max(1, min($limit, $this->maxLimit));
    }
}
